C++ DLL 工程创建与使用
DLL,是 Dynamic Link Library的缩写,中文名
动态链接库
。DLL是一个包含可由多个程序,同时使用的代码和数据的库。 本文简介DLL 概念,记录 DLL 工程创建与使用方法。
简介
- 动态链接库( Dynamic-link library,缩写为 DLL) 是微软公司在windows 系统中实现共享函数库概念的一种实现方式。所谓动态链接,就是把常用的公共函数封装到 DLL 文件中,当程序需要用到这些函数时,系统才会动态地将 DLL 加载到内存中使用。
- 调用方式主要分为两种:
- 静态加载: 启动时加载DLL:需要使用
.h
头文件和.lib
文件 - 动态加载: 运行时加载DLL:使用
LoadBibrary()
GetProcessAddress()
- 静态加载: 启动时加载DLL:需要使用
- 动态链接库的扩展名:
.dll
,.ocx
或者.drv
(驱动程序)。
动态链接库的优势
- 由于 DLL 可以在需要时加载,因此可以节约内存空间,提升运行效率;
- 更新 DLL 不需要重新编译链接整个程序,仅更换 DLL、lib 、头文件等文件即可。
调用方式
定义外部接口
- 不是所有 dll 中的函数都可以在装载后调用,需要向外开放的内容在声明时需要加前缀
__declspec(dllexport)
- 我看到的现象是如果需要动态加载的函数,还额外需要定义在
extern "C"
函数体中
静态加载
- 静态加载 dll 是在程序启动时加载,需要使用
.h
头文件和.lib
文件 - 在应用程序中引入 dll 的头文件声名接口,引入库 lib 文件,在程序目录中包含 dll 文件,即可将 dll 中向外开放的接口当作正常接口使用
动态加载
- 可以在程序运行过程中随时动态加载 dll 中为动态加载开放的函数
- 完整使用流程如下:
- 声明函数指针
typedef DWORD(*MYDEMOW)();
- 定义函数指针变量
MYDEMOW demo =
- 动态加载DLL到内存
hmo = LoadLibrary(_T("DLL2.dll"));
- 函数指针变量接收DLL中加载函数的地址
MYDEMOW demo= (MYDEMOW)GetProcAddress(hmo, "DEMOW")
- 调用函数指针
demo();
- 释放动态链接库
FreeLibrary(hmo);
动态链接库搜索顺序
- 对于Windows,加载动态链接库时:
- 如果内存中已经有同module名的DLL,除非是DLL redirection或manifest,否则直接就用内存中这个DLL而不再搜索。
- 如果DLL名字属于当前Windows版本的Known DLL,则必须用Known DLL。清单见 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
- 如果DLL有依赖DLL,操作系统按缺省标准规则根据module名字搜索依赖DLL。即使第一个DLL指定了全路径。
- Windows Desktop应用程序的DLL标准搜索序:
- 应用程序所在目录;
- 系统目录。GetSystemDirectory函数返回该目录。
- 16比特系统目录;
- Windows目录。使用GetWindowsDirectory函数返回该目录。
- 当前(工作)目录;
- 环境变量PATH中列出的目录。
- 如果SafeDllSearchMode被禁止,则当前目录成为第二个被搜索的目录。
创建 DLL
- 以 Visual Studio 2017 环境为例:
文件
->新建
->项目
->Visual C++
->Windows 桌面
->动态链接库
。- 我给项目起名
dll_demo
- 新建头文件
dll.h
,在其中声明外界调用的类和接口,我在这里设置了几种示例,用于静态、动态调用测试:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 | #pragma once#ifdef CREATEDELL_API_DU#else #define CREATEDELL_API_DU _declspec(dllimport) //当编译时,头文件不参加编译,所以.cpp文件中先定义,后头文件被包含进来,因此外部使用时,为dllexport,而在内部编译时,则为dllimport#endif // 静态加载测试类class CREATEDELL_API_DU animal //需要被外界调用的类(父类){public: virtual int outDate() = 0; //纯虚函数 void getWide(int x); void getHigh(int y);protected: int wide; int high;};class CREATEDELL_API_DU cat :public animal //需要被调用的类(子类cat){public: int outDate();};class CREATEDELL_API_DU dog :public animal //需要被调用的类(子类dog){public: int outDate();};// 静态/动态 加载测试函数int square1(int x);extern "C"{ __declspec(dllexport) int square2(int x);}__declspec(dllexport) int square3(int x);//动态加载测试类class Math{public: virtual double add(double a, double b) = 0; virtual double substract(double a, double b) = 0; virtual double multiply(double a, double b) = 0; virtual double divide(double a, double b) = 0; virtual ~Math() {};};extern "C"{ __declspec(dllexport) Math* FactoryCreate(); __declspec(dllexport) void FactoryDestroy(Math* pmath);}class MathImplementation : public Math{public: double add(double a, double b); double substract(double a, double b); double multiply(double a, double b); double divide(double a, double b);}; |
---|
新建 dll.cpp
源文件,在其中完成对外类、接口的实现
在源文件开头需要引入 pch.h
加入
#include "pch.h"
, 否则会报错 1错误 C1010 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include "pch.h"”? dll_demo
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 | #include "pch.h"#define CREATEDELL_API_DU _declspec(dllexport)#include <iostream>#include "dll.h"using namespace std;//父类中函数实现 //静态加载类实现void animal::getWide(int x) { wide = x;}void CREATEDELL_API_DU animal::getHigh(int y) { high = y;}//子类cat中数据输出实现int CREATEDELL_API_DU cat::outDate() { return (wide + high); wide += wide; high += high;}//子类dog数据输出实现int CREATEDELL_API_DU dog::outDate() { return (wide - high);}//函数的实现//测试函数实现int square1(int x){ return x * x;}int square2(int x){ return x * x;}int square3(int x){ return x * x;}//动态加载测试类实现Math* FactoryCreate(){ return new(std::nothrow) MathImplementation; //注意返回的是实现类的指针}void FactoryDestroy(Math* pmath){ delete pmath;}double MathImplementation::add(double a, double b){ return a + b;}double MathImplementation::substract(double a, double b){ return a - b;}double MathImplementation::multiply(double a, double b){ return a * b;}double MathImplementation::divide(double a, double b){ return a / b;} |
---|
- 我选择了 Debug x86 平台,生成解决方案
- 在项目文件夹可以看到生成的
dll
,lib
等文件
- 至此我们完成了
dll
的创建
加载使用 DLL
- 在已经生成好
dll
、lib
、.h
后,我们就可以着手使用了 - 创建 Visual C++ 空项目,取名
dll_load
- 我们采用运用
dll
,lib
,.h
文件的方式调用 dll - 需要配置包含目录包含
dll.h
- 加入
lib
文件所在路径,作为库目录
- 添加
lib
文件作为依赖项
- 将
dll
文件拷贝到项目源文件夹用于静态加载 - 创建源文件,起名
load.cpp
- 加入调用
dll
代码,其中包含了静态、动态加载 dll 函数、类的简单示例,一些需要注意的点我写在注释里了
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 | #include<iostream>#include <Windows.h>#include <string>#include "dll.h"typedef int(*test_func1)(int);typedef int(*FUN_SUB)(int, int);typedef Math* (*PFNFACTORYCREATE)();typedef void(*PFNFACTORYDESTROY)(Math*);using std::wstring;using std::to_wstring;using namespace std;bool main(){ // 静态调用函数测试 cout << endl << "静态调用函数测试" << endl; // cout << square1(7) << endl; // 由于在 dll 创建时未声名 __declspec(dllexport),因此会报错“无法解析的外部符号” cout << square2(7) << endl; cout << square3(7) << endl; // 静态调用类测试 cout << endl << "静态调用类测试" << endl; dog dog; //实例化dog对象、赋值、并输出。 dog.getHigh(5); dog.getWide(6); cout << dog.outDate() << endl; cat cat; //实例化cat对象、赋值、并输出 cat.getHigh(16); cat.getWide(4); cout << cat.outDate() << endl; Math * sta_obj = FactoryCreate(); cout << sta_obj->add(1.0, 2.0) << endl; cout << sta_obj->substract(1.0, 2.0) << endl; cout << sta_obj->multiply(1.0, 2.0) << endl; cout << sta_obj->divide(1.0, 2.0) << endl; FactoryDestroy(sta_obj); // 动态加载DLL cout << endl << "动态加载DLL" << endl; HMODULE hModule = LoadLibrary("dll_demo.dll"); if (hModule == nullptr) { std::cout << "加载DLL失败!\n"; return 0; } // 动态加载 dll 函数 cout << endl << "动态加载 dll 函数" << endl; test_func1 dllFuntest1 = test_func1(GetProcAddress(hModule, "square1")); // 由于未进行 __declspec(dllexport) 声名,加载失败 test_func1 dllFuntest2 = test_func1(GetProcAddress(hModule, "square2")); test_func1 dllFuntest3 = test_func1(GetProcAddress(hModule, "square3")); // 由于未写入 extern "C",动态加载失败,但可以静态加载 if (dllFuntest1 == nullptr) { std::cout << "dllFuntest1 加载函数失败!\n"; } if (dllFuntest2 == nullptr) { std::cout << "dllFuntest2 加载函数失败!\n"; } if (dllFuntest3 == nullptr) { std::cout << "dllFuntest3 加载函数失败!\n"; } std::cout << dllFuntest2(7) << std::endl; // 动态加载 dll 类 cout << endl << "动态加载、使用 dll 类" << endl; PFNFACTORYCREATE FactoryCreate = PFNFACTORYCREATE(GetProcAddress(hModule, "FactoryCreate")); PFNFACTORYDESTROY FactoryDestroy = PFNFACTORYDESTROY(GetProcAddress(hModule, "FactoryDestroy")); if (FactoryCreate == nullptr) { std::cout << "加载类定义失败!\n"; return 0; } Math *pmath = FactoryCreate(); cout << pmath->add(1.0, 2.0) << endl; cout << pmath->substract(1.0, 2.0) << endl; cout << pmath->multiply(1.0, 2.0) << endl; cout << pmath->divide(1.0, 2.0) << endl; FreeLibrary(hModule); system("pause"); return 0;} |
---|
- 运行代码,看到调用
dll
得到的输出
1234567891011121314151617181920212223242526 | 静态调用函数测试4949静态调用类测试1203-120.5动态加载DLL动态加载 dll 函数dllFuntest1 加载函数失败!dllFuntest3 加载函数失败!49动态加载、使用 dll 类3-120.5请按任意键继续. . . |
---|
错误记录
1 | Error C2375 'onnx_inference::initModel': redefinition; different linkage |
---|
- 在头文件中未给类定义添加CREATEDELL_API_DU修饰时,cpp中类成员函数会出现以上错误。
参考资料
相关文章
- 探索资管领域AI:微众银行首席AI科学家杨强教授助阵易方达基金
- 百度收购YY,其实挺值得
- EasyRecovery14最新版数据恢复软件下载教程
- Photoshop2023最新版本下载安装包及更新说明介绍
- IDM脚本插件如何安装?及配置浏览器扩展教程
- Photoshop更新最新2023版本有哪些新功能?
- 福布斯中国十八年:一部顶级民企简史
- 不止千亿美金,属于百度的AI时代才刚开始
- 疫情下的春节回家路:这些感受我想跟你说
- “冲浪”工业互联网,BAT的新征程
- 自如的稳,牵动整个长租行业
- “香港科大百万奖金创业大赛”的十年深耕:让硬科技像蒲公英一样散播
- 解放千万智能家居,度家给出语音控制的最优解
- 昇腾打出“众智”牌,创新人才培养新范式
- Windows11环境编译leveldb
- 【超详细】*和&在C/C++中的常见用法(附示例讲解)
- 【一站式解惑】Linux中.a、.so和.o文件以及-I,-L,LIBRARY_PATH,LD_LIBRARY_PATH等
- [oeasy]python0045_转化为10进制数_int_integrate_integer_entire_整数
- 记好这24个ES6方法,用于解决实际开发的JS问题
- 智慧工地安全帽智能识别系统