动态连接库一般不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其余DLL调用来完成某项工做的函数。只有在其余模块调用动态连接库中的函数时,它才发挥做用。实际编程时,可把完成某种功能的函数放在一个动态连接库中,提供给其余程序调用。编程
Windows API中全部函数都包含在DLL中,比较重要的有3个,分别为:函数
一、Kernel32.dllspa
包含用于管理内存、进程和线程的函数。线程
二、User32.dllcode
包含用于执行用户界面任务(如窗口建立和消息传递的函数等)的函数。orm
三、GDI32.dllblog
包含用于画图和显示文本的函数。进程
静态库和动态库内存
一、静态库文档
函数和数据被编译成一个二进制文件,扩展名为.LIB。使用静态库时,编译连接可执行文件时,连接器从库中复制这些函数和数据并把它们和应用程序的其余模块组合起来建立最终的可执行文件。发布产品时,只须要发布这个可执行文件,并不须要发布被使用的静态库。
二、动态库
须要提供2个文件:引入库.lib文件和DLL文件。动态库的引入库.lib文件与静态库虽而后缀相同,但含义彻底不一样。引入库文件.lib包含DLL导出的函数和变量的符号名,.dll文件包含DLL实际的函数和数据。编译连接时,只须要连接DLL的引入库文件,DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载须要的DLL,将该DLL映射到进程的地址空间,而后访问DLL中的导出函数。发布产品时,除了发布可执行文件,还要同时发布动态连接库。
若是多个应用程序使用同一个DLL,该DLL的页面只须要放入内存一次,全部的应用程序均可以共享它的页面。
有两种加载动态连接库的方式:隐式连接和显式加载。
应用程序若是想访问某个DLL中的函数,该函数必须是已经被导出的函数。为了让DLL导出一些函数,须要在每个将要被导出的函数前面添加标志符_declspec(dllexport)。
客户调用DLL的导出函数时,必须先申明该函数是外部的extern。除了使用extern关键字代表函数是外部定义的以外,还可使用_declspec(dllimport)来代表函数是从动态连接库中引入的。使用_declspec(dllimport)声明外部函数,可以明确告诉编译器函数是从动态连接库中引入的,编译器能够生成运行效率更高的代码。
一般在编写动态连接库时,都会提供一个头文件,在此文件中提供DLL导出函数原型的声明,以及函数的有关注释文档。程序编译时,头文件不参与编译,源文件单独编译。
dll_1.h文件
#ifdef DLL1_API #else #define DLL1_API _declspec(dllimport) #endif DLL1_API int add(int a, int b); DLL1_API int subtract(int a, int b);
dll_1.cpp文件
#define DLL1_API _declspec(dllexport) #include "dll_1.h" DLL1_API int add(int a, int b) { return a + b; } DLL1_API int subtract(int a, int b) { return a - b; }
动态连接库还能够导出C++类。
dll_1.h文件
#ifdef DLL1_API #else #define DLL1_API _declspec(dllimport) #endif DLL1_API int add(int a, int b); DLL1_API int subtract(int a, int b); class DLL1_API point { public: void Output(int x, int y); };
dll_1.cpp文件
#define DLL1_API _declspec(dllexport) #include "dll_1.h" DLL1_API int add(int a, int b) { return a + b; } DLL1_API int subtract(int a, int b) { return a - b; } void point::Output(int x, int y) { HWND hwnd = GetForegroundWindow(); HDC hdc = GetDC(hwnd); TCHAR buf[20] = { '\0' }; swprintf(buf, 20, _T("x = %d, y = %d"), x, y); TextOut(hdc, 0, 0, buf, wcslen(buf)); ReleaseDC(hwnd, hdc); }
若是隐式连接加载DLL,.dll更新后,须要将新的.dll和.lib文件复制到相应工程目录。
动态连接库还能够不导出整个C++类,而只导出C++类的某些函数。
dll_1.h文件
class/* DLL1_API*/ point { public: void DLL1_API Output(int x, int y); void test(); };
若是声明类时,指定了导出标志,该类中的全部函数都将被导出,不然只有那些声明时指定了导出标志的类成员函数才被导出。
名字改编问题
C++编译器生成DLL时,会对导出函数的名字进行改编,不一样编译器使用的改编规则不一样,改编后的名字是不同的。若是利用不一样的编译器分别生成DLL和访问DLL的客户端程序,客户端程序访问DLL的导出函数时就会出现问题。所以,但愿动态连接库文件编译时,导出函数的名称不要发生改变。作法是,定义导出函数时,加上限定符extern “C”,字母C大写。
dll_1.h文件
#ifdef DLL1_API #else #define DLL1_API extern "C" _declspec(dllimport) #endif DLL1_API int add(int a, int b); DLL1_API int subtract(int a, int b);
dll_1.cpp文件
#define DLL1_API extern "C" _declspec(dllexport) #include "dll_1.h" DLL1_API int add(int a, int b) { return a + b; } DLL1_API int subtract(int a, int b) { return a - b; }
extern “C”能够解决C++和C语言之间相互调用时函数命名的问题,但该方法不能用于导出一个类的成员函数,只能用于导出全局函数。若是导出函数的调用约定发生改变,即便使用限定符extern “C”,函数的名字仍会发生改变。
显示加载方式加载DLL
LoadLibrary的做用是将指定的可执行模块映射到调用进程的地址空间,返回值是加载模块的句柄。GetProcAddress函数用来得到导出函数的地址。动态加载DLL时,客户端再也不须要包含导出函数声明的头文件和引入库文件,只须要.dll文件便可。
void CMFCApplication1Dlg::OnBnClickedBtnSub() { HMODULE HInst = LoadLibrary(_T("ConsoleApplication1.dll")); if (HInst == NULL) return; typedef int (*ADDPROC)(int a, int b); ADDPROC Add = (ADDPROC)GetProcAddress(HInst, "add"); if (!Add) { MessageBox(_T("获取函数地址失败!")); return; } CString str; str.Format(_T("5 + 3 = %d."), Add(5, 3)); MessageBox(str); }
实际上采用隐式连接方式访问DLL时,程序启动时也是经过调用LoadLibrary函数加载该进程的动态连接库的。
若是采用动态加载方式使用DLL,访问DLL的客户端程序,若是对DLL的访问已经完成,再也不须要访问该DLL时,应调用FreeLibrary函数释放该DLL。FreeLibrary主要减小被加载的DLL的引用计数。当计数变为0时,该DLL模块将从调用进程的地址空间卸载。
DllMain函数
DLL的入口函数是DllMain,该函数是可选的。编写DLL程序时,能够提供也能够不提供DllMain函数。若是提供了DllMain函数,系统加载该DLL时,就会调用该函数。
不该该在DllMain函数中进行复杂的调用,由于加载该动态连接库时,可能还有一些核心动态连接库没有被加载。若是本身编写的DllMain函数须要调用核心动态连接库中的某些函数,就会致使程序终止。
参考资料:
一、《VC++深刻详解》