在部分 SDK 的对接中,有些平台除了 DLL 外并没有提供导入库来供我们使用,那就只能使用代码中加载 DLL 的办法来调用 DLL 内的函数,本文来记录一下两种用法,再分析一下优劣。
我们可以先生成一个测试的 DLL:
1 | // max_dll.h |
之所以使用 extern "C"
的原因是在于 C++ 的 name mangling
是 implementation-define
的,简单来说 C++ 的 ABI 不稳定,使用 C 的链接可以避免不同实现 name mangling
规则不一导致符号不一致的问题。
生成 DLL (更具体的可以在我之前的文章 ——C/C++ 编译和链接模型分析中查看:
1 | $ g++ max_dll.cpp -shared -o max_dll.dll |
运行时加载 DLL
然后我们考虑在外面的代码中怎么才能调用这个 DLL 中的 max
函数呢?
因为编译和链接的缘故,因为该 DLL 没有导入库,所以不能在连接时指定导入库从而直接使用 DLL 中的函数,但是我们可以动态地加载 DLL 文件,在 Windows 上可以使用 LoadLibrary 和 GetProcAddress 组合实现。
其中 LoadLibrary
的作用是加载 DLL 文件,GetProcAddress
的作用是从 DLL 内获取符号的函数指针,FreeLibrary
则是释放 DLL 文件的句柄 (handler):
其原型如下:
1 |
|
加载 DLL 实例如下:
1 |
|
注意:如果上面的 max_dll.cpp
不是使用 extern "C"
来链接的,那么上面的代码在这一行:
1 | dll_max max_func=(dll_max)GetProcAddress(hdll, "max"); |
会获取函数指针失败,因为按照 C++ 的 name mangling
规则,函数 max
在 dll 内的符号名字不是 max
而是类似下面这样的 (之所以说” 类似” 是因为没有标准规定,依赖实现):
我们可以通过 nm
来查看目标文件中的符号信息(不使用 extern "C"
的 dll 版本),已隐藏其他的符号:
1 | # 非extern "C"版本 |
把上面出错的代码改为下面,则可以获取函数指针成功 (但是这样做也太麻烦了):
1 | dll_max max_func=(dll_max)GetProcAddress(hdll, "_Z3maxii"); |
而使用 extern "C"
的 dll 版本内的符号信息为 (因为使用 C 的链接方式则没有任何符号改编):
1 | # extern "C"版本 |
注:Linux 上与
LoadLibrary
/GetProcAddress
/FreeLibrary
分别对应 (Linux 动态链接库为.so
) 的是dlopen
/dlsym
/dlclose
,其原型分别如下:
1 |
|
使用 DLL 的导入库
而如果具有导入库,我们就可以用类似于静态链接的办法,而不用在运行时加载 DLL 文件再获取函数指针:
1 | # 创建DLL并创建一个导入库lib |
-Wl
为传递给链接器的参数,--out-implib
为生成导入库。
然后我们就可以像使用静态链接一样来链接 DLL 中的符号:
1 | // call_max.cpp |
然后执行编译,但不链接 -c
,只生成目标文件 call_max.o
:
1 | $ g++ -c call_max.cpp -o call_max.o |
然后把 imp_max_dll.lib
链接进来:
1 | $ g++ call_max.o imp_max_dll.lib -o call_max.exe |
没有符号未定义错误。
然后运行:
1 | $ ./call_max.exe |
我们查看一下 call_max.exe 依赖的动态链接库,使用 ldd
命令:
1 | $ ldd call_max.exe |
可以看到,这里 call_max.exe 依赖的是当前目录下的 max_dll.dll
。
如果把 max_dll.dll
删除呢?我们删除之后再试一下:
1 | $ ./call_max.exe |
会提示找不到文件的错误。
运行时动态加载 DLL 和使用 DLL 的导入库各有优缺点,未完待续,有时间再继续分析。
v1.4.14