动态链接库的使用:加载和链接

Use of dynamic link libraries: loading and linking

在部分 SDK 的对接中,有些平台除了 DLL 外并没有提供导入库来供我们使用,那就只能使用代码中加载 DLL 的办法来调用 DLL 内的函数,本文来记录一下两种用法,再分析一下优劣。

我们可以先生成一个测试的 DLL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// max_dll.h
#ifndef MAX_DLL_H_
#define MAX_DLL_H_

extern "C" int max(int,int);
typedef int (*dll_max)(int,int);

#endif

// max_dll.cpp
#include "max_dll.h"

extern "C" int max(int lhs, int rhs)
{
return lhs>rhs?lhs:rhs;
}

之所以使用 extern "C" 的原因是在于 C++ 的 name manglingimplementation-define 的,简单来说 C++ 的 ABI 不稳定,使用 C 的链接可以避免不同实现 name mangling 规则不一导致符号不一致的问题。
生成 DLL (更具体的可以在我之前的文章 ——C/C++ 编译和链接模型分析中查看:

1
2
3
$ g++ max_dll.cpp -shared -o max_dll.dll
$ ls
max_dll.cpp max_dll.dll* max_dll.h

运行时加载 DLL

然后我们考虑在外面的代码中怎么才能调用这个 DLL 中的 max 函数呢?
因为编译和链接的缘故,因为该 DLL 没有导入库,所以不能在连接时指定导入库从而直接使用 DLL 中的函数,但是我们可以动态地加载 DLL 文件,在 Windows 上可以使用 LoadLibraryGetProcAddress 组合实现。
其中 LoadLibrary 的作用是加载 DLL 文件,GetProcAddress 的作用是从 DLL 内获取符号的函数指针,FreeLibrary 则是释放 DLL 文件的句柄 (handler):
其原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <Windows.h>
// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
// Return Value
// If the function succeeds, the return value is a handle to the module.
// If the function fails, the return value is NULL. To get extended error information, call GetLastError.
HMODULE LoadLibraryA(
LPCSTR lpLibFileName
);
// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
// Return Value
// If the function succeeds, the return value is the address of the exported function or variable.
// If the function fails, the return value is NULL. To get extended error information, call GetLastError.
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer valid.
BOOL FreeLibrary(
HMODULE hLibModule
);

加载 DLL 实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "max_dll.h"
#include <cstdio>
#include <tchar.h>
#include <Windows.h>

#define DLL_FILE _T("max_dll.dll")

int main()
{
HINSTANCE hdll = LoadLibrary(DLL_FILE);
if (hdll!=NULL)
{
dll_max max_func=(dll_max)GetProcAddress(hdll, "max");
if(max_func != NULL)
{
std::printf("%d\n",max_func(123,456));
FreeLibrary(hdll);
}else
{
std::printf("From DLL Get function Address is falid.\n");
}
}else
{
std::printf("LoadLibrary is faild.\n");
}
return 0;
}

注意:如果上面的 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
2
3
# 非extern "C"版本
$ nm max_dll.dll
00000000660814b0 T _Z3maxii

把上面出错的代码改为下面,则可以获取函数指针成功 (但是这样做也太麻烦了):

1
dll_max max_func=(dll_max)GetProcAddress(hdll, "_Z3maxii");

而使用 extern "C" 的 dll 版本内的符号信息为 (因为使用 C 的链接方式则没有任何符号改编):

1
2
3
# extern "C"版本
$ nm max_dll.dll
00000000660814b0 T max

注:Linux 上与 LoadLibrary/GetProcAddress/FreeLibrary 分别对应 (Linux 动态链接库为.so) 的是 dlopen/dlsym/dlclose,其原型分别如下:

1
2
3
4
5
6
7
8
#include <dlfcn.h>

// dlopen,returns library handle on success, or NULL on error
void *dlopen(const char *libfilename.,int flags);
// dlsym,returns Address of symbol,or NULL if symbol is not found.
void *dlsym(void *handler,char *symbol);
// dlclose, returns 0 on success,or -1 on error
int dlclose(void *handler);

使用 DLL 的导入库

而如果具有导入库,我们就可以用类似于静态链接的办法,而不用在运行时加载 DLL 文件再获取函数指针:

1
2
# 创建DLL并创建一个导入库lib
g++ max_dll.cpp -shared -o max_dll.dll -Wl,--out-implib,imp_max_dll.lib

-Wl 为传递给链接器的参数,--out-implib 为生成导入库。
然后我们就可以像使用静态链接一样来链接 DLL 中的符号:

1
2
3
4
5
6
7
8
9
// call_max.cpp
#include <cstdio>
#include "max_dll.h"

int main(void)
{
std::printf("%d\n",max(123, 456));
return 0;
}

然后执行编译,但不链接 -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
2
$ ./call_max.exe
456

我们查看一下 call_max.exe 依赖的动态链接库,使用 ldd 命令:

1
2
3
4
5
6
$ ldd call_max.exe
ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffa14b80000)
KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffa12b00000)
KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffa11d90000)
msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ffa128a0000)
max_dll.dll => /c/Users/visionsmile/Desktop/code/max_dll.dll (0x66080000)

可以看到,这里 call_max.exe 依赖的是当前目录下的 max_dll.dll
如果把 max_dll.dll 删除呢?我们删除之后再试一下:

1
2
$ ./call_max.exe
call_max.exe: error while loading shared libraries: max_dll.dll: cannot open shared object file: No such file or directory

会提示找不到文件的错误。

运行时动态加载 DLL 和使用 DLL 的导入库各有优缺点,未完待续,有时间再继续分析。

扩展阅读

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题: 动态链接库的使用:加载和链接
文章作者: 查利鹏
发布时间:2018/10/15 20:50
本文字数:1.3k 字
原始链接:https://imzlp.com/posts/18949/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!
Powered By Valine
v1.4.14