程序员的自我修养之Windows下的动态链接

9.1 DLL简介

DLL即动态链接库的缩写,它相对于Linux下的共享对象。
Windows下的DLL文件和EXE文件实际上是一个概念,它们都是有PE格式的二进制文件。
微软希望通过DLL机制加强软件的模块化设计,使得各种模块之间能够松散地组合、重用和升级。

9.1.1 进程地址空间和内存管理

一个DLL在不同进程中拥有不同的使用数据副本。在ELF中,由于代码段是地址无关的,所以它可以实现多个进程之间共享一份代码,但是DLL的代码却并不是地址无关的,所以它只是在某些情况下可以被多个进程间共享。

9.1.2 基地址和RVA

PE里面有两个概念就是基地址相对地址。当一个PE文件被加载时,其进程空间中起始地址就是基地址。对于任何一个PE文件来说,它都有一个优先装载的基地址,这个值就是PE文件头中的Image Base。
对于一个可执行EXE文件来说,Image Base一般值是0x400000,对于DLL文件来说,这个值一般是0x10000000。

9.1.3 DLL共享数据段

正常情况下,每个DLL的数据段在各个进程中都是独立的,每个进程都拥有自己的副本。但是Windows允许将DLL的数据段设置成共享的,即任何进程都可以共享该DLL的同一份数据段。比较常见的做法是将一些需要共享的变量分离出来,放到另外一个数据段中,然后设置成进程之间共享的。也就是说,一个DLL有两个数据段,一个是进程间共享,一个是私有的。

9.1.4 DLL的简单例子

导出概念:在ELF中,共享库所有的全局函数和变量在默认情况下都可以被其他模块使用,也就是说ELF默认导出所有的全局符号。但是在DLL中情况是,我们需要显式的告诉编译器我们需要导出的某个符号,否则编译器默认所有符号都不导出。当我们在程序中使用DLL导出符号时,这个过程被称为导入
在C/C++中,可以使用”_declspec”属性关键字来修饰某个函数或者变量。当使用_declspec(dllexport)时,表示该符号是从本DLL导出符号,_declspec(dllimport)是表示该符号是从别的DLL导入符号。

9.1.5 创建DLL

复制代码
__declspec(dllexport) double Add(double a, double b) {
return a + b;
}

__declspec(dllexport) double Sub(double a, double b) {
return a - b;
}

__declspec(dllexport) double Mul(double a, double b) {
return a * b;
}
复制代码

执行:

cl /LDd Math.c

9.1.6 使用DLL

程序使用DLL的过程其实是引用DLL中的导出函数和符号过程,即导入过程。

复制代码
#include<stdio.h>
__declspec(dllimport) double Sub(double a,double b);

int main(int argc,char **argv)
{
double result=Sub(3.0,2.0);
printf("Result = %f/n",result);
return 0;
}
复制代码

使用下面命令将TestMath.c编译成TestMath.obj。

cl /c TestMath.c

使用链接器将TestMath.obj和Math.lib链接起来产生一个可执行文件TestMath.exe。

link TestMath.obj Math.lib

这个过程如下图:

Math.lib中并不包含正在Math.c的代码和数据,它描述Math.dll的导出符号,它包含了TestMath.o链接到Math.dll导入符号以及一部分桩代码,又称作”胶水”代码。Math.lib文件被称为导入库

9.1.7 使用模块定义文件

声明DLL中某个函数为导出函数的办法有两种:

  • 一种就是前面使用的”__declspec(dllexport)”
  • 另外一种就是采用模块定义(.def)文件声明。

.def文件用于控制链接过程,为链接器提供有关链接程序的导出符号、属性、以及其他信息。
使用”_stdcall”调用规范的函数Add就会被修饰成”_Add@16”,前面以开头,后面以@n结尾,n表示函数调用时参数所占堆栈空间大小。使用.def文件可以将导出函数重新命名。
微软以DLL的形式提供Windows的API,而每个DLL中的导出函数又以这种”__stdcall”的方式声明。但也采用了导出函数重命名的方法。

9.1.8 DLL显示运行时链接

DLL也支持运行时链接,即运行时加载,Windows提供了3个API为:

  • LoadLibrary,这个函数用来装载一个DLL到进程空间,它的功能和dlopen类似
  • GetProcAddress,用来查找某个符号地址,与dlsym类似
  • FreeLibrary,用来卸载某个已加载的模块,与dlclose类似
复制代码
#include<Windows.h>
#include<stdio.h>

typedef double(*Func)(double, double);

int main(int argc, char **argv) {
Func function;
double result;
float i=1;
HINSTANCE hinstLib = LoadLibrary("Math.dll");
if (hinstLib == NULL) {
printf("ERROR");
}
function =(Func)GetProcAddress(hinstLib, "Add");
if (function==NULL)
{
printf("ERROR");
}
result = function(1.0, 6.0);
FreeLibrary(hinstLib);
printf("Result=%f\n", result);
scanf_s("%f", i);
return 0;

}

【本文由“孤单程序员”发布,2017年10月12日】

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值