C++动态库详解

news/2024/11/28 0:17:39/文章来源:https://www.cnblogs.com/dmjcb/p/18573360

dmjcb个人博客
原文地址

动态库

概念

动态库($Dynamic$ $Library$), 又称动态链接库($Dynamic$ $Link$ $Library$, $DLL$), 是一种在程序运行时所加载文件

其与静态库主要区别在于动态库代码并不在程序编译时直接包含, 而是在程序执行时根据需要动态加载

格式

graph LR;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++中需要导出C风格接口(即无名称修饰), 需用 extern "C" 告知编译器按C语言规则导出

#ifdef __cplusplus
extern "C" {
#endif// 具体函数#ifdef __cplusplus
}
#endif

导出符号(Symbol Export)

为将函数从动态库中导出被其他程序使用, 需在函数前添加导出符号

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();

编译

完整代码路径

命令行

graph LR;X(参数)X --> A(fPIC) --> A1(创建与地址无关编译程序 ,使在多个应用程序间共享)X --> B(shared) --> B1(尽量使用动态库, 但需要系统动态库)
g++ [*.cpp] -fPIC -shared -o [*.so/*.dll]

将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;
}
  • 类调用

以类调用时需增加导出符号, 修改MyClass.hpp如下

#include <iostream>#ifdef _WIN32#define __EXPORT __declspec(dllexport)
#else#define __EXPORT __attribute__((visibility("default")))
#endifclass __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})

  • 函数式调用

若通过函数调用, 则需于类外再封装一层调用

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")))
#endiftemplate<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)

显式链接

显式链接是通过接口函数显式链接动态库并直接调用库中函数, 调用流程如下

graph LR;A(加载动态库<br>dlopen<br>LoadLibrary)B(获取库函数地址<br>dlsym<br>GetProcAddress)C(调用)D(关闭动态库<br>dlclose<br>FreeLibrary)A-->B-->C-->D

显式调用上面HelloAPI动态库

#include<iostream>#if defined (_WIN32) | defined (_WIN64)#include<windows.h>
#elif defined (__linux__)#include <dlfcn.h>
#endiftypedef 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);
#endifreturn 0;
}

Linux编译时需链接动态链接库的加载器库dl, Windows则不需要

命令行

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") thenadd_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

  • 使用

将pch.h 与生成的DllTest.dll、DllTest.liub拷贝到Project项目中

新建Main.cpp

#include "pch.h"int main() {PrintInfo();std::cout << Add(1, 2) << std::endl;
}

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

运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/842584.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数分笔记

符号说明(部分) 存在唯一:\(\exist|\) 或 \(\exist!\) 使得:\(\operatorname{s.t.}\)(so that/such that) 非:\(\neg\) 正整数:\(\mathbb{Z}^+,\mathbb{N}_+,\mathbb{Z}_+,\mathbb{N}^+\) 定义为:\(\triangleq\) 或 \(\dot=\) 笛卡尔乘积 \(A\times B=\{(a,b)|a\in A,…

自用软件推荐、 思源笔记插件 | 记录

效率软件mykeyMap,可以设置键盘快捷键,提高码字效率。小键盘福音。 ​​自用习惯:(超级键Caps组合) 可以设置快捷启动软件 可以快速切换窗口,管理窗口。 可以快速调节音量 可以划词直接搜索其他网站内容(还挺好用的,平时搜索素材不用一个一个地打开网站再复制进去搜索)…

从零开始学 Maven:简化 Java 项目的构建与管理

Maven 是一个由 Apache 软件基金会开发的项目管理和构建自动化工具。它主要用在 Java 项目中,但也可以用于其他类型的项目。一、关于Maven 1.1 简介 Maven 是一个由 Apache 软件基金会开发的项目管理和构建自动化工具。它主要用在 Java 项目中,但也可以用于其他类型的项目。M…

左侧导航栏element -2024/11/27

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>首页</title><style>.demo-table-expand {font-size: 0;}.demo-table-expand label {width: 90px;color: #99a9bf;}.demo-table-expand …

考研打卡(28)

开局(28) 开始时间 2024-11-27 22:50:07 结束时间 2024-11-27 23:25:29明天是1124刚才和室友去吃了一百一的羊肉火锅数据结构 设哈希表长m=14,哈希函数H(key)=key MOD 11。 表中已有4个节点addr(15)=4,addr(38)=5,addr(61)=6,addr(84)=7, 其余地址为空,如用二次探查再散…

ThreeJs-04详解材质与纹理

一.matcap材质 这个材质不会受到光照影响,但是如果图片本身有光就可以一直渲染这个图片本来的样子,用来将一个图片纹理渲染到物体上的材质代码实现 加载模型后,开启纹理渲染,并把它的材质变为这个材质,并且贴上纹理图二.Lambert材质 Lambert网格材质是Three.js中最基本和常…

Java学习笔记——2024.11.27

2024.11.27 一、字符类型 1.字符类型初探可以存放一个汉字(2字节)或者数字(这个c4存储的应该是ASCII编码为97的字符,也就是a)2.字符类型细节public class Chardetial {public static void main(String[] args) {char c1 = 97;System.out.println(c1); // achar c2 = a;System.…

JDBC API

1.DriverManager 1》注册驱动,mysql8以上版本可省 2》连接数据库 2.Connection 1》获取执行sql对象 2》管理事务(统一失败)3.Statement 1》执行sql语句4.ResultSet创建集合 List accounts = new ArrayList<类名>();accounts为集合名 往集合中添加类 accounts.add(类名…

linux模拟HID USB设备及wireshark USB抓包配置

目录1. 内核配置2. 设备配置附 wireshark USB抓包配置 linux下模拟USB HID设备的简单记录,其他USB设备类似。 1. 内核配置 内核启用USB Gadget,使用fs配置usb device信息。 Device Drivers ---> [*] USB support ---><*> USB Gadget Support ---><*…

2024御网杯信息安全大赛个人赛wp_2024-11-27

MISC题解 题目附件以及工具链接: 通过网盘分享的文件:御网杯附件 链接: https://pan.baidu.com/s/1LNA6Xz6eZodSV0Io9jGSZg 提取码: jay1 --来自百度网盘超级会员v1的分享一.信息安全大赛的通知 Ctrl+A发现存在隐写:选中中间空白那一部分,把文字变成红色,得出flag二、编码…

设计模式--原型模式及其编程思想

原型模式(Prototype Pattern) 原型模式的核心思想是通过复制(克隆)现有对象来创建新对象。 原型模式通常涉及两个角色:原型对象和具体原型对象。原型对象是需要被复制的对象,而具体原型对象是实现了克隆方法的原型对象。 在Java中,原型模式通常通过实现Cloneable接口和重…

JS-DOM与BOM

DOM DOM(Document Object Model) 文档对象模型是W3C组织制定并推荐的标准, 这个标准提供了一系列操作HTML的统一API(Application Programming Interface) 核心对象是document 浏览器的工作流程浏览器读取HTML文件 在内存中生成DOM树 调用API渲染显示DOM树 DOM树 是将HTML文档映…