动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。
1、我们可以把完成某种功能的函数放在一个动态链接库中,提供给其它函数调用。Windows API中所有的函数都包含在DLL中,其中三个最重要:
1)Kernal32.dll 包含那些用于内存管理,进程和线程的函数,如CreateThread函数。
2)User32.dll 包含执行用户界面任务,如窗口的创建和消息的传送的函数,如3)CreateWindow函数。
3)GDI32.dll 用于画图的显示文本的函数。
2、静态库与动态库:
静态库:
函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。产品发布时不需要发布被使用的静态库。
动态库:
动态库一般提供两个文件:一个引入库(.lib),一个是DLL(.dll)文件。.lib文件中包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。发布产品时要同时发布 DLL。
图示 P703 两个进程访问同一个DLL时的情形
两种加载dll文件的方式,隐式加载和显式加载。
3、Dumpbin命令
应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数。可以用Dumpbin查看。在使用之前可能需要通过VCVARS32.bat来建立VC使用的环境变量。
dumpbin -exports name.dll
4、导出DLL中函数
加_declspec(dllexport)于函数前面。
1)隐式加载
(1)通过extern声明外部函数。并在工程属性的链接器的命令行中输入lib符号名文件。把dll文件和lib文件放入指定目录中在工具选项的VC++目录中包含。
(2)利用_declspec(dllimport)于外部函数声明前。
为了方便dll的使用,通常都是提供一个.h文件给客户的;我们通常在头文件的函数声明按如下形式处理:
#ifdef DLL1_API #else #define DLL1_API _declspec(dllimport) #endif DLL1_API int _stdcall add(int a,int b);
#define DLL1_API extern "C" _declspec(dllexport) #include "Dll1.h" #include <Windows.h> #include <stdio.h> int _stdcall add(int a,int b) { return a+b; }
同样,可以把上面定义的DLL1加在一个类前面,或类的成员函数前面来把类或类的成员函数导出。如下所示:
class /*DLL1*/ Point { public: void DLL1_API output(int x,int y); void test(); };
2)显式加载
通过LoadLibrary来实现。LoadLibrary将指定的可执行模块映射到调用进程的地址空间,and the return value is a handle to the module。再通过GetProcAddress 来获得动态库中导出函数的地址。
HINSTANCE hInst; hInst=LoadLibrary("Dll3.dll"); typedef int (/*_stdcall*/ *ADDPROC)(int a,int b); //ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z"); ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"add"); //ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
The MAKEINTRESOURCE macro converts an integer value to a resource type
compatible with the resource-management functions. This macro is used in place of a
string containing the name of the resource
The DllMain function is called when the driver DLL first starts up.
使用完后,通过FreeLibrary 来减少被加载的dll的引用计数,当减到0时,该DLL模块从调用进程的地址空间卸载。
5、名字改编问题
不同的编译器在编译函数时,及采用不同的函数调用约定时,对函数的改编是不一样的。
http://www.cnblogs.com/mydomain/archive/2010/09/27/1837179.html
由此,在函数调用时会产生一些问题,如用C++编译器生成的函数通过C编译器调用时会出错。对于这种情况,可以在声明中加上extern "C"
#define DLL1_API extern "C" _declspec(dllimport)
利用extern "C"可以解决C++,C间相互调用问题,但是有一个缺陷,就是不能用于导出一个类的成员函数。,只能用于导出全局函数这种情况。
如果函数的调用约定发生了变化,如有的函数用_stdcall,有的用pascal,那么即使使用了extern "C",名字改编仍会发生。
可以通过定义一个模块定义文件.def的方式来解决这个问题:
新建一个newname.def文件,加入工程中:
LIBRARY Dll2 EXPORTS add subtract
EXPORTS:下面定义的符号名如果与函数名一样,则以符号名导出函数,如果不同,按下面规则导出函数:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
There are three methods for exporting a definition, listed in recommended order of use:
1.The __declspec(dllexport) keyword in the source code
2.An EXPORTS statement in a .def file
3.An /EXPORT specification in a LINK command[1]
参考:
[1] http://msdn.microsoft.com/en-us/library/hyx1zcd3.aspx
[2] VC++深入详解