动态链接库(dll) - 隐式加载(载入时加载)和显式加载(运行时加载)
2018-02-07 23:44
337 查看
本文章转载自C语言中文网
静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存。
如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面。这是静态链接库的一个弊端。
动态链接库有两种加载方式:隐式加载和显示加载。
隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。
usedlldemo.cpp
找到上节创建的 DllDemo 工程,将 debug 目录下的 dllDemo.lib 和 dllDemo.dll 复制到当前工程目录下。
前面已经说过:.lib 文件包含DLL导出的函数和变量的符号名,只是用来为链接程序提供必要的信息,以便在链接时找到函数或变量的入口地址;.dll 文件才包含实际的函数和数据。所以首先需要将 DllDemo.lib 引入到当前项目。
即:#pragma comment(lib,”..\Debug\DllDemo.lib”)
点击确定回到项目,编译、链接并运行,输出结果如下:
LoadLibrary() 函数的原型声明如下所示:
LoadLibrary() 函数不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如位图资源或图标资源等。
LoadLibrary() 函数有一个字符串类型(LPCTSTR)的参数,该参数指定了可执行模块的名称,既可以是一个.dll文件,也可以是一个.exe文件。
如果调用成功, LoadLibrary() 函数将返回所加载的那个模块的句柄。该函数的返回类型是HMODULE。 HMODULE类型和HINSTANCE类型可以通用。
当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,这可以通过调用 GetProcAddress() 函数来实现。
该函数用来获取DLL导出函数的 地址,其原型声明如下所示:
可以看到,GetProcAddress函数有两个参数,其含义分别如下所述:
hModule:指定动态链接库模块的句柄,即 LoadLibrary() 函数的返回值。
pProcName:字符串指针,表示DLL中函数的名字。
将原来的内容改成如下:
usedlldemo.cpp
点击确定回到项目,编译、链接并运行,输出结果如下:
通过以上的例子,我们可以看到,隐式加载和显式加载这两种加载DLL的方式各有优点,如果采用动态加载(显式加载)方式,那么可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写程序代码时就可以把链接工作做好,在程序中可以随时调用DLL导出的函数。
但是,如果程序需要访问十多个DLL,如果都采用隐式链接方式加载它们的话, 那么在该程序启动时,这些DLL都需要被加载到内存中,并映射到调用进程的地址空间, 这样将加大程序的启动时间。
而且,一般来说,在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数,其他情况下都不需要访问这些DLL中的函数。但是这时所有的DLL都已经被加载到内存中,资源浪费是比较严重的。
在这种情况下,就可以采用显式加载的方式访问DLL,在需要时才加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。
有一点需要说明的是,实际上, 采用隐式链接方式访问DLL时,在程序启动时也是通过调用LoadLibrary() 函数加载该进程需要的动态链接库的。
静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存。
如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面。这是静态链接库的一个弊端。
动态链接库有两种加载方式:隐式加载和显示加载。
隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。
隐式加载
首先创建一个工程,命名为 UseDllDemo,添加源文件 usedlldemo.cpp,内容如下:usedlldemo.cpp
#pragma comment(lib,"..\\Debug\\DllDemo.lib") #include <iostream> using std::cout; using std::cin; using std::endl; extern "C" __declspec(dllimport) int add(int a, int b); extern "C" __declspec(dllimport) int sub(int a, int b); int main() { cout << endl << add(1, 2) << endl; cout << sub(5, 1); cin.get(); return 0; }
找到上节创建的 DllDemo 工程,将 debug 目录下的 dllDemo.lib 和 dllDemo.dll 复制到当前工程目录下。
前面已经说过:.lib 文件包含DLL导出的函数和变量的符号名,只是用来为链接程序提供必要的信息,以便在链接时找到函数或变量的入口地址;.dll 文件才包含实际的函数和数据。所以首先需要将 DllDemo.lib 引入到当前项目。
即:#pragma comment(lib,”..\Debug\DllDemo.lib”)
点击确定回到项目,编译、链接并运行,输出结果如下:
显式加载
显式加载动态链接库时,需要用到 LoadLibrary() 函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。LoadLibrary() 函数的原型声明如下所示:
HMODULE LoadLibrary(LPCTSTR 1pFileName);
LoadLibrary() 函数不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如位图资源或图标资源等。
LoadLibrary() 函数有一个字符串类型(LPCTSTR)的参数,该参数指定了可执行模块的名称,既可以是一个.dll文件,也可以是一个.exe文件。
如果调用成功, LoadLibrary() 函数将返回所加载的那个模块的句柄。该函数的返回类型是HMODULE。 HMODULE类型和HINSTANCE类型可以通用。
当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,这可以通过调用 GetProcAddress() 函数来实现。
该函数用来获取DLL导出函数的 地址,其原型声明如下所示:
FARPROC GetProcAddress(HMODULE hModule, LPCSTR 1pProcName);
可以看到,GetProcAddress函数有两个参数,其含义分别如下所述:
hModule:指定动态链接库模块的句柄,即 LoadLibrary() 函数的返回值。
pProcName:字符串指针,表示DLL中函数的名字。
将原来的内容改成如下:
usedlldemo.cpp
#include<stdio.h> #include<stdlib.h> #include<windows.h> // 必须包含 windows.h typedef int(*FUNADDR)(int ,int); // 指向函数的指针 int main() { int a = 10, b = 5; HINSTANCE dllDemo = LoadLibrary(L"..\\Debug\\DllDemo.dll"); FUNADDR add, sub; if (dllDemo) { add = (FUNADDR)GetProcAddress(dllDemo, "add"); sub = (FUNADDR)GetProcAddress(dllDemo, "sub"); } else { printf("Fail to load DLL!\n"); system("pause"); exit(1); } printf("\na+b=%d\n", add(a, b)); printf("a-b=%d\n", sub(a, b)); system("pause"); return 0; }
点击确定回到项目,编译、链接并运行,输出结果如下:
通过以上的例子,我们可以看到,隐式加载和显式加载这两种加载DLL的方式各有优点,如果采用动态加载(显式加载)方式,那么可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写程序代码时就可以把链接工作做好,在程序中可以随时调用DLL导出的函数。
但是,如果程序需要访问十多个DLL,如果都采用隐式链接方式加载它们的话, 那么在该程序启动时,这些DLL都需要被加载到内存中,并映射到调用进程的地址空间, 这样将加大程序的启动时间。
而且,一般来说,在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数,其他情况下都不需要访问这些DLL中的函数。但是这时所有的DLL都已经被加载到内存中,资源浪费是比较严重的。
在这种情况下,就可以采用显式加载的方式访问DLL,在需要时才加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。
有一点需要说明的是,实际上, 采用隐式链接方式访问DLL时,在程序启动时也是通过调用LoadLibrary() 函数加载该进程需要的动态链接库的。
相关文章推荐
- 动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)
- 动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)
- 动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)
- 动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)
- 动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)
- dll的加载方式主要分为两大类,显式和隐式链接
- DLL显式加载与隐式加载
- 动态链接库的生成(dll)和 动态链接库隐式and显式调用
- LoadResourceDll类,载入资源中的动态链接库(dll)文件,把DLL打包到EXE文件里面
- 程序运行报异常: KERNELBASE.dll模块加载异常
- linux下动态链接库(.so)的显式调用和隐式调用
- DLL动态库的创建,隐式加载和显式加载
- Dll的显式和隐式调用
- 通过HookNtCreateSection 动态监控驱动sys、动态链接库dll、可执行文件exe加载
- 运行PHP程序时报php_exif.dll未加载
- C++ 多个类的DLL封装以及隐式链接和显式链接
- C#工程添加了DLL编译运行时却提示”无法加载DLL“的解决方案
- linux下动态链接库(.so)的显式调用和隐式调用
- VC++动态链接库的显示加载和隐式加载
- Java加载dll动态链接库,并打包执行为exe文件