Windows Dll动态库隐式链接

本文详细介绍DLL动态库的构建过程,包括创建头文件、源文件及生成DLL映像文件等步骤。同时,阐述如何构建可执行模块以及实现隐式链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Dll动态库隐式链接

本文主要介绍以下几个方面:

  • 构建DLL的步骤
  • 构建可执行的模块的步骤
  • 隐式链接步骤
  • 让程序运行起来

构建DLL的步骤

1)我们必须创建一个头文件,在其中包含我们想要的DLL中导出的函数原型、结构以及符号。为了构建该DLL,DLL的所有源文件需要包含这个头文件。

2)创建C/C++源文件来实现想要在DLL模块中导出的函数和变量。

3)在构建DLL模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。

4)当所有.obj模块都创建完毕之后,连接器会将所有.obj模块的内容合并起来,产生一个单独的DLL映像文件,这个映像文件包含DLL中所有二进制代码以及全局/静态变量。

5)如果链接器检测到DLL的源文件输出了至少一个函数或变量,那么链接器还会生成一个.lib文件。这个lib文件非常小,这是因为它不包含任何函数或者变量。它只是列出了所有被导出的函数和变量的符号名。

构建可执行的模块的步骤

1)在所有引用了导出的函数、变量、数据结构或符号的源文件中,必须包含由DLL的开发人员所创建的头文件。

2)创建C/C++源文件来实现想要包含在可执行模块中的函数和变量。
当然,代码可以引用在DLL的头文件中定义的函数和变量。

3)在构建可执行模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。

4)当所有.obj模块都创建完毕之后,连接器会将所有.obj模块的内容合并起来,产生一个单独的可执行映像文件。这个映像文件包含可执行文件中所有二进制代码以及全局/静态变量。该可执行模块还包含一个导入段(import section),其中列出了所有它需要的DLL模块的名称。此外,对列出的每个DLL,该段还记录了可执行文件的二进制代码从中引用的函数和变量的符号名。

5)加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行模块的导入段。对导入段中列出的每个DLL,加载程序会在用户的系统中对该DLL模块进行定位,并将该DLL映射到进程的地址空间。注意,由于DLL模块可以从其他DLL模块中导入函数和变量,因此DLL模块可能有自己的导入段并需要将它所需的DLL模块映射到进程的地址空间中。

隐式链接步骤

创建DLL头文件,在源文件中对头文件中声明的函数或者变量进行定义。
userdefine.h头文件如下

#ifdef USERDEFINE_EXPORTS
#define USERDEFINE_API extern "C" __declspec(dllexport)
#else
#define USERDEFINE_API extern "C" __declspec(dllimport)
#endif


USERDEFINE_API int fnuserdefine(void);

USERDEFINE_API int g_nResult;//尽量避免导出变量

userdefine.cpp文件如下:

#define USERDEFINE_API extern "C" __declspec(dllexport)

#include "userdefine.h"
#include <tchar.h>

int g_nResult;

int fnuserdefine(void)
{
    g_nResult = 256;
    _tprintf(_T("DLL test example\n"));
    return g_nResult;
}

在编译前面的DLL源文件的时候,USERDEFINE_API在包含userdefine.h头文件之前定义为__declspec(dllexport)。如果编译器看到一个变量、函数或C++类是用__declspec(dllexport)修饰的,那么它就知道应该在生成的DLL模块中导出该变量、函数或者C++类。注意,对那些要被导出的函数和变量,我们必须在头文件中的变量和函数定义的前面加上USERDEFINE_API的定义。另外注意的是,在源文件userdefine.cpp文件中不必在要被导出的变量和函数前面加USERDEFINE_API标识符。因为编译器在解析头文件的时候会记住应该导出哪些变量或函数。
USERDEFINE_API符号包含extern “C”修饰符。只有在编写C++代码的时候,才应该使用这个修饰符,在编写C代码的时候不应该使用该修饰符。C++编译器通常会对函数名和变量名进行改编(name-mangled),这在链接的时候会导致严重的问题。
可执行文件不应该在包含这个头文件之前定义USERDEFINE_API。由于USERDEFINE_API未定义,因此头文件会将USERDEFINE_API定义为__declspec(dllimport),这样编译器就知道该可执行文件的源文件要从DLL模块中导入一些变量和函数。

让程序运行起来

可执行文件的源代码如下:

#include "userdefine.h"
#pragma comment(lib,"userdefine.lib")
int _tmain( int argc, TCHAR* argv[] )
{
    int nRet = fnuserdefine();
    _tprintf(_T("nRet=%d\n"), nRet);
}

编译源文件的时候,需要把DLL的头文件和lib文件放在该工程目录下。程序运行的时候需要向程序告知DLL的路径。
启动一个可执行模块的时候,操作系统会加载程序会先为进程创建虚拟地址空间,接着把可执行模块映射到进程的地址空间。之后加载程序会检查可执行模块的导入段,试图对所需的DLL进行定位并将它们映射到进程的地址空间中。
由于导入段只包含DLL的名称,不包含DLL的路径,因此加载程序必须在用户的磁盘上搜索DLL。下面是加载程序的搜索顺序。
1)包含可执行文件的目录;
2)Windows的系统目录;
3 )16位的系统目录,即Windows目录中System的子目录;
4)Windows目录;
5)进程的当前目录;
6)PATH环境变量中所列出的目录;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值