c++动态库详解
概念
动态库, 又称动态链接库(\(Dynamic\) \(Link\) \(Library\), \(DLL\)), 是包含程序代码和数据的可执行文件, 在运行时被程序加载和链接
动态库通过将功能封装, 实现代码模块化, 使程序更加灵活和易于维护, 还有助于共享数据和资源, 以减少内存占用, 并提高程序运行效率
其与静态库主要区别在于动态库代码并不在程序编译时直接包含, 而是在程序执行时根据需要动态加载
X(文件后缀)
X-->A(Linux)-->A1(.so)
X-->B(Windows)-->B1(.dll)
X-->C(macOS)-->C1(.dylib)
特点
- 运行时加载
运行时才被加载到内存, 而非编译时就包含在可执行文件中, 可节省内存
- 共享性
多程序可共享同个动态库, 共享内存中相同代码, 减少资源占用
- 版本控制
动态库可单独更新, 若功能更改只需替换库文件, 而不必重新编译所有相关程序
- 支持多语言
动态库通常可被多种编程语言调用, 可在不同开发环境中灵活使用
开发
设以下示例代码, 生成动态库 HelloAPI.dll/so
// HelloAPI.hpp
#include <iostream>
#ifndef __INCLUDE_HELLO_API_HPP__
#define __INCLUDE_HELLO_API_HPP__
#if defined(_WIN32)
#define __EXPORT __declspec(dllexport)
#elif defined(__linux__)
#define __EXPORT __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif
__EXPORT void Hello();
#ifdef __cplusplus
}
#endif
#endif
// HelloAPI.cpp
#include "HelloAPI.hpp"
void Hello() {
std::cout << "Hello World" << std::endl;
}
特性
在创建C和C++动态库时有一些关键差异特性
命名修饰(Name Mangling)
- 定义
C++编译器为支持函数重载, 会对函数名称进行特殊编码, 以区分不同函数签名, 称为名称修饰
- 处理
C语言并无命名修饰机制, 因此直接调用C++动态库会导致链接错误
若要支持C调用, 需在函数名前添加 extern "C", 或以 extern "C" {...}包裹, 使函数按C方式处理
// 若是C++编译器
#ifdef __cplusplus
extern "C" {
#endif
// C语言可调用函数声明
void Func();
#ifdef __cplusplus
}
#endif
- 注意
(1) extern "C"只能用于函数和全局变量声明, 不能用于类成员或模板
(2) extern "C"修饰函数内不能出现C++所有特性
导出符号(Export Symbol)
- 定义
为将函数从动态库中导出被其他程序调用, 需在函数前添加导出符号
windows中为__declspec(dllexport)关键字, linux中为__attribute__((visibility("default")))
- 使用
#ifdef _WIN32
#define __EXPORT __declspec(dllexport)
#elif defined(__linux__)
#define __EXPORT __attribute__((visibility("default")))
#endif
__EXPORT void Hello();
- 注意
若没有正确导出符号, 动态库中函数、变量或对象将无法被其他程序或库调用, 引发链接错误
编译
命令行
g++ <*.cpp> -fPIC -shared -o <*.so/*.dll>
X(参数)
X-->A("-fPIC")
A-->A1(Position Independent Code 位置无关代码)
A-->A2(生成代码可在内存中任意位置运行)
X-->B("-shared")-->B1(生成一个共享库)
X-->C("-o")-->C1(指定输出文件名)
将HelloAPI.hpp与HelloAPI.cpp生成HelloAPI动态库

cmake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HelloAPI)
add_library(${PROJECT_NAME} SHARED "")
target_sources(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/HelloAPI.cpp)

xmake
-- xmake.lua
add_rules("mode.debug", "mode.release")
target("HelloAPI")
set_kind("shared")
add_files("HelloAPI.cpp")


分类
源文件不含类
不含类时生成动态库可直接调用, 例如上面HelloAPI.hpp与HelloAPI.cpp
源文件含类
// MyClass.hpp
#include <iostream>
class MyClass {
public:
MyClass() = default;
~MyClass() = default;
void SetValue(const int val);
void Print() const;
private:
int mValue;
};
// MyClass.cpp
#include "MyClass.hpp"
void MyClass::SetValue(const int val) {
this->mValue = val;
}
void MyClass::Print() const {
std::cout << "mValue = " << mValue << std::endl;
}
- 类调用(仅支持C++)
以类调用时需增加导出符号, 修改MyClass.hpp如下
#include <iostream>
#ifdef _WIN32
#define __EXPORT __declspec(dllexport)
#else
#define __EXPORT __attribute__((visibility("default")))
#endif
class __EXPORT MyClass {
public:
MyClass() = default;
~MyClass() = default;
void SetValue(const int val);
void Print() const;
private:
int mValue;
};
测试
// Main.cpp
#include "MyClass.hpp"
int main() {
MyClass myClass;
myClass.SetValue(0xFFFF);
myClass.Print();
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyClassAPI)
add_library(${PROJECT_NAME} SHARED "")
target_sources(${PROJECT_NAME} PUBLIC${CMAKE_SOURCE_DIR}/MyClass.cpp)
add_executable(Main Main.cpp)
target_link_libraries(Main ${PROJECT_NAME})

- 函数式调用(可支持C/C++)
类外再封装一层C接口
// MyClassDLL.hpp
#include "MyClass.hpp"
#ifdef _WIN32
#define __EXPORT __declspec(dllexport)
#else
#define __EXPORT __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif
__EXPORT void* MyClassCreate();
__EXPORT void MyClassDestroy(void* handle);
__EXPORT void MyClassSetValue(void* handle, int val);
__EXPORT void MyClassPrint(void* handle);
#ifdef __cplusplus
}
#endif
// MyClassDLL.cpp
#include "MyClassDLL.hpp"
__EXPORT void* MyClassCreate() {
return new MyClass();
}
__EXPORT void MyClassDestroy(void* handle) {
delete static_cast<MyClass*>(handle);
}
__EXPORT void MyClassSetValue(void* handle, int val) {
MyClass* obj = static_cast<MyClass*>(handle);
obj->SetValue(val);
}
__EXPORT void MyClassPrint(void* handle) {
MyClass* obj = static_cast<MyClass*>(handle);
obj->Print();
}
测试
// Main.cpp
#include "MyClassDLL.hpp"
int main() {
void* handle = MyClassCreate();
MyClassSetValue(handle, 0xFFFF);
MyClassPrint(handle);
MyClassDestroy(handle);
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyClassDLLAPI)
add_library(${PROJECT_NAME} SHARED "")
target_sources(${PROJECT_NAME} PUBLIC
${CMAKE_SOURCE_DIR}/MyClass.cpp
${CMAKE_SOURCE_DIR}/MyClassDLL.cpp
)
add_executable(Main Main.cpp)
target_link_libraries(Main ${PROJECT_NAME})

模板
// TemplateDLL.hpp
#ifndef __INCLUDE_TEMPLATE_DLL_HPP__
#define __INCLUDE_TEMPLATE_DLL_HPP__
#include <iostream>
#ifdef _WIN32
#define __EXPORT __declspec(dllexport)
#else
#define __EXPORT __attribute__((visibility("default")))
#endif
template<typename T>
T Sub(T x, T y);
template<typename T>
class TemplateDLL {
public:
TemplateDLL() = default;
~TemplateDLL() = default;
static T Add(T x, T y);
};
#endif
// TemplateDLL.cpp
#include "TemplateDLL.hpp"
template __EXPORT int Sub<int>(int, int);
template __EXPORT double Sub<double>(double, double);
template class __EXPORT TemplateDLL<int>;
template class __EXPORT TemplateDLL<double>;
template class __EXPORT TemplateDLL<std::string>;
template<typename T>
T Sub(T x, T y) {
return T(x - y);
}
template<typename T>
T TemplateDLL<T>::Add(T x, T y) {
return T(x + y);
}
- 测试
// Main.cpp
#include "TemplateDLL.hpp"
int main() {
std::cout << Sub<int>(0xA, 0xB) << std::endl;
std::cout << Sub<double>(1.234, 9.876) << std::endl;
std::cout << TemplateDLL<int>::Add(0xA, 0xB) << std::endl;
std::cout << TemplateDLL<double>::Add(1.234, 9.876) << std::endl;
std::cout << TemplateDLL<std::string>::Add("Hello", "World") << std::endl;
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(TemplateDLL)
add_library(${PROJECT_NAME} SHARED "")
target_sources(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/TemplateDLL.cpp)
add_executable(Main Main.cpp)
target_link_libraries(Main ${PROJECT_NAME})

调用
隐式链接
隐式链接会在链接时让编译器将动态库链接到可执行文件中, 运行时自动加载
// Main.cpp
#include "HelloAPI.hpp"
int main(void) {
Hello();
return 0;
}
隐式调用上面HelloAPI动态库
命令行
g++ [源文件] [库文件] -o [可执行文件]

若报找不到库文件错误, 移动文件到/usr/lib目录
cmake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Main)
add_library(HelloAPI SHARED "")
target_sources(HelloAPI PUBLIC ${CMAKE_SOURCE_DIR}/HelloAPI.cpp)
add_executable(${PROJECT_NAME} "")
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/Main.cpp)
target_link_libraries(${PROJECT_NAME} HelloAPI)

显式链接
显式链接是通过接口函数显式链接动态库并直接调用库中函数, 调用流程如下
X(开始)
A[加载<br>dlopen<br>LoadLibrary]
B[获取函数地址<br>dlsym<br>GetProcAddress]
C[调用函数]
D[关闭<br>dlclose<br>FreeLibrary]
Z(结束)
X-->A-->B-->C-->D-->Z
显式调用上面HelloAPI动态库
#include<iostream>
#if defined (_WIN32) | defined (_WIN64)
#include<windows.h>
#elif defined (__linux__)
#include <dlfcn.h>
#endif
typedef void(*VoidFunc)();
int main() {
// 加载
#if defined (_WIN32) | defined (_WIN64)
HMODULE handle = LoadLibrary("HelloAPI.dll");
if (!handle) {
std::cerr << "无法加载动态库: " << GetLastError() << std::endl;
}
VoidFunc helloFunc = (VoidFunc)GetProcAddress(handle, "Hello");
if (helloFunc == nullptr) {
std::cerr << "无法找到函数: " << GetLastError() << std::endl;
FreeLibrary(handle);
}
#elif defined (__linux__)
void* handle = dlopen("HelloAPI.so", RTLD_LAZY | RTLD_LOCAL);
if (!handle) {
std::cerr << "无法加载动态库: " << dlerror() << std::endl;
}
VoidFunc helloFunc = (VoidFunc)dlsym(handle, "Hello");
if (helloFunc == nullptr) {
std::cerr << "无法找到函数: " << dlerror() << std::endl;
dlclose(handle);
}
#endif
// 调用
helloFunc();
// 卸载
#if defined (_WIN32) | defined (_WIN64)
FreeLibrary(handle);
#elif defined (__linux__)
dlclose(handle);
#endif
return 0;
}
Linux环境下显示链接时需额外链接加载器库dl
命令行
g++ Main.cpp -o Main (-ldl)

cmake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Main)
add_executable(${PROJECT_NAME} "")
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/Main.cpp)
if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
target_link_libraries(${PROJECT_NAME} dl)
endif()


xmake
-- xmake.lua
add_rules("mode.debug", "mode.release")
target("Main")
set_kind("binary")
add_files("Main.cpp")
add_links("HelloAPI")
add_linkdirs(".")
if is_os("linux") then
add_syslinks("dl")
end


IDE调用
VS2022
创建解决方案Project与动态链接库项目DllTest, 在Project项目中调用DllTest中生成的动态库

- 编写
DllTest/pch.h
#include <iostream>
#define __EXPORT __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif
__EXPORT void PrintInfo();
__EXPORT int Add(int x, int y);
#ifdef __cplusplus
}
#endif
DllTest/pch.cpp
void PrintInfo() {
std::cout << "Hello World" << std::endl;
}
int Add(int x, int y) {
return x + y;
}

生成动态库DllTest.dll与动态库导入库DllTest.lib

- 使用
// Main.cpp
#include "pch.h"
int main() {
PrintInfo();
std::cout << Add(1, 2) << std::endl;
}
将pch.h 与DllTest.dll、DllTest.liub拷贝到Project项目中

添加DllTest.lib路径, 用于导入动态库




c++动态库详解的更多相关文章
- Linux下Gcc生成和使用静态库和动态库详解(转)
一.基本概念 1.1什么是库 在windows平台和linux平台下都大量存在着库. 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行. 由于windows和linux的平台不同( ...
- Linux下Gcc生成和使用静态库和动态库详解
参考文章:http://blog.chinaunix.net/uid-23592843-id-223539.html 一.基本概念 1.1什么是库 在windows平台和linux平台下都大量存在着库 ...
- Linux-Gcc生成和使用静态库和动态库详解
一.基本概念 1.1什么是库 在windows平台和linux平台下都大量存在着库. 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行. 由于windows和linux的平台不同( ...
- 【转】 iOS 开发之静态库.a和动态库详解 -- 不错
原文网址:http://blog.csdn.net/lxl_815520/article/details/52154331 一, 简单介绍 1.什么是库 库是程序代码的集合,是共享程序代码的一种方式 ...
- linux下的静态库与动态库详解
静态库 先说说我们为什么需要库? 当有些代码我们大量会在程序中使用比如(scanf,printf等)这些函数我们需要在程序中频繁使用,于是我们就把这些代码编译为库文件,在需要使用时我们直接链接即可. ...
- 【转】Linux下gcc生成和使用静态库和动态库详解
一.基本概念 1.1 什么是库 在Windows平台和Linux平台下都大量存在着库. 本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行. 由于windows和linux的平台不 ...
- Linux静态库与动态库详解
引言 为了代码的复用性和模块化,我们常常使用一些库文件,在Windows操作系统下位.lib .dll作为静态库和动态库的后缀名. 在Linux下,静态链接库名字一般为libabcdef.a,其中ab ...
- C++静态库与动态库详解
1 库的概念? 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库. 2 动态库与静态库的概念? 先回顾一下编译过程: 2.1 静态库 静态库在链接阶段,会将汇编生成的目 ...
- Linux共享库、静态库、动态库详解
1. 介绍 使用GNU的工具我们如何在Linux下创建自己的程序函数库?一个“程序函数库”简单的说就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用.程序函数库 ...
- (zz)Linux下Gcc生成和使用静态库和动态库详解
http://blog.chinaunix.net/uid-23592843-id-223539.html
随机推荐
- DOM – Dimension & Coordinate (offset, client, computed, rect)
前言 很多年前有记入过一篇 box-sizing 和 dom width. 想想也挺可悲的, 那年我是负责后端的, 却要帮着前端去学习这些知识来解决问题... 也好, 现在 full stack, 也 ...
- Vue3——Vite + element-plus +Vue3 项目搭建、"@"别名设置
1. 环境准备 node 官网 npm 切换国内 npm 源镜像 npm config set registry https://registry.npmmirror.com 查看当前的镜像源 npm ...
- 致敬传奇 Kruskal 重构树题硬控我三小时
NOI2018 归程 存边的数组拿来干两件事,忘了清空了,其实最好开两个的 dfs 没开 vis 导致不知道为什么出现的绕圈 倍增的 fa[i][j] 定义的时候前面是 \(2^{i}\) 写着写着记 ...
- Spark集群的安装及高可用配置
spark官网学习文档 Spark集群的安装及高可用配置 前期需求:Hadoop和Scala必须已经安装完成 步骤: ①进入spark下载网站中https://spark.apache.org/dow ...
- 诸葛测字在线测算免费api接口_json格式数据返回
诸葛测字是一种古老的占卜术,相传是由三国时代的诸葛亮所创. 诸葛测字通过选取三个汉字,计算其笔画数,然后根据这些数字对照特定的歌诀或表格来预测吉凶.解答疑惑.这种方法强调心诚手净,焚香祷告,通过三 ...
- js的作用域有哪些 and 他们的特点
全局作用域:是所有代码的执行环境,比如script标签里所有的代码 或 独立的js 文件: 局部作用域:是函数内部代码的执行环境: 块级作用域:是 {} 内的代码执行环境:
- 妙用编辑器:使用Notepad--的标记颜色功能更高效的阅读日志文件
应用场景 在日常维护工作中,经常需要查看一些日志,以判断系统的运行状态或者进行问题定位,当系统出现故障时,一般都会有特殊的关键字,但对于浩如烟海的日志来说,识别这些关键字信息还是非常费眼力的,比如有如 ...
- 云原生周刊:Meshery v0.70 发布 | 2024.1.22
开源项目推荐 flux-cluster-template 该项目用于部署由 Flux.SOPS.GitHub Actions.Renovate.Cilium 等支持的 Kubernetes 集群,专注 ...
- 你真的理解 Kubernetes 中的 requests 和 limits 吗?
在 Kubernetes 集群中部署资源的时候,你是否经常遇到以下情形: 经常在 Kubernetes 集群种部署负载的时候不设置 CPU requests 或将 CPU requests 设置得过低 ...
- KubeSphere 社区双周报 | OpenFunction 发布 v1.1.0 | 2023.5.26-6.8
KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...