Windows核心编程:DLL高级技术

 

作者: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.模块的绑定









你可能感兴趣的:(thread,编程,windows,Microsoft,dll,编译器)