创建DLL动态库
1、新建Dll项目
文件->新建->项目->Visual C++ win32控制台程序(填写项目名MyDll)->下一步->(应用程序类型)勾选:DLL,(附加选项)勾选:空项目->完成
2、添加新建项MyDll.cpp
因显示加载dll时仅需要用到项目生成的dll文件,所以这里可以不使用.h文件。
#include <iostream>
int __stdcall Add(int a, int b)
{
return a + b;
}
void __stdcall myPrint(const char* s)
{
std::cout << s << std::endl;
}
__stdcall是Microsoft对编译器的一个扩展,是一种用于调用Win32 API函数的调用约定。使用__stdcall约定时函数栈的清理由被调用方执行,因此可以解决不同的编译器生成函数栈方式不同的问题,也就是使用该约定生成的dll可以跨编译器使用。另外如果导出函数需要给win32汇编使用或者作为系统回调函数使用,使用__stdcall声明方式是非常重要的。
3、添加.def文件(模块定义文件)
在新添加的.def文件中写入以下内容:
LIBRARY MyDll
EXPORTS
Add
myPrint
通常情况下这里只需要用到两种模块定义语句,LIBRARY和EXPORTS。这两条语句的使用规则如下:
LIBRARY [library][BASE=address]
library 参数指定 DLL 的名称。 也可以使用 /OUT 链接器选项指定 DLL 输出名。BASE=address 参数设置操作系统用来加载 DLL 的基址。 该参数可重写默认的 DLL 位置。
EXPORTS
definition
第一个 definition 可以和 EXPORTS 关键字在同一行或在下一行上。 .DEF 文件可以包含一个或多个 EXPORTS 语句。
导出 definition 的语法为:
entryname[=internalname] [@ordinal [NONAME]] [[PRIVATE] | [DATA]]
- entryname 是要导出的函数名或变量名。 这是必选项。 如果导出的名称与 DLL 中的名称不同,则使用 internalname 指定 DLL 中导出的名称。
- 可选@ordinal 用于指定序号。除非 DLL 的客户端需要按序号导出函数以支持旧版代码,否则不建议使用该功能。
- 可选 NONAME 关键字,用于指定只按序号导出,从而减小生成的 DLL 中导出表的大小。
- 可选 PRIVATE 关键字用于禁止将 entryname 包含在由 LINK 生成的导入库中。
- 可选 DATA 关键字指定导出的是数据,而不是代码。
4、编译生成dll文件
生成结果如下所示
使用Dependency Walker工具可以打开dll文件查看导出的函数格式
显示加载方式使用DLL动态库
通过显式加载的方式访问DLL,可以在需要时加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。而在不需要时将其释放掉,从而可以减少资源占用。相比于使用隐式加载的程序启动时加载所有动态库,使用显示加载的程序启动更快。
显示加载方式的局限是只能导出全局函数,不能导出类成员函数。但用于抽象接口类时,这条缺点不会有影响,具体用法将在下一篇中讲解。
1、新建测试项目
文件->新建->项目->Visual C++ win32控制台程序(填写项目名testDll)->下一步-> (附加选项)勾选:空项目->完成
2、添加新建项main.cpp文件
#include <iostream>
#include <Windows.h>
using namespace std;
int main(void)
{
HMODULE hMod = LoadLibrary("MyDll.dll");
if (hMod == nullptr) {
cout << "load dll error!" << endl;
return -1;
}
typedef int (__stdcall* TypeFunAdd)(int, int);
typedef int(__stdcall* TypeFunMyPrint)(const char*);
TypeFunAdd AddT = (TypeFunAdd)GetProcAddress(hMod, "Add");
if (AddT == nullptr) {
cout << "load func error!" << endl;
return -1;
}
cout << "Test Add Func: 2 + 5 = " << AddT(2, 5) << endl;
TypeFunMyPrint myPrintT = (TypeFunMyPrint)GetProcAddress(hMod, "myPrint");
if (myPrintT == nullptr) {
cout << "load func error!" << endl;
return -1;
}
myPrintT("Test myPrint Func.");
FreeLibrary(hMod);
return 0;
}
相比于隐式加载,这里不需要加入相关的lib依赖项。且隐式加载时,dll文件需放置在可执行文件的同一目录下,此处显示加载时dll文件应放置在项目根目录下。
上面的 LoadLibrary() 函数的作用是将指定的dll模块映射到调用进程的地址空间。GetProcAddress()函数的作用是获取dll中导出函数的地址,需要使用导出的函数名作参数。同时使用对应的函数指针变量接收返回的函数地址,另外此处函数指针的声明方式应该与dll中函数的声明方式一致。
3、编译运行
注:
编译时若出现错误,无法将参数 1 从“const char [10]”转换为“LPCWSTR”。只需要设置属性 使用多字节字符集 即可。