现在的位置: 首页 > 综合 > 正文

Windows核心编程:DLL高级技术

2017年05月23日 ⁄ 综合 ⁄ 共 4402字 ⁄ 字号 评论关闭

 

作者:shenzi

链接:http://blog.csdn.net/shenzi

Windows核心编程:DLL高级技术

1.DLL模块的显示载入和符号链接

    图1显示了应用程序如何显示地载入一个DLL并与DLL的符号进行链接:


图1:DLL创建过程以及应用程序显式链接到DLL的过程

构建DLL


1)头文件,其中包含待导出函数的原型、结构和符号的声明
2)C/C++源文件,其中包含待导出函数的实现和变量
3)编译器为每个C/C++源文件生成.obj文件
4)连接器将每个.obj模块合并,从而生成DLL
5)如果至少导出了一个函数/变量,那么链接器会同时生成.lib文件
注意:在显示链接的时候,没有用到这个.lib文件


构建EXE



6)头文件,其中包含待导出函数的原型、结构和符号的声明
7)C/C++源文件,其中包含待导出函数的实现和变量
8)编译器为每个C/C++源文件生成.obj文件
9)链接器将每个.obj模块合并,从而生成.exe
注意:由于没有直接引用该DLL导出的符号,因此这里不需要它的.lib文件。生成的.exe文件中不包含导入表


显示地载入DLL模块


    在任何时候,进程中的一个线程可以调用下面两个函数来将一个DLL映射到进程的地址空间中:
    

HMODULE LoadLibrary(PCTSTR pszDLLPathName);



    

HMODULE LoadLibraryEx(

        PCTSTR pszDLLPathName,

        HANDLE hFile,

        DWORD dwFlags);



    
这两个函数会在用户的系统中对DLL的文件映像进行定位,并试图将该文件映像映射到调用进程的地址空间中。两个函数返回的HMODULE表示文件映像被映
射到的虚拟内存地址。DllMain入口点所接收的HINSTANCE参数也同样是文件映像被映射到的虚拟内存地址。

显示地卸载DLL模块   
    

当进程不再需要引用DLL中的符号时,我们应该调用下面的函数来显示地将DLL从进程的地址空间中卸载:
    


VOID FreeLibraryAndExitThread(
    HMODULE hInstDll,
    DWORD dwExitCode);




     线程可以通过调用GetModuleHandle函数来检测一个DLL是否已经被映射到了进程的地址空间中:
    


HMODULE GetModuleHandle(PCTSTR pszModuleName);
  


如果传NULL给



GetModuleHandle,那么函数会返回应用程序的可执行文件的句柄。





显示地链接到导出符号


    一旦显示地载入了一个DLL模块,线程必须通过调用下面的函数来得到它想要引用的符号的地址:
    

FARPROC GetProcAddress(
    HMODULE hInstDll,//指定包含符号的DLL句柄,通过先前调用LoadLibrary(Ex)或GetModuleHandle返回
    PCSTR pszSymbolName);//指定想要返回的符号名或序号


     注意:参数




pszSymbolName
在函数原型中的类型为PCSTR,而不是PCTSTR。这意味着GetProcAddress函数只能接受ANSI字符串——我们从来不会传Unicode字符串给这个函数,这是因为编译器/链接器始终都是将符号的名称以ANSI字符串的形式保存在DLL的导出段中的。





2.DLL的入口点函数


     一个DLL可以有一个入口点函数。系统会在不同的时候调用这个入口点函数。这些调用时通知性质的,通常被DLL用来执行一些与进程或线程有关的初始化和清理工作。我们可以像下面这样来实现入口点函数:
     




BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {

        switch (fdwReason) {

            case DLL_PROCESS_ATTACH:
            // The DLL is being mapped into the process' address space.

            break;

            case DLL_THREAD_ATTACH:

            // A thread is being created.

            break;

            case DLL_THREAD_DETACH:
            // A thread is exiting cleanly.

            break;

            case DLL_PROCESS_DETACH:

            // The DLL is being unmapped from the process' address space.
         break;

        }

        return(TRUE); // Used only for DLL_PROCESS_ATTACH

    }


  
 
参数hInstDll包含该DLL实例的句柄,这个值表示一个虚拟内存地址,DLL的文件映像就被映射到进程地址空间中的这个位置。如果DLL是隐式载入
的,那么最后一个参数fImpLoad的值将不为零,如果DLL是显式载入的,那么fImpLoad的值将为零。参数fdwReason表示系统调用入口
点函数的原因。这个参数可能是下列4个值之一:DLL_PROCESS_ATTACH

, DLL_PROCESS_DETACH

, DLL_THREAD_ATTACH

, 或 DLL_THREAD_DETACH


DLL_PROCESS_ATTACH:

   


当系统第一次将一个DLL映射到进程的地址空间中时,会调用DllMain函数,并在fdwReason参数中传入DLL_PROCESS_ATTACH。



DLL_PROCESS_DETACH:

   


当系统将一个DLL从进程的地址空间中撤销映射时,会调用DLL的DllMain函数,并在fdwReason参数中传入


DLL_PROCESS_DETACH



    图2显式了县城调用LoadLibrary时系统执行的步骤;图3显式了当线程调用FreeLibrary时系统执行的步骤:

  
图2:线程调用LoadLibrary是系统执行的步骤

图3:线程调用FreeLibrary时系统执行的步骤

DLL_THREAD_ATTACH:


    
当进程创建一个线程的时候,系统会检查当前映射到该进程的地址空间中的所有DLL文件映射,并用


DLL_THREAD_ATTACH来调用每个DLL的DllMain函数。





DLL_THREAD_DETACH:



    让线程终止的首选方式是它的线程函数返回。这会使得系统调用ExitThread来终止线程。ExitThread告诉系统改线程想要终止,但系统不会立即终止该线程,而会让这个即将终止的线程用
DLL_THREAD_DETACH来调用所有已映射DLL的DllMain函数。



DllMain的序列化调用:


    系统会将对DLL的DllMain函数的调用序列化。

DllMain和C/C++运行库:


   
编写一个DLL得时候,可能需要C/C++运行库在启动方面给予我们一些帮助。举个例子,假设我们正在构建的DLL包含一个全局变量,这个全局变量是一个
C++类的实例。在我们能够在DllMain函数中安全地使用该全局变量之前,必须保证它的构造函数已经被调用过。这就是C/C++运行库的DLL启动代
码的工作。
     在默认情况下,如果用Microsoft链接器并制定了/DLL开关,那么链接器会认为入口点函数名是_DllMainCRTStartup


这个函数包含在C/C++运行库中,在链接DLL的时候回被静态地链接到DLL的文件映像。(即便用的是C/C++运行库的DLL版本,对这个函数的链接仍会是静态的)。在C/C++运行时的初始化完成之后,_DllMainCRTStartup

函数会调用我们的DllMain函数。
3.延迟载入DLL

     一个延迟载入的DLL是隐式链接的,系统一开始不会将该DLL载入,只有当我们的代码视图区引用DLL中包含的一个符号时,系统才会实际载入该DLL。
4.函数转发器


    函数转发器(function forwarder)是DLL输出段中的一个条目,用来将一个函数调用转发到另一个DLL中的另一个函数。
     我们可以在自己的DLL中模块中使用函数转发器。最简单的方法是使用pragma指示符,如下所示:
    

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



5.已知的DLL


    系统对操作系统提供的某些DLL进行了特殊的处理,这些DLL被称为已知的DLL
(known DLL)。在注册表有一个注册表项:
    

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/KnownDLLs



     举例说明,假设我们在KnownDLLs注册表项中添加了下列值:
    

Value name: SomeLib

      Value data: SomeOtherLib.dll


 
 
    系统会用正常的搜索规则来对这个DLL进行定位

  
   LoadLibrary(TEXT("SomeLib"));  

//载入的是
SomeOtherLib.dll



   LoadLibrary(TEXT("SomeLib.dll")); 

//载入的依然是
SomeOtherLib.dll
,而不是


SomeLib


   LoadLibrary

LoadLibraryEx
被调用的时候,函数首先会检查我们传入的DLL地名字是否包含.dll扩展名。如果没有包含,那么函数会用正常的搜索规则来搜索这个DLL。如果指定
了.dll扩展名,那么这两个函数会先将扩展名去掉,然后再KnownDLLs注册表项中搜索,看其中是否有与之相符的值名。如果没有值名与之相符,那么
函数会使用正常的搜索规则。但是,如果找到了与之相符的值名,那么系统会查看与值名相对应的数据,并试图用该数据来载入DLL。


6.DLL重定向


   
7.模块的基地址重定位


    Rebase.exe

    如果在执行Rebase工具的时候传给它一组映像文件名,那么它会执行下列操作:

  • 它会模拟创建一个进程地址空间
  • 它会打开应该被载入到这个地址空间中的所有模块,并得到每个模块的大小以及它们的首选基地址
  • 它会在模拟的地址空间中对模块重定位的过程进行模拟,使各模块之间没有交叠
  • 对每个重定位过的模块,它会解析该模块的重定位段,并修改模块在磁盘文件中的代码
  • 为了反映新的首选基地址,它会更新每个重定位过的模块的头文件    

8.模块的绑定

抱歉!评论已关闭.