windows动态链接机制(三)

来源:互联网 发布:kmp算法next数组例题 编辑:程序博客网 时间:2024/05/29 17:28

3. 导入库(lib)

  动态库(dll)有两种使用方法:
  (1)静态链接:库的发行方提供头文件(.h)和导入库文件(.lib)供用户使用,在链接生成用户目标模块(.dll或.exe)时,链接器根据导入库文件(.lib)的指示在目标模块中生成导入表。目标模块在装载时根据导入表的信息解析符号地址。这种在编译、链接和装载期间确定符号地址的机制称为静态链接。
  (2)动态链接:用户模块使用Dynamic-Link Library Functions(LoadLibraryEx、GetModuleHandle和GetProcAddress等,可查阅MSDN)在程序运行期间动态获取动态链接库的符号地址。这种在运行期间确定符号地址的机制称为动态链接。
  本篇主要研究导入库(lib)的原理,理解链接如何根据导入库信息将依赖符号绑定到导出符号。

3.1 导入库(lib)示例分析

3.1.1 示例1

  (1)示例代码
  (a)Math1.c

__declspec(dllexport) double __cdecl Add(double a, double b){    return a+b;}__declspec(dllexport) double __cdecl Sub(double a, double b){    return a-b;}__declspec(dllexport) double __cdecl Mul(double a, double b){    return a*b;}__declspec(dllexport) double __cdecl Div(double a, double b){    return a/b;}

  (2)编译方法

cl /c /Za /MDd Math1.clink /DLL /out:Math1.dll Math1.obj

  (3)分析
使用dumpbin查看Math1.lib的信息:

dumpbin /all Math1.lib

  由于篇幅的关系此处不贴出输出信息,用下图来总结Math1.lib的内容:
这里写图片描述
  _Add与__imp__Add是同一个符号,_Add(或__imp__Add)将被链接到Math1.dll的导出符号Add,导出符号Add的hint可能是0。
  Name type为no preffix表示依赖符号去掉前缀“”,例如依赖符号_Add去掉前缀“”就是导出符号Add。
  其他相同,此处略过。

3.1.2 示例2

  (1)示例代码
  (a)Math2.c

double Add(double a, double b){    return a+b;}double Sub(double a, double b){    return a-b;}double Mul(double a, double b){    return a*b;}double Div(double a, double b){    return a/b;}

  (b)Math2.def

LIBRARY Math2EXPORTSAdd @5Mul @6Sub @7Div @8

  (2)编译方法

cl /c /Za /MDd Math2.clink /DLL /DEF:Math2.def /out:Math2.dll Math2.obj

  (3)分析
  使用dumpbin查看Math2.lib的信息:

dumpbin /all Math2.lib

  由于篇幅的关系此处不贴出输出信息,用下图来总结Math2.lib的内容:
这里写图片描述
  _Add与__imp__Add是同一个符号,_Add(或__imp__Add)将被链接到Math2.dll中的导出序号为5的符号。
  Name type为ordinal表示依赖符号链接到指定序号的导出符号,例如依赖符号_Add链接到Math2.dll中序号为5的导出符号。
  其他相同,此处略过。

3.1.3 示例3

  (1)示例代码
  (a)Math3.c

double Add(double a, double b){    return a+b;}double Sub(double a, double b){    return a-b;}double Mul(double a, double b){    return a*b;}double Div(double a, double b){    return a/b;}

  (b)Math3.def

LIBRARY Math3EXPORTS_Add = Add_Mul = Mul_Sub = Sub_Div = Div

  (2)编译方法

cl /c /Za /MDd Math3.clink /DLL /DEF:Math3.def /out:Math3.dll Math3.obj

(3)分析
  使用dumpbin查看Math3.lib的信息:

dumpbin /all Math3.lib

  由于篇幅的关系此处不贴出输出信息,用下图来总结Math3.lib的内容:
这里写图片描述
  __Add与__imp___Add是同一个符号,  _Add(或__imp___Add)将被链接到Math3.dll中的导出符号_Add,导出符号Add的hint可能是0。
  Name type为no preffix表示依赖符号去掉前缀“”,例如依赖符号__Add去掉前缀“”就是导出符号_Add。
  其他相同,此处略过。

3.1.4 示例4

  (1)示例代码
  (a)Math4.c

__declspec(dllexport) double __stdcall Add(double a, double b){    return a+b;}__declspec(dllexport) double __stdcall Sub(double a, double b){    return a-b;}__declspec(dllexport) double __stdcall Mul(double a, double b){    return a*b;}__declspec(dllexport) double __stdcall Div(double a, double b){    return a/b;}

  (2)编译方法

cl /c /Za /MDd Math4.clink /DLL /out:Math4.dll Math4.obj

  (3)分析
  使用dumpbin查看Math4.lib的信息:

dumpbin /all Math4.lib

  由于篇幅的关系此处不贴出输出信息,用下图来总结Math4.lib的内容:
这里写图片描述
  _Add@16与__imp__Add@16是同一个符号,_Add@16(或__imp__Add@16)将被链接到Math4.dll中的导出符号_Add@16,导出符号_Add@16的hint可能是0。
  Name type为name表示依赖符号名字与导出符号名字相同,例如依赖符号_Add@16就是导出符号_Add@16。
  其他相同,此处略过。

3.1.5 示例5

  (1)示例代码
  (a)Math5.c

double __stdcall Add(double a, double b){    return a+b;}double __stdcall Sub(double a, double b){    return a-b;}double __stdcall Mul(double a, double b){    return a*b;}double __stdcall Div(double a, double b){    return a/b;}

  (b)Math5.def

LIBRARY Math5EXPORTSAddMulSubDivMyMul=Mul

  (2)编译方法

cl /c /Za /MDd Math5.clink /DLL /DEF:Math5.def /out:Math5.dll Math5.obj

  (3)分析
  使用dumpbin查看Math5.lib的信息:

dumpbin /all Math5.lib

  由于篇幅的关系此处不贴出输出信息,用下图来总结Math5.lib的内容:
这里写图片描述
  _Add@16与__imp__Add@16是同一个符号,_Add@16(或__imp__Add@16)将被链接到Math5.dll中的导出符号Add,导出符号Add的hint可能是0。
  Name type为undecorate表示依赖符号名字去掉修饰就是导出符号名字,例如依赖符号_Add@16去掉修饰就是导出符号Add。
  其他相同,此处略过。

3.2 导入函数

  通过上一节的分析可以发现,每个导出符号在导入库中都对应有2个依赖符号,其中一个依赖符号是另外一个依赖符号加上前缀“_imp”。
  通过静态链接的方法使用动态链接库(dll)时,接口的声明通常如下:
  __declspec(dllimport) double Add(double a, double b);
  如果接口声明中去掉“__declspec(dllimport)”,程序一样可以正常运行:
  double Add(double a, double b);
  那么“__declspec(dllimport)”的作用究竟是什么?下面用两个例子来分析该问题。

3.2.1 示例1

  (1)示例代码
  (a)Math.c

__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;}__declspec(dllexport) double Div(double a, double b){    return a/b;}

(b)TestMath.c

#include <stdio.h>#if 1__declspec(dllimport) double Mul(double a, double b);__declspec(dllimport) double Div(double a, double b);__declspec(dllimport) double Add(double a, double b);__declspec(dllimport) double Sub(double a, double b);#endifint main(int argc, char **argv){    double result = 0.0;    result = Add(3.0, 2.0);    result = Sub(3.0, 2.0);    result = Mul(3.0, 2.0);    result = Div(3.0, 2.0);    printf("result = %f\n", result);    return 0;}

  (2)编译方法

cl /c /Za /MDd Math.clink /DLL /out:Math.dll Math.objcl /c /Za /MDd TestMath.clink TestMath.obj Math.lib

(3)分析
  使用dumpbin工具查看TestMath.obj的依赖符号:

dumpbin /symbols TestMath.obj

这里写图片描述
  可以看到如果导入符号的接口声明使用“__declspec(dllimport)”进行修饰,则依赖符号为:“__imp__Add” 、“__imp__Div” 、“__imp__Mul”和“__imp__Sub”。
  使用dumpbin工具查看TestMath.exe的反汇编指令:

dumpbin /disasm TestMath.exe

这里写图片描述
  可以看到,对符号“__imp__Add” 、“__imp__Div” 、“__imp__Mul”和“__imp__Sub”的调用都只使用call指令进行了一次间接跳转。

3.2.2 示例2

  (1)示例代码
  (a)Math.c

__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;}__declspec(dllexport) double Div(double a, double b){    return a/b;}

  (b)TestMath.c

#include <stdio.h>#if 0__declspec(dllimport) double Mul(double a, double b);__declspec(dllimport) double Div(double a, double b);__declspec(dllimport) double Add(double a, double b);__declspec(dllimport) double Sub(double a, double b);#endifint main(int argc, char **argv){    double result = 0.0;    result = Add(3.0, 2.0);    result = Sub(3.0, 2.0);    result = Mul(3.0, 2.0);    result = Div(3.0, 2.0);    printf("result = %f\n", result);    return 0;}

  (2)编译方法

cl /c /Za /MDd Math.clink /DLL /out:Math.dll Math.objcl /c /Za /MDd TestMath.clink TestMath.obj Math.lib

  (3)分析
  使用dumpbin工具查看TestMath.obj的依赖符号:

dumpbin /symbols TestMath.obj

这里写图片描述
  可以看到如果导入符号的接口声明未使用“__declspec(dllimport)”进行修饰,则依赖符号为:“_Add” 、“_Div” 、“_Mul”和“_Sub”。
  使用dumpbin工具查看TestMath.exe的反汇编指令:

dumpbin /disasm TestMath.exe

这里写图片描述
  可以看到,对符号“_Add” 、“_Div” 、“_Mul”和“_Sub”的调用首先使用call指令跳转到桩代码处(FF 25开头的指令),然后由桩代码使用jmp指令跳转到目标地址。

3.2.3 导入函数总结

  使用下面的图例能够清楚地看到在导入函数接口声明中是否使用“__declspec(dllimport)”指示时编译器生成的依赖符号:
这里写图片描述

3.3 导入库总结

  由上面5个示例可以看出:
  (1)dll中每个导出符号在导入库都有2个对应的依赖符号(symbol),其中一个依赖符号是另一个依赖符号加上前缀“_imp”。
  (2)Name type表示依赖符号名字经过Name type指定:的方法转换后得到导出符号名字。
  总结一下导出库的作用就是:在链接时,链接器将目标文件(.obj)中的依赖符号在导入库中进行查询。如果依赖符号在导入库中有匹配,则根据Name type指定的方法将依赖符号转换为对应的导入符号,如果导出符号是按序号到处(Name type为ordianl)则链接器将导入序号写入导入表,如果不是按序号导出,链接器将导入符号名字写入导入表;如果依赖符号在导入库中没有匹配到,则链接器将会报告“符号未定义”错误。