在部分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的导入库各有优缺点,未完待续,有时间再继续分析。