动态链接库dll学习资料总结_第1页
动态链接库dll学习资料总结_第2页
动态链接库dll学习资料总结_第3页
动态链接库dll学习资料总结_第4页
动态链接库dll学习资料总结_第5页
已阅读5页,还剩91页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、1. 什么是lib文件,lib和dll的关系如何 (1)lib是编译时需要的,dll是运行时需要的。 如果要完成源代码的编译,有lib就够了。 如果也使动态连接的程序运行起来,有dll就够了。 在开发和调试阶段,当然最好都有。 (2)一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应

2、用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。 (3)在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,DLL库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行时再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用

3、程序将会产生错误。2、严重警告:(1) 用 extern "C" _declspec(dllexport) 只可以导出全局函数,不能导出类的成员函数(2) 使用extern "C" _declspec(dllexport)输出的函数可以被c语言调用,否则则不可(3) 注意标准调用约定的问题,输出与调用的函数约定应该一致,如当dll模块的函数输出采用标准调用约定_stdcall,则调用程序的导入函数说明也要用标准约定(4)用extern "C" _declspec(dllexport) 和 EXPOTRT导出的函数不改变函数名,可以给c

4、+或c编写的exe调用.假如没有extern "C",导出的函数名将会改变,只能给c+编写的exe调用 (5)在动态加载动态链接库函数时注意GetProcAddress(hInst,"add")中第二个参数是否为动态链接库导出的函数名,因为在生成动态库时可能会改变动态库导出函数的函数名,加上修饰符(6)dll初始化全局变量时,全局变量要放在共享数据断,并且初始化每一个变量,在StartHook函数里初始化其值,记得一进函数就初始化(7)调试时,编译器会自动查找其目录下(不含debug和release目录)的dll文件,所以dll文件应该放在主文件目录下,

5、但生成的应用程序则只会在同一个目录下找dll(不需要lib文件),所以单纯的运行exe,不通过编译器,那就要把dll文件放在与exe相同的目录下(8)用#pragma comment(lib,"dllTest.lib")导入lib文件,不需要在设置里修改(9) dll里的指针变量不要用newDLL 调用方式DLL(动态连接库),可以分为动态调用于静态调用。下面我分别举一个例子说说。1)动态调用:首先:在VC+6.0中创建 Win32 Dynamic-link library工程创建一个动态连接库工程: 在头文件TestDll.h中写下代码 extern "C&qu

6、ot; int _declspec(dllexport) add(int numa, int numb);/声明导出函数 在源文件TestDll.cpp中实现改函数: int _declspec(dllexport) add(int numa, int numb) return numa + numb; 然后,创建一个测试程序,TestDemo,创建一个.cpp文件,然后放下代码:HINSTANCE hinstance;typedef int (*lpAdd)(int a, int b);lpAdd lpadd;int main() hinstance = LoadLibrary("

7、E:vcDLLTestDllDebugTestDll.dll"); lpadd = (lpAdd)GetProcAddress(hinstance, "add"); cout << "2 + 3 = " << lpadd(2, 3) << endl; FreeLibrary(hinstance); return 0;下面我们来逐一分析。首先,语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpA

8、ddFun的实例addFun; 其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll; 再次,在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用; 最后,应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。 通过这个简单的例子,我们获知DLL定义和调用的一般概念: (1)D

9、LL中需以某种特定的方式声明导出函数(或变量、类); (2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。2)静态连接:代码如下:#include <iostream>using namespace std;#pragma comment(lib,"Testlib.lib") /.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息extern "C" _declspec(dllimport) add(int x,int y);/声明导入函数 int main() int result = add(2,3); cout &

10、lt;<result <endl; return 0;注意:(1)、在编写dll文件时,必须以某种方式声明导出函数(dll文件内有导出函数和内部函数两种,内部函数供dll内部调用),可以用自定义模块文件(.def文件)来声明导出函数,也可以使用_declspec(dllexport)前缀来声明导出函数。PS:.def文件除了声明导出函数之外还可以消除函数修饰符的影响,并产生动态链接库导入库(.lib文件)(2)、在显示调用dll时,只需要.dll文件即可,而且只能通过函数指针来调用dll中的导出函数。在隐式调用dll时,要将相应的.lib文件加入到工程中,并且在调用dll的导出函数

11、之前,必须对使用的函数进行声明(或者包含进相应的dll文件的头文件也可)。(3)、.def文件只在生成dll的过程中起作用,在应用程序调用dll时,不起作用。· DLL的调用方法1. 动态链接库(Dynamic Link Library),简称DLL。DLL 是一个包含可由多个程序同时使用的代码和数据的库。它允许程序共享执行特殊任务所必需的代码和其他资源,一般来说,DLL是一种磁盘文件,以.dll、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到调用进程的虚拟空间中,成为调用进程的一部分。DLL的调用

12、可以分为两种:一种是隐式调用,一种是显示调用 这里简单分享DLL的两种调用方法。隐式的调用这种调用方式需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,在使用DLL中的函数时,只须声明一下后就可以直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。隐式调用不需要调用LoadLibrary()和FreeLibrary()。程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过隐式调用方式编译生

13、成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号被写入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序也将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。 显式调用这种调用方式是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调进来,并指定DLL的路径作为参数。LoadLi

14、bary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。当完成对动态链接库的导入以后,再使用GetProcAddress()获取想要引入的函数,该函数将符号名或标识号转换为DLL内部的地址,之后就可以象使用本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态连接库。DLL的优点简单的说,dll有以下几个优点:1) 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如 果使

15、用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。2) 不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。事实上,很多软件都是以这种方式升级的。例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。3) Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。显示/隐式加载dll例子:首先生成dll文件项目属性,配置属

16、性,常规,配置类型,动态库(dll)dll.hint add(int left, int right);dll.cpp#include"dll.h"int add(int left, int right)return left+right;然后会在相应工程目录debug目录下找到dll和lib文件-调用dll把dll.h XXX.dll XXX.lib文件拷贝到新工程目录下加载dll.h到工程里,下面开始使用dllimport.cpp#include"dll.h"/显示加载无需lib文件HINSTANCE hinstance = LoadLibrary(

17、"XXX.dll");typedef int (*_add) (int left, int right);_add addofdll = (_add)GetProAddress(hinstance, "add");/隐式加载需要lib一起只用#prama comment(lib, "XXX.lib");下面可以正常使用add或者addofdll两个函数了-当函数功能比较小的时候,其实可以不用dll,直接用静态连接库lib,虽然它可以使得最后的exe文件变大项目属性,配置属性,常规,配置类型,静态库(lib)定义都和一样,使用的时候直接

18、根据XXX.h文件找到你想使用的函数,然后#prama comment(lib, "XXX.lib");后便可使用。动态库的创建:1 在VC里创建一个Win32项目,比如项目名称叫MyTestDLL,选择DLL和空项目,确定,一个空的DLL工程便创建好;2 添加头文件和源文件以及def文件,之所以使用def文件,是因为对于C+里面的函数,编译器会生成原函数名不同的名字,使用def文件可以直接避免这个问题,也不需要定义_declspec(dllexport)和_declspec(dllimport)这些修饰符。定义要在DLL里包含的函数,比如:int Add(int a, i

19、nt b)return a + b;在def文件里,添加要导出的函数名,如下格式:然后编译工程,就会生成MyTestDLL.dll和MyTestDLL.lib这两个文件。对于动态库的显示调用,在编译时,直接使用一个dll文件,然后在程序中LoadLibrary即可。对于隐式调用,在编译时,需要同时有头文件、dll文件和lib文件,并且也需要和调用静态库一样,使用预处理指令#pragma comment(lib, "libname")静态库的创建很简单,在调用工程中,编译时,只需要头文件和lib文件,也需要上面的预处理指令,注意,“libname”可以带也可以不带“.lib”

20、后缀。对于动态库,调用工程不管编译时采用的是隐式还是显示,运行时都需要dll文件;对于静态库,调用工程则不需要在运行期引用静态库文件。动态库和静态库和运行时库和引入库的区别 1。运行时库:Unix中一个典型的运行时库例子就是libc,它包含标准的C函数,如,print(),exit()等等,用户能创建他们自己的运行库(在Windows中是DLL),而具体的细节依赖编译器和操作系统的。2。静态库:函数和数据被编译进一个二进制文件(通常扩展名为.lib),静态库实际上是在链接时被链接到EXE的,库本身不需要与可执行文件一起发行。3。动态库:用VC+创建的动态库包含两个文件,一个lib文件和一个dl

21、l文件,这个lib文件就是引入库,不是静态库,引入库有时也叫输入库或导入库。注:windows操作系统下动态库和运行时库的扩展名都是.dll,COM组件的扩展名也是.dll,动态库的引入库和静态库的扩展名都是.lib。windows下调用动态库的方法:1。隐式加载:即在程序中包含lib文件和.h文件,隐式链接有时称为静态加载或加载时动态链接。例如:#include "somedll.h"#pragma comment( lib, "somedll.lib")然后就可以直接调用此dll中的函数,注意运行时仍然需要somedll.dll。2。显示加载:使用l

22、oadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函数的原型。显式链接有时称为动态加载或运行时动态链接。3。区别:如果在进程启动时未找到 DLL,操作系统将终止使用隐式链接的进程。同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。有关Win32 DLL,Unix共享库及普通库的详细库结构信息请参考链接器与加载器一书。 MSDN:1。确定要使用的链接方法:有两种类型的链接:隐式链接和显式链接。隐式链接应用程序的代码调用导出 DLL 函数时发生隐式链接。当调用可执行文件的源代码被编译或被汇编时,DLL 函数调

23、用在对象代码中生成一个外部函数引用。若要解析此外部引用,应用程序必须与 DLL 的创建者所提供的导入库(.LIB 文件)链接。导入库仅包含加载 DLL 的代码和实现 DLL 函数调用的代码。在导入库中找到外部函数后,会通知链接器此函数的代码在 DLL 中。要解析对 DLL 的外部引用,链接器只需向可执行文件中添加信息,通知系统在进程启动时应在何处查找 DLL 代码。系统启动包含动态链接引用的程序时,它使用程序的可执行文件中的信息定位所需的 DLL。如果系统无法定位 DLL,它将终止进程并显示一个对话框来报告错误。否则,系统将 DLL 模块映射到进程的地址空间中。如果任何 DLL 具有(用于初始

24、化代码和终止代码的)入口点函数,操作系统将调用此函数。在传递到入口点函数的参数中,有一个指定用以指示 DLL 正在附带到进程的代码。如果入口点函数没有返回 TRUE,系统将终止进程并报告错误。最后,系统修改进程的可执行代码以提供 DLL 函数的起始地址。与程序代码的其余部分一样,DLL 代码在进程启动时映射到进程的地址空间中,且仅当需要时才加载到内存中。因此,由 .def 文件用来在 Windows 的早期版本中控制加载的 PRELOAD 和 LOADONCALL 代码属性不再具有任何意义。显式链接大部分应用程序使用隐式链接,因为这是最易于使用的链接方法。但是有时也需要显式链接。下面是一些使用

25、显式链接的常见原因:直到运行时,应用程序才知道需要加载的 DLL 的名称。例如,应用程序可能需要从配置文件获取 DLL 的名称和导出函数名。如果在进程启动时未找到 DLL,操作系统将终止使用隐式链接的进程。同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。例如,进程可通知用户所发生的错误,并让用户指定 DLL 的其他路径。如果使用隐式链接的进程所链接到的 DLL 中有任何 DLL 具有失败的 DllMain 函数,该进程也会被终止。同样是在此情况下,使用显式链接的进程则不会被终止。因为 Windows 在应用程序加载时加载所有的 DLL,故隐式链接到许多 DLL 的应用

26、程序启动起来会比较慢。为提高启动性能,应用程序可隐式链接到那些加载后立即需要的 DLL,并等到在需要时显式链接到其他 DLL。显式链接下不需将应用程序与导入库链接。如果 DLL 中的更改导致导出序号更改,使用显式链接的应用程序不需重新链接(假设它们是用函数名而不是序号值调用 GetProcAddress),而使用隐式链接的应用程序必须重新链接到新的导入库。下面是需要注意的显式链接的两个缺点:如果 DLL 具有 DllMain 入口点函数,则操作系统在调用 LoadLibrary 的线程上下文中调用此函数。如果由于以前调用了 LoadLibrary 但没有相应地调用 FreeLibrary 函数

27、而导致 DLL 已经附加到进程,则不会调用此入口点函数。如果 DLL 使用 DllMain 函数为进程的每个线程执行初始化,显式链接会造成问题,因为调用 LoadLibrary(或 AfxLoadLibrary)时存在的线程将不会初始化。 如果 DLL 将静态作用域数据声明为 _declspec(thread),则在显式链接时 DLL 会导致保护错误。用 LoadLibrary 加载 DLL 后,每当代码引用此数据时 DLL 就会导致保护错误。(静态作用域数据既包括全局静态项,也包括局部静态项。)因此,创建 DLL 时应避免使用线程本地存储区,或者应(在用户尝试动态加载时)告诉 DLL 用户潜

28、在的缺陷。2。隐式链接:为隐式链接到 DLL,可执行文件必须从 DLL 的提供程序获取下列各项:包含导出函数和/或 C+ 类的声明的头文件(.h 文件)。类、函数和数据均应具有 _declspec(dllimport),有关更多信息,请参见 dllexport, dllimport。要链接的导入库(.LIB files)。(生成 DLL 时链接器创建导入库。)实际的 DLL(.dll 文件)。使用 DLL 的可执行文件必须包括头文件,此头文件包含每个源文件中的导出函数(或 C+ 类),而这些源文件包含对导出函数的调用。从编码的角度讲,导出函数的函数调用与任何其他函数调用一样。若要生成调用可执行

29、文件,必须与导入库链接。如果使用的是外部生成文件,请指定导入库的文件名,此导入库中列出了要链接到的其他对象 (.obj) 文件或库。操作系统在加载调用可执行文件时,必须能够定位 DLL 文件。3。显式链接:在显式链接下,应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序必须:调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。使用完 DLL 后调用 FreeLibrar

30、y。typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);.HINSTANCE hDLL; / Handle to DLLLPFNDLLFUNC1 lpfnDllFunc1; / Function pointerDWORD dwParam1;UINT uParam2, uReturnVal;hDLL = LoadLibrary("MyDLL");if (hDLL != NULL)lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,"DLLFunc1");if (!

31、lpfnDllFunc1)/ handle the errorFreeLibrary(hDLL); return SOME_ERROR_CODE;else/ call the functionuReturnVal = lpfnDllFunc1(dwParam1, uParam2);4。将可执行文件链接到 DLL 可执行文件以下列两种方式之一链接到(或加载)DLL:隐式链接 显式链接 隐式链接有时称为静态加载或加载时动态链接。显式链接有时称为动态加载或运行时动态链接。在隐式链接下,使用 DLL 的可执行文件链接到该 DLL 的创建者所提供的导入库(.lib 文件)。使用 DLL 的可执行文件加载

32、时,操作系统加载此 DLL。客户端可执行文件调用 DLL 的导出函数,就好像这些函数包含在可执行文件内一样。在显式链接下,使用 DLL 的可执行文件必须进行函数调用以显式加载和卸载该 DLL,并访问该 DLL 的导出函数。客户端可执行文件必须通过函数指针调用导出函数。可执行文件对两种链接方法可以使用同一个 DLL。另外,由于一个可执行文件可隐式链接到某个 DLL,而另一个可显式附加到此 DLL,故这些机制不是互斥的。vc调用dll 调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同。Windows提供了两种将DLL映像到进

33、程地址空间的方法:1. 隐式的加载时链接这种方法需要DLL工程经编译产生的LIB文件,此文件中包含了DLL允许应用程序调用的所有函数的列表,当链接器发现应用程序调用了LIB文件列出的某个函数,就会在应用程序的可执行文件的文件映像中加入一些信息,这些信息指出了包含这个函数的DLL文件的名字。当这个应用程序运行时,也就是它的可执行文件被操作系统产生映像文件时,系统会查看这个映像文件中关于DLL的信息,然后将这个DLL文件映像到进程的地址空间。系统通过DLL文件的名称,试图加载这个文件到进程地址空间时,它寻找DLL 文件的路径按照先后顺序如下:·程序运行时的目录,即可执行文件所在的目录;&

34、#183;当前程序工作目录·系统目录:对于Windows95/98来说,可以调用GetSystemDirectory函数来得到,对于WindowsNT/2000来说,指的是32位Windows的系统目录,也可以调用GetSystemDirectory函数来得到,得到的值为SYSTEM32。·Windows目录·列在PATH环境变量中的所有目录VC中加载DLL的LIB文件的方法有以下三种:LIB文件直接加入到工程文件列表中在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中“Add Files to Project”菜单,在弹出的文件对话框中选中要

35、加入DLL的LIB文件即可。设置工程的 Project Settings来加载DLL的LIB文件打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。通过程序代码的方式加入预编译指令#pragma comment (lib,”*.lib”),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。当应用程序对DLL的LIB文件加载后,

36、还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。2 显式的运行时链接 ,(我用的是此方法)隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行

37、动态加载。使用GetProcAddress函数得到要调用DLL中的函数的指针。不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。extern "c"与.def文件的作用首先,我们需要知道C和C+编译器对函数名字的处理方式是不一样的;其次,就是同为C编译器的两个不同产品,在编译时对函数名字的处理方式也是有区别的,比如microsoft vc+与dev c+。所以,extern "C"与.def文件正是为了解决这两种情况而引入的处理方法。第一、extern "C"的作用 比如一

38、个C源程序A.c要使用C+编写的库函数,在A.c中#include "B.h",其中B.h中有要使用的函数的原形声明func。当编译链接源程序时,却发现了“链接错误,未决的外部符号.”的错误,这是什么原因呢?原因就是,C编译器编译A.c时,将func编译为func,当链接时链接器去C+库中寻找func,但是C+的编译器在编译库时将func编译成_funcyyyrrr,自然链接器就找不着相应的函数的信息了,所以就会报错!有什么办法可以处理这种情况呢?可以在编写C+库的时候,为每一个函数(或导出函数)加上extern "C",它的含义是告知C+编译器在编译这

39、些函数的时候,以C编译器的方式处理函数名。这样生成的库中的函数名字就是func了,当C程序调用库函数,编译链接时,链接器就能找到期望的信息,则链接成功。第二、.def文件的作用(仅与VC+编程相关) 前面提到,不同厂商开发的两个C编译器也会有一些差异,最突出的就是microsoft的C编译器,它对函数名字的处理很特别(究竟是什么样子,可以使用Dumpbin工具查看dll的导出函数),所以要在使用他方编写的库时,程序链接能成功,有两种方法:1使用库编写者使用的C编译器(这里指VC+),显然这种方法不合理;2库的编写者在使用VC+编写库时使用.def文件。 .def文件的作用即是,告知编译器不要以

40、microsoft编译器的方式处理函数名,而以指定的某方式编译导出函数(比如有函数func,让编译器处理后函数名仍为func)。这样,就可以避免由于microsoft VC+编译器的独特处理方式而引起的链接错误。用.def文件描述dll的输出函数,比如 EXPORTS DllFun1 1 NONAME DllFun2 2 NONAME DllFun3 3 NONAME DllFun4 4 NONAME . 后面是dll输出函数的顺序号,顺序号要求在1到max(函数个数)之间。 用.def声明了输出函数以后,不要在头文件里再声明AFX_EXT_API或者是dllexport之类的声明,而用ext

41、ern "C"声明了输出函数以后,要引用原来输出函数的头文件。如果不是用extern "C"声明的(包括用.def声明的),那么头文件里无需用AFX_EXT_API或者是dllexport之类的声明。.def文件的作用在VC+中,生成DLL可以不使用.def文件。只需要在VC+的函数定义前要加_declspec(dllexport)修饰就可以了。但是使用_declspec(dllexport)和使用.def文件是有区别的。如果DLL是提供给VC+用户使用的,你只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL。但是如果你的DLL是

42、供其他程序如VB、delphi,以及.NET用户使用的,那么会产生一个小麻烦。因为VC+对于_declspec(dllexport)声明的函数会进行名称转换,如下面的函数: _declspec(dllexport) int _stdcall IsWinNT() 会转换为IsWinNT0,这样你在VB中必须这样声明: Declare Function IsWinNT Lib "my.dll" Alias "IsWinNT0" () As Long 的后面的数由于参数类型不同而可能不同。这显然不太方便。所以如果要想避免这种转换,就要使用.def文件方式。 E

43、XPORTS后面的数可以不给,系统会自动分配一个数。对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。例子:我们用VC6.0制作一个dll,不使用.def文件,在头文件中这样写#ifndef LIB_H#define LIB_Hextern "C" int _declspec(dllexport)add(int x,int y);#endif 如果是.def文件,可以这样LIBRARY "xxx_dll"EXPORTSadd

44、PRIVAT动态链接库(Dynamic Link Library)学习笔记 我对动态链接和动态链接库的概念并不陌,但一直以来就停留在概念的层面上,没有更深入的了解。今天抽空看了一下有关动态链接和动态链接库的文章,有了一些新的认识,当然不能忘了写在这里。那么现在就开始.什么是动态链接和动态链接库 动态链接(Dynamic Linking)是相对于静态链接(Static Linking)而言的。程序设计中,为了能做到代码和模块的重用,程序设计者常常将常用的功能函数做成库,当程序需要实现某种功能时,就直接调用库文件中的函数,从而实现了代码的重用。早期的程序设计中,可重用的函数模块以编译好的二进制代码

45、形式放于静态库文件中,在MS的操作系统中是Lib为后缀的文件。程序编写时,如果用户程序调用到了静态库文件中的函数,则在程序编译时,编译器会自动将相关函数的二进制代码从静态库文件中复制到用户目标程序,与目标程序一起编译成可执行文件。这样做的确在编码阶段实现了代码的重用,减轻了程序设计者的负担,但并未在执行期实现重用。如一个程序a.exe使用了静态库中的 f() 函数,那么当a.exe有多个实例运行时,内存中实际上存在了多份f()的拷贝,造成了内存的浪费。 随着技术的进步,出现了新的链接方式,即动态链接,从根本上解决了静态链接方式带来的问题。动态链接的处理方式与静态链接很相似,同样是将可重用代码放

46、在一个单独的库文件中(在MS的操作系统中是以dll为后缀的文件,Linux下也有动态链接库,被称为Shared Object的so文件),所不同的是编译器在编译调用了动态链接库的程序时并不将库文件中的函数执行体复制到可执行文件中,而是只在可执行文件中保留一个函数调用的标记。当程序运行时,才由操作系统将动态链接库文件一并加载入内存,并映射到程序的地址空间中,这样就保证了程序能够正常调用到库文件中的函数。同时操作系统保证当程序有多个实例运行时,动态链接库也只有一份拷贝在内存中,也就是说动态链接库是在运行期共享的。 使用动态链接方式带来了几大好处:首先是动态链接库和用户程序可以分开编写,这里的分开即

47、可以指时间和空间的分开,也可以指开发语言的分开,这样就降低了程序的耦合度;其次由于动态链接独特的编译方式和运行方式,使得目标程序本身体积比静态链接时小,同时运行期又是共享动态链库,所以节省了磁盘存储空间和运行内存空间;最后一个是增加了程序的灵活性,可以实现诸如插件机制等功能。用过winamp的人都知道,它的很多功能都是以插件的形式提供的,这些插件就是一些动态链接库,主程序事先规定好了调用接口,只要是按照规定的调用接口写的插件,都能被winamp调用。 WIndow 95、98、NT系列等系统都提供了动态链接库的功能,并且这些操作系统的系统调用大多都是通过动态链接库实现的,最常见的NT系列OS中

48、的KENEL32.dll,USER32.dll,GDI32.dll等动态链接库文件就包含了大量的系统调用。在windows家族中,NT内核的操作系统在动态链接库机制上较之前的95、98系统要更安全。95、98系统在程序调用动态链接库时,将动态链接库加载到2G-3G之间的被称为进程共享空间的虚拟地址空间,并且所有进程关于这1G的虚拟地址空间的页表都是相同的,也就是说对于所有的进程,这片共享区的页表都指向同一组物理页,这样一来,加载入内存的的动态链接库对所有正在运行的进程都是可见的。如果一个动态链接库被其中一个进程更改,或其自身崩溃,将影响到所有调用它的进程,如果该动态链接库是系统的动态链接库,那

49、么将导致系统的崩溃。在Windows NT系统中,动态链接库被映射到进程的用户地址空间中,并用Copy On Write机制保证动态链接库的共享安全,Copy On Write可以理解为写时拷贝。一般情况下,多个运行的进程还是按原来的模式共享同一个动态链接库,直到有进程需要向动态链接库的某个页面写数据时,系统将该页做一个拷贝,并将新复制页面的属性置为可读可写,最后修改进程的页表使之指向新拷贝的物理页。这样无论该进程怎么修改此页的数据,也不会影响到其他调用了此动态链接库的进程了。Windows下动态链接库的编写 因为本人对linux没有太多研究,所以这里只介绍windwos环境下动态链接库的编写

50、。 在VC中新建一个空的Win32动态链接库工程(Win32 Domanic Library),然后添加一个C+ Sourse File到工程,我这里的文件名取DllTest.cpp。然后在文件中添加如下内容: /DllTest.cpp _declspec(dllexport) int add(int a,int b) return a+b; _declspec(dllexport) int subtract(int a,int b) return a-b; 接下来编译链接,就会在debug目录下生成一个调试版本的动态链接库,该链接库包含了add和subtract两个可供外部调用的函数。我们注

51、意到,在源文件中多了一个没有见过的语句 _declspec(dllexport) ,这个语句的作用就是向编译器指出我需要在生成的动态链接库中导出的函数,没有导出的函数是不能被其他程序调用的。要知道一个动态链接库导出了什么函数,可以在命令提示行用命令"dumpbin -exports DllTest.dll"来查看(也可以用VC工具包中的depends使用程序来查看)。以下是用dumpbin命令查看DllTest.dll而生成的信息:Dump of file DllTest.dll File Type: DLL Section contains the following e

52、xports for DllTest.dll 0 characteristics 4420BEA4 time date stamp Wed Mar 22 11:04:04 2006 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 0000100A ?addYAHHHZ 2 1 00001005 ?subtractYAHHHZ Summary 7000 .data 1000 .idata 3000 .rdata 2000 .reloc 2A000 .text

53、 可以看到,我们编写的动态链接库导出了两个函数,分别名为?addYAHHHZ 和 ?subtractYAHHHZ,为什么名字不是add和subtract呢?这是因为C+为了支持函数的重载,会在编译时将函数的参数类型信息以及返回值类型信息加入到函数名中,这样代码中名字一样的重载函数,在经过编译后就互相区分开了,调用时函数名也经过同样的处理,就能找到对应的函数了。编译器对函数的重命名规则是与调用方式相关的,在这里采用的是C+的默认调用方式。以此对应的还有stdcall方式、cdecl方式、fastcall方式和thiscall方式,不同调用方式的重命名规则不一样。 需要特别说一下的是stdcall

54、方式和cdecl方式: stdcall方式(标准调用方式)也即pascal调用方式,它的重命名规则是函数名自动加前导的下划线,后面紧跟一个符号,其后紧跟着参数所占字节数,之所以要跟参数字节数,是因为stdcall采用被调函数平衡堆栈方式,用函数名最后的数字告诉编译器需要为函数平衡的字节数。例如,如果我们的DllTest.dll采用stdcall方式编译的话,导出的函数名将会是 _add8 和 _subtract8 ,而函数编译后的汇编代码最后一句一定是 ret8。 cdecl方式即C语言调用方式,它的重命名规则仅仅是在函数名前加下划线(奇怪的是我用vc6编译的c语言函数,名字没有任何改变),因

55、为C语言采用的是调用函数平衡堆栈的方式,所以不需要在函数名中加入参数所占的字节数,这样的堆栈平衡方式也使C语言可以编写出参数不固定的函数;同时C语言不支持函数重载,因此不需要在函数名中加入参数类型信息和返回值类型信息。 更多关于调用方式的介绍请看我收藏的文章C语言函数调用约定 。 动态链接库已经生成了,接下来就是调用的工作了。调用动态链接库有两种方式:隐式调用和显式调用,下面我们分别来看两种调用方式的具体过程:动态链接库的隐式调用 新建一个空的Win32 Console Application,命名为DllCaller,向工程中添加名为DllCaller.cpp 的C+ Sourse File

56、,在文件中写入如下代码:#include <iostream>using namespace std;/extern int add(int a,int b);_declspec(dllimport) int add(int a,int b);int main() cout<<"3+5="<<add(3,5)<<endl; return 1; 编译,没有错误,链接,有两个错误:找不到外部引用符号。要怎样才能让我们的程序找到动态连接库中的函数呢?这里是关键的一步。到刚才的DllTest工程目录下,从debug文件夹中拷贝生成的DllTest.dll文件和DllTest.lib文件到DllCaller工程目录。然后依次在vc中选择菜单:Project ->Settings->Liink, 在Object/library Modules中加入一项文件名:DllTest.lib,这里的DllTest.lib并不是静态库文件,而是DllTest.dll的导入库文件,它包含了DllTest.dll动态链接库导出的函数信息,只有在工程链接设置里添加了该文件,才能够使调用了该动态链接库的工程正确链接。完成以上步骤后,我们再编译链接

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论