Windows核心编程之DLL基础

来源:互联网 发布:软件项目 文档 编辑:程序博客网 时间:2024/05/21 06:55

一.DLL基础

          相信大部分C/C++程序员都用过静态或动态链接库,在Linux和Windows中也大量的使用着静态库和动态库技术。系统提供的接口,标准库接口,都是以库的形式提供;java的JNI是用动态库的技术实现的。今天主要讲的是windows中动态库的一些基础知识点。在windows中动态链接库习惯称为DLL。

        最初接触到动态库的实现是在看 <深入理解计算机系统> 的链接一章,作者详细的讲解了动态链接库的链接过程,加载过程和符号解析,重定位过程。有兴趣的可以去看看这一本书,之后在学windows核心编程的DLL章节时,就会发现在不同的系统中,动态库有不同的表现形式,但本质的东西都是一样的。

       使用DLL的好处:

       (1)模块化,简化了项目管理。通过把逻辑上独立的功能放在一个DLL中实现,增加了模块化。同时,不同的团队或者个人可并行实现自己的模块,提高了开发效率同时简化             了项目的管理。

     (2)动态扩展程序的特性:在运行时可检测一下参数来决定当前环境下应该提供什么功能,然后加载相应的DLL。

     (3)节省内存:这是动态库最重要的一个特性。一个DLL可以被加载进内存多次,即有多个该DLL的实例,但在同一时刻,物理内存中只会加载一次该DLL,即所有加载该DLL 的应用都共享该DLL在物理内存中的页面。所以动态链接库也叫共享库。

        DLL的这种共享特性也会带来一些问题,例如一个DLL在内存中有多个实例,如果其中一个实例改变了DLL中的全局变量,则会影响到其他的实例。这个问题是通过写时复制技术来避免的,即当有一个实例要改变全局变量时,就为该实例单独复制一份DLL到内存中,该实例之后改变的就是这个复制出来的DLL了。


二.构建DLL模块

一个DLL中的函数,变量和类要被其他模块使用,必须将它们导出。通常,我们把要导出的符号单独放在一个头文件中声明,声明这些符号是要导出的;另一方面,其他模块要使用该DLL导出的符号,必须将这些符号导入。

看一个实际的DLL模块的头文件:

#pragma once#ifdef LOG4CPP_EXPORTS#define LOG4CPP_API __declspec(dllexport)#else#define LOG4CPP_API __declspec(dllimport)#endifnamespace myLog4cpp{#ifdef __cplusplusextern "C" {#endif  //__cplusplusvoid LOG4CPP_API mylog(int num1 , int num2) ;#ifdef __cplusplus}#endif //__cplusplus}//namespace myLog4cpp


         该头文件声明了一个函数void LOG4CPP_API mylog(int num1,int num2) ;看头文件前面的宏声明:

         如果定义了宏LOG4CPP_EXPORTS,则LOG4CPP_API为: __declspec(dllexport),这是DLL导出的声明方法;

         如果没有定义宏LOG4CPP_EXPORTS,则LOG4CPP_API为:__declspec(dllimport),这是DLL导入的声明方法;

         所以在DLL模块中,应该定义宏LOG4CPP_EXPORTS,而在使用该DLL的项目中,不能定义宏LOG4CPP_EXPORTS。通过这种方式,可以实用同一个头文件来声明符号的导出和导入。

         另一种导出DLL符号的方法是创建.def文件,并在.def中包含EXPORTS段:

         EXPORTS

                           mylog


        在上面给出的头文件中,还有一个extern "C"的使用,因为C++编译器会改变函数名字,这样会导致C++导出的接口不能被使用C语言的项目使用。extern "C"就是用来避免这种情形的。当用extern "C"时,编译器会把函数接口编译成适合C使用的函数接口。


三.何为导出

          当Microsoft的C/C++编译器看到用__declspec(dllexport)修饰的符号时,会在生成的.obj文件中嵌入一些额外的信息,当链接器在链接DLL的所有.obj文件时,会解析这些信息。在链接DLL的时候,链接器会检测这些与导出的符号有关的嵌入信息,并生成一个.lib文件,这个.lib文件列出了该DLL导出的符号。当其他模块用到该DLL时,只需要有这个.lib文件就可以进行编译链接。除了创建这个.lib文件之外,链接器还会在生成的DLL中嵌入一个导出符号表,这个导出符号表列出了导出的变量,函数和类的符号名。链接器还会保存相对虚拟地址(RVA),即在该DLL内以0为基地址的地址。之后DLL被加载到进程的地址空间时,可以根据这个RVA地址进行重定位。


四.何为导入

     当连接器在解决导入符号的时候,会在生成的可执行模块中嵌入一个特殊的段,即导入段。导入段列出了该模块所需的DLL模块,以及它从每个DLL模块中引用的符号。


四.DLL的载入

        DLL有两种载入方式:隐式载入方式和显式载入方式。DLL的载入是指该DLL的文件映像被映射到调用进程的地址空间中,进程中的所有线程这时就可以调用该DLL中的函数了,对进程中的线程来说,该DLL中的代码和数据就像是一些附加的代码和数据。

        在隐式载入中,DLL的载入是在应用程序加载到进程的地址空间时发生的。

        在显式载入中,DLL的载入是在应用程序的运行时动态加载。直到需要某个功能时,程序才把这个功能所在的DLL加载进来,所以这种方式是最灵活的。window提供了几个函数:

         HMODULE LoadLibrary(PCTSTR pszDLLPathName);         //载入DLL

         VOID  FreeLibrary(HMODULE hInstDll) ;                                 //卸载DLL

         FARPROC GetProcAddress(HMODULE hInstDll,PCSTR pszSymbolName);    //获取想要引用的符号地址


原创粉丝点击