程序员的自我修养——第八、九章——windows下的动态链接

第八章不是很感兴趣,直接跳到第九章。

 

  DLL即动态链接库(Dynamic Link Library)的缩写.

  一个DLL中有两个数据段,一个进程间共享,另一个私有.

 

      当我们使用“__declspec(dllexport)”时表示该符号是从本DLL导出的符号。“__declspec(dllimport)”表示该符号是从别的DLL导入的符号。

 

创建DLL:

/*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;

}

使用cl编译器进行编译:

cl   /LDd  Math.c

 

参数/LDd表示产生Debug版的DLL,不加任何参数则表示产生EXE可执行文件,可以使用/LD来编译生成Release版的DLL。

通过dumpbin 查看DLL的导出符号:

D:\Program Files\home\*>dumpbin /EXPORTS Math.dll

Microsoft (R) COFF/PE Dumper Version 11.00.50214.1

Copyright (C) Microsoft Corporation.  All rights reserved.

 

 

Dump of file Math.dll

 

File Type: DLL

 

  Section contains the following exports for math.dll

 

    00000000 characteristics

    4F878A19 time date stamp Fri Apr 13 10:06:17 2012

        0.00 version

           1 ordinal base

           3 number of functions

           3 number of names

 

    ordinal hint RVA      name  //三个导出函数即相对地址

 

          1    0 00001000 Add

          2    1 00001040 Mul

          3    2 00001020 Sub

 

  Summary

 

        3000 .data

        9000 .rdata

        3000 .reloc

       1E000 .text

 

使用DLL:

/*test_math.c*/

#include <stdio.h>

 

__declspec(dllexport) 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;

}

 

编译:

D:\Program Files\home\*>cl    /c test_math.c

D:\Program Files\home\*> link  test_math.obj  math.lib

 

声明DLL中的某个函数为导出函数的办法有两种,使用“__declspec(dllexport)”扩展;另一种就是采用模块定义(.def)文件声明。

创建math.def文件:

/*math.def*/

LIBRARY Math

EXPORTS

Add

Sub

Mul

Div

 

编译:

D:\Program Files\home\*>cl  math.c /LD  /DEF  math.def

 

最终输出跟第一种方法相同的结果。

 

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

·LoadLibrary,这个函数用来装载一个DLL到进程的地址空间,它的功能跟dlopen类似。

·GetProcAddress, 用来查找某个符号的地址,与dlsym类似

·FreeLibrary, 用来卸载某个已加载的模块,与dlclose类似

 

/*RunDllSimple.c*/

#include <windows.h>

#include <stdio.h>

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

int main(int argc, char *argv[])

{

  Func function;

  double result;

 

  HINSTANCE hinstLib=LoadLibrary("math.dll");

  if (hinstLib==NULL)

  {

    printf("ERROR:unable to load DLL.\n");

    return 1;

  }

 

  function=(Func)GetProcAddress(hinstLib, "Add");

  if (function==NULL)

  {

    printf("ERROR:unable to find DLL function.\n");

    FreeLibrary(hinstLib);

    return 1;

  }

 

  result=function(1.0, 2.0);

  FreeLibrary(hinstLib);

  printf("result=%f\n", result);

 

  return 0;

}

D:\Program Files\home\*>cl RunDllSimple.c

D:\Program Files\home\*>RunDllSimple.exe

result = 3.000000

 

符号导出导入表:

      当一个PE需要将一些函数或变量提供给其他PE文件使用时,我们把这种行为叫做符号导出。导出表提供了一个符号名与符号地址的映射关系。

      在ELF中,“.rel.dyn”和“.rel.plt”两个段中分别保存了该模块所需要导入的变量和函数的符号以及所在的模块等信息。而“.got”和“.got.plt”则保存着这些变量和函数的真正地址。

 

查看依赖了哪些DLL:

D:\Program Files\home\*>dumpbin    /IMPORTS   math.dll

 

我们的math.dll导入了KERNEL32.dll

 

在PE文件中,导入表是一个IMAGE_IMPORT_DESCRIPTOR的结构数组,每个IMAGE_IMPORT_DESCRIPTOR结构对应一个被导入的DLL。

 

EXE文件的基地址默认为0x00400000,而DLL文件基地址默认为0x10000000

 

DLL绑定:把导出函数的地址保存到模块的导入表中,可以省去每次启动时符号解析的过程。

DLL绑定的方法:

D:\Program Files\home\*>editbin   /BIND   test_math.exe

Microsoft (R) COFF/PE Editor Version 11.00.50214.1

Copyright (C) Microsoft Corporation.  All rights reserved.

D:\Program Files\home\*>dumpbin  /IMPORTS   test_math.exe

Microsoft (R) COFF/PE Dumper Version 11.00.50214.1

Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file test_math.exe

 

File Type: EXECUTABLE IMAGE

 

  Section contains the following imports:

 

    math.dll

                40C100 Import Address Table

                40DF30 Import Name Table

              FFFFFFFF time date stamp

              FFFFFFFF Index of first forwarder reference

 

      10001020      2 Sub

 

    KERNEL32.dll

                40C000 Import Address Table

                40DE30 Import Name Table

              FFFFFFFF time date stamp

              FFFFFFFF Index of first forwarder reference

 

      77E330E2    540 SetUnhandledExceptionFilter

      77E30AFD     D0 CreateFileW

      77E3296C    1DA GetCommandLineA

….

….

DLL绑定实现,editbin对被绑定的程序的导入符号进行遍历查找,找到以后就把符号的运行时的目标地址写入到被绑定程序的导入表内。

 

c++与动态链接

 

指导意见:

(1)所有接口函数都应该是抽象的。所有的方法都应该是纯虚的。(或者inline的方法也可以)

(2)所有的全局函数都应该使用extern “C”来防止名字修饰的不兼容。并且导出函数的都应该是__stdcall调用规范(COM的DLL都使用这样的规范)。这样即使用户本身的程序默认以__cdecl方式编译的,对于DLL的调用也能正确。

(3)不要使用C++标准库STL

(4)不要使用异常

(5)不要使用虚析构函数。可以创建一个destroy()方法并且重载delete操作符并且调用destroy()

(6)不要在DLL里面申请内存。而且在DLL外释放(或者相反)。不同的DLL和可执行文件可能使用不同的堆,在一个堆里面申请内存而在另外一个堆里面释放会导致错误。比如,对于内存分配相关的函数不应该是inline的,以防止它在编译时被展开到不同的DLL和可执行文件

(7)不要在接口中使用重载方法。因为不同的编译器对于vtable的安排可能不同。

 

3. DLL HELL(dll噩梦)

 

预防方法:

静态链接

防止DLL覆盖(windows文件保护实现)

避免DLL冲突(让每个应用程序拥有自己依赖的DLL)

.Net下DLL Hell的解决方案(Manifest,XML描述文件,(side-by-side manager)SxS Manager实现对相应版本的DLL的加载)

 

posted @ 2012-05-11 20:38  KingsLanding  阅读(530)  评论(0编辑  收藏  举报