动态链接库(二)

DLL高级技术

    1.1 显示载入DLL模块:

LoadLibrary,LoadLibraryEx.两个函数的返回值HMODULE表示文件映像被映射到的虚拟内存地址,等价于HINSTANCE(DllMain入口点函数所接收的参数),LoadLibraryEx函数有两个额外的参数:hFile(保留应为NULL),dwFlags:

    DONT_RESOLVE_DLL_REFERENCES 只映射文件映像,不调用DllMain,同时不会自动载入这个Dll中用到的其它DLL(使用这个标志调用导出函数时将面临很大的风险)。更多细节参见:LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed:地址:http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx

    LOAD_LIBRARY_AS_DATAFILE 可以将DLL或EXE以资源的形式映射到进程地址空间。

    LOAD_WITH_ALTERED_SEARCH_PATH 改变DLL加载时的搜索算法(GetDllDirectory,SetDllDirectory).

其它参见MSDN:http://msdn.microsoft.com/en-us/library/ms684179(VS.85).aspx

 

   1.2 显示地卸载DLL模块:

    BOOL FreeLibrary(HMODULE hModule);

   VOID FreeLibraryAndExitThread(HMODULE hModule,  DWORD dwExitCode );

该函数在Kernel32.dll中实现如下:

    VOID FreeLibraryAndExitThread(HMODULE hModule,  DWORD dwExitCode );

  {

      FreeLibrary(hModule);

      ExitThread(dwExitCode);

  }

该函数主要用于当我们在DLL中创建一个线程,在完成工作后,想先后调用FreeLibrary,ExitThread,这样就会出现问题,当FreeLibrary返回时调用ExitThread的代码已经不复存在了,将会挂掉整个进程。调用FreeLibraryAndExitThread时即使FreeLibrary已经撤销DLL的映射,线程仍可以继续执行并调用ExitThread(这条指令在Kernel32.dll中)。

   注意:系统会在每个进程中为每个DLL维护一个使用计数,LoadLibrary,LoadLibraryEx函数多次调用会递增这个计数(只会在同一个进程中映射一次),FreeLibrary,FreeLibraryAndExitThread会递减这个计数当为0是撤销DLL的映射。

  HMODULE hInstDll = GetModuleHandle(_TEXT("MyLib"));

  if (hInstDll == NULL )

{

   hInstDll = LoadLibrary(_TEXT("MyLib"));

}
  GetModuleHandle传入NULL会返回应用程序的可执行句柄。

  如果只有一个DLL(或EXE)的HMODULE/HINSTANCE,可以通过GetMoudleFileName获取全路径。

  GetModuleHandleEx。

  不要混用LoadLibrary,LoadLibraryEx.以LOAD_LIBRARY_AS_DATAFILE 标志载入的DLL返回句柄调用GetMoudleFileName将会返回0(并不认为是一个完全载入的DLL);

 1.3 DLL入口函数:

      DLL入口函数在执行的时候存在一些限制,这些限制与获取进程范围内的加载程序锁(loader lock)有关。具体参见Best Practices for Creating DLLs:http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx

     DLL_PROCESS_ATTACH:在DLL第一次被映射到进程空间时被通知;在创建新进程时,系统会分配进程地址空间并将EXE的文件映像以及所需DLL的文件映像映射到进程的地址空间中,然后系统将创建进程的主线程并用这个主线程来调用每个DLL的DllMain函数同时传入DLL_PROCESS_ATTACH,当所有已映射的DLL完成该通知的处理后,主线程才开始执行可执行模块的C/C++运行时的启动代码(startup code),然后执行可执行模块的入口点函数(_tmain或_tWinMain)。对于LoadLibrary或LoadLibraryEx载入的DLL由调用此函数的线程调用DllMain函数。

    DLL_PROCESS_DETACH:系统将DLL从进程撤销时会以此通知调用DllMain函数,当撤销的原因是进程要终止,那么调用ExitProcess函数的线程将负责调用DllMain函数,调用FreeLibrary,FreeLibraryAndExitThread撤销时由调用此函数的线程负责调用DllMain函数。在DllMain函数没有处理完DLL_PROCESS_DETACH通知之前,线程是不会从此函数中退出的,只有当所有的DLL通知处理完成后,操作系统才会真正地终止进程。用TerminateProcess函数终止进程,系统不会以此通知调用DllMain函数。

     DLL_THREAD_ATTACH:当新创建一个线程时,系统会检查所有已经映射到进程地址空间的DLL,并以此通知由新创建的线程调用DllMain函数,只有在完成所有的通知处理后,系统才会让新线程执行它的线程函数。当新DLL被映射时系统不会让已经创建的线程以此通知调用DllMain函数,主线程不会处理此通知,在进程创建的时候被映射到进程地址空间的任何DLL会收到DLL_PROCESS_ATTACH通知,但不会收到DLL_THREAD_ATTACH通知。

    DLL_THREAD_DETACH:让线程的终止的首先方式是让它的线程函数返回,这样系统会调用ExitThread来终止线程,但系不会立即终止线程,会让这个线程以此通知调用所有已映射DLL的DllMain函数完成之后,才会终止线程,TerminateThread函数跟TerminateProcess函数一样不会让线程调用DllMain函数,DLL撤销映射时系统不会让任何还在运行的线程以此通知调用DllMain函数。

   注意:以LoadLibrary,LoadLibraryEx载入的DLL系统以DLL_PROCESS_ATTACH来调用DllMain函数(不会DLL_THREAD_ATTACH),在撤销映射时以DLL_THREAD_DETACH通知来调用DllMain函数。

 

   1.4 DllMain的序列化调用:
    一个DLL的DllMain函数在同一时刻只能有一个线程调用处理。

   BOOL DisableThreadLibraryCalls(HMODULE  hModule);让系统不用DLL_THREAD_ATTACH,DLL_THREAD_DETACH通知去调用指定的DLL的DllMain函数。

 

 

 

 

   1.5 DllMain和C/C++运行库:

 

    在链接DLL的时候,链接器会将DLL的 入口点函数的地址嵌入到生成的DLL文件映像中,在默认的情况下,如果用Microsoft链接器并指定/DLL开关,那么入口点函数名是_DllMainCRTStartup,这个函数在c/c++运行库中,在链接DLL时被静态地链接到DLL的文件映像(即使用的是运行库的DLL版本)。

 

   如果我们的DLL代码中没有提供DllMain函数,我们可以使用c/c+运行库提供的DllMain函数,它的实现大致如下(如果静态链接到c/c++运行库):

 

   1.6 延迟载入DLL

     局限性:

     a.一个导出了字段(数据或全局变量)的DLL是无法延迟载入的。

     b.Kernel32.dll模块是无法延迟载入的这是因为必须在入此模块才能调用LoadLibrary和GetProcAddress。

     c.不应该在DllMain入口点函数中调用延迟载入的函数,因为这样可能会导致程序崩溃。

    进一步了解其局限性参阅:Constraints of Delay Loading DLLs:http://msdn.microsoft.com/en-us/library/yx1x886y(VS.80).aspx.

 

    1.7 函数转发器:

    //Function forwarders to function in DllWork

    #pragram comment(linker, "/export:SomeFunc=DllWork:SomeOtherFunc").

    我们必须为每个需要转发的函数单独创建一行pragram.

 

    1.8 已知的DLL

    HKLM/SYSTEM/CurrentControlSet/Control/Session Manager/KownDlls.

    当我们调用LoadLibrary,LoadLibraryEx时系统会检查我们传入的DLL的名字是否包含了.dll扩展名,如果没有,那么函数会用正常的搜索规则进行。如果指定了扩展名,那么会先将扩展名去掉,在KownsDlls注册表项中搜索,如果没有值名与之对应,那么函数将按正常的规则,否则将载入这个对应的DLL.

 

   1.9 DLL重定向:

    强制操作系统的加载程序首先从应用程序的目录中载入模块。SuperAppl.exe的重定向文件名称必须是SuperAppl.exe.local.我们可以创建一个名为.local的文件夹把自己的DLL放在这个文件夹中,让windows能够轻易的找到它们。对应注册的COM对象来说极其有用,它允许应用程序将它的COM对象DLL放在自己的目录中,这样注册的同一个COM对象的其它应用程序就不会妨碍我们的应用程序。Vista这项特性默认关闭。HKLM/Microsoft/WindowsNT/CurrentVersion/Image File Execution Options注册项中增加一个条目DWORD DevOverrideEnable值为1。

 

Isolated Applications and Side-by-side Assemblies:http://msdn.microsoft.com/en-us/library/aa375193.aspx

 

 1.10 模块的基地址重定位

     没个可执行文件的和DLL模块都有一个首先地址(preferred base address),默认DLL(0x10000000),0x00400000.可以用Dumpbin工具(加/headers开关)来查看文件映像的首先基地址。

    当一个模块无法被载入到它的首先基地址时存在以下两个缺点:

    a.加载程序必须遍历重定位段并修改模块中的大量代码。

    b.当加载程序写入到模块的代码页面中时,系统的写时复制机制会强制这些页面以系统的页交换文件作为后备存储器。

    我们可以用/FIXED开关创建一个不包含任何重定位信息的映像。我们应该使用/SUBSYSTEM:WINDOWS,5.0开关,如果连接器检测到模块没有东西需要进行重定位修正,那么它会关闭一个特殊的IMAGE_FILE_RELOCS_STRIPPED标志将模块中的重定位段省略掉。(这时WINDOWS 2000加载程序的一项新特性,5.0也不足为奇了。)。

   Rebase工具。ImageHlp API提供的RaBaseImage函数。

  1.11 模块的绑定

   采用模块绑定技术可以使应用程序更快的初始化并使用更少的存储器。

   Bind工具。ImageHlp API 提供的BindImageEx函数。

   如果加载程序发现模块已经绑定过了,所需的DLL也确实被载入到了它们的首先基地址,而且时间戳也吻合,那它实际上不需要做任何事,不必对任何模块重定位,也不必查看任何导入函数的序列地址,应用程序可以直接开始执行。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值