21.1 关于库的基础知识
(1)两种LIB库——都是链接时才用,链接完就不再使用了。属开发期的产物。
LIB库 |
区别 |
对象库 (也叫静态链接库) |
①是多个obj文件能过Lib.exe组合成一个.lib文件。包含了实际执行代码、符号表等。链接时被加到exe文件中。 ②只需要使用#pragma comment(lib,"XXXX.lib")或在编译器中设置相关选项。并包含头文件到进相应的文件中。 |
导入库 (导入动态库时使用) |
①只包含函数的地址和符号表等,确保程序静态载入动态链接库。②引入方式与静态链接库一样,只需要使用#pragma comment(lib,"XXXX.lib")或在编译器中设置相关选项,并按照头文件函数接口的声明调用函数就可以了。 ③如果是动态载入,则不需要该导入库了。但要通过LoadLibrary调入DLL文件,并手工调用GetProcAddress获得对应函数。 |
(2)静态链接库和动态链接库的特点
名称 |
特点 |
|
静态链接库 |
缺点 |
①多个程序使用相同库函数时,要存多份相同的代码到各个exe中,显然浪费空间。 ②如果某个函数有错或更新算法,则所有用到此函数的exe要重新编译一遍,升级麻烦。 ③多个exe运行时,要载入相同的代码,浪费内存。 |
优点 |
①仅在链接时使用,链接完后,可执行文件可脱离库文件单独存在 ②代码的访问速度快 |
|
动态链接库 |
特点 |
①程序运行时被载入,且内存中只保留一份代码,这份代码是通过分页机制被映射到不同进程的地址空间。但数据段仍是多份的,会被映射到不同的物理内存中,有多少个程序,就会产生多少份的数据段 ②动态链接库与可执行文件的不同,仅在文件头的属性位不同而己,exe文件的一些特征,动态链接库中也有。如动态链接库也可以使用各种资源。 ③动态链接库是被映射到其他应用程序的地址空间的,与应用程序是“一体”的。它所拥有的资源也可被应用程序使用。它的任何操作都代表应用程序进行,当在库中打开文件、分配内存和创建窗口,这些都为应用程序所拥的。 ④不能独立于应用程序而单独运行。 |
(3)库的入口点和退出点
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
fdwReason |
含义 |
DLL_PROCESS_ATTACH |
①当动态链接链被映射到程序进程的地址空间时,相当于初始化信号 ②在进程的整个生命周期内,只会用这个参数调用一次。如果以后的线程通过调用LoadLibraryEx函数,只会递增DLL的使用计数,而不再用这个参数去调用DllMain。 ③返回值TRUE,表明初始化成功。返回FALSE,系统会终止整个进程的运行。 ④hInstance为动态链接库的模块实例句柄(注意不是“宿主”的实例句柄),获得该句柄的唯一途径是在入口函数被调用时,保存这个参数,否则运行时没有其他方法可获取了。 |
DLL_PROCESS_DETACH |
①表示动态链接库将被卸载,给库提供一个自清理的机会 ②同样,进程的整个生命期内,只会用这个参数调用函数一次。 ③注意,DLL能够阻止进程的终止。例如,当收到DLL_PROCESS_DETACH时,让程序进入一个无限循环。只有当每个DLL都处理完该通知后,操作系统才会中止进程。 |
DLL_THREAD_ATTACH |
①表示应用程序创建了一个新的线程,系统会向当前进程中的所有DLL发送该通知。只有系统处理后这个通知,系统才允许新线程开始执行它的线程函数。 ②如果程序频繁的创建创建和结束线程,会以该参数和DLL_THREAD_DETACH频繁地调用该函数。 ③系统只会新的线程调用该函数,如果系统己经存的线程则不会再次收到该通知。 ④主线程比较特殊,他不会调用以该参数去调用DllMain。 |
DLL_THREAD_DETACH |
①线程将要结束时,会以该参数调用DllMain。注意,此时的线程还没结束,甚到还可以发送线程消息。但不应该再使用PostMessage,因为该线程可能在消息被收取取之前就消失了。 ②如果调用TerminateThread终止线程时,那么不会收到该通知。 ③如果DLL被撤消时,仍有线程在运行,那么就不为任何线程调用该函数。 ④该通知能阻止线程的中止 |
(4)动态链接链的调用
①静态调用:
#include "..\EdrLib\EdrLib.h" //包含动态库的头文件 #pragma comment(lib,"..\Debug\EdrLib.lib") //告诉编译器DLL相对应的lib文件所在路径和文件名
★特点:使用方便,像使用自己内部函数一样的使用DLL中的函数。但装入DLL过程中任何错误,应用程序没有任何机会完成应变的措施,因为它根本没来得及被装入执行。
②动态调用
//注意,这里可以不包含动态库的头文件 typedef int (* lpAddFun)(int ,int);//定义一个动态库里的AddNew的函数指针。 lpAddFun addFun; //函数指针 hDll = LoadLibrary("EdrLib.dll");//动态加载DLL模块句柄 addFun = (lpAddFun)GetProcAddress(hDll, "AddNew");//得到函数的地址 …… addFun(2,3); …… if(hDll) FreeLibrary(hDll);//释放已经加载的DLL模块。
★特点:因为事先不需要知道动态库文件名和函数名,所以也就不再需要根据导入库中的dll文件名和函数名写入exe文件头的导入表中,所以就不再需要#include动态库的头文件和.lib导入库。需要使用动态库时才由应用程序通过LoadLibrary装入,如果动态库不存在会返回NULL,这时错误是可控的。
21.2 动态链接库导出函数名称问题
(1)DLL导出函数名称的关系图
(2)用模块定义文件导出CC++文件中的函数——可让编译器不改动函数名。
①新建模块定义文件,内容如下:(文件名如“EdrLib.def”)
;模块定义文件里的注释是用分号的 LIBRARY EdrLib ;EdrLib为动态链接库的名称 EXPORTS EdrCenterTextA @1 ;@1,为函数的编序号 EdrCenterTextW @2
②设置链接时依赖文件
方法:“项目属性”→“配置属性”→“链接器”→“输入”→“模块定义文件”→输入“EdrLib.def”(不包含引号)
(3)VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调用方式。在 DLL 导出函数中,为了跟 Windows API 保持一致,一般使用 __stdcall方式。如果调用约定仍采用C默认的方式不变,则按模块定义文件或_declspec(dllexport)导出都能使C文件(注意,不是C++文件)的函数名保持不变。但如果调用约定发生改变时,即使使用extern "C",编译后的函数名还是会发生改变。例如上面我们加入_stdcall关键字说明调用约定。
【EdrTest程序】
EdrLib.dll链接库
//Edrlib.h
/*-------------------------------------------------------- EDRLIB.H header file --------------------------------------------------------*/ #pragma once; #include <windows.h> #ifdef _cplusplus #ifdef API_EXPORT #define EXPORT extern "C" __declspec(dllexport) //当头文件供动态库本身使用时 #else #define EXPORT extern "C" __declspec(dllimport) //当头文件供调用库的程序使用时 #endif #else #ifdef API_EXPORT #define EXPORT __declspec(dllexport) //当头文件供动态库本身使用时 #else #define EXPORT __declspec(dllimport) //当头文件供调用库的程序使用时 #endif #endif EXPORT BOOL CALLBACK EdrCenterTextA(HDC, PRECT, PCSTR); EXPORT BOOL CALLBACK EdrCenterTextW(HDC, PRECT, PCWSTR); #ifdef UNICODE #define EdrCenterText EdrCenterTextW #else #define EdrCenterText EdrCenterTextA #endif
//EdrLib.c
/*-------------------------------------------------------- EDRLIB.C —— Easy Drawing Routine Library module (c) Charles Petzold,1998 --------------------------------------------------------*/ #include <Windows.h> #define API_EXPORT #include "EdrLib.h" //入口和退出点 int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { return TRUE; } EXPORT BOOL CALLBACK EdrCenterTextA(HDC hdc, PRECT prc, PCSTR pString) { int iLength; SIZE size; iLength = lstrlenA(pString); GetTextExtentPoint32A(hdc, pString, iLength, &size); return TextOutA(hdc, (prc->right - prc->left - size.cx) / 2, (prc->bottom - prc->top - size.cy) / 2, pString,iLength); } EXPORT BOOL CALLBACK EdrCenterTextW(HDC hdc, PRECT prc, PCWSTR pString) { int iLength; SIZE size; iLength = lstrlenW(pString); GetTextExtentPoint32W(hdc, pString, iLength, &size); return TextOutW(hdc, (prc->right - prc->left - size.cx) / 2, (prc->bottom - prc->top - size.cy) / 2, pString, iLength); }
//EdrTest测试文件
/*------------------------------------------------------------ EDRTEST.C -- Program using EDRLIB dynamic-link library (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> //-----------------------静态调用(隐式链接)------------------- //#include "..\EdrLib\EdrLib.h" //#pragma comment(lib,"..\Debug\EdrLib.lib") //动态链接库的导入库 //-----------------------动态调用(显式链接)-------------------- #ifdef UNICODE typedef BOOL (CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCWSTR); #define EdrCenterText "EdrCenterTextW" #else typedef BOOL (CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCSTR); #define EdrCenterText "EdrCenterTextA" #endif //--------------------------------------------------------- LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("StrProg") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, // window class name TEXT ("DLL Demonstration Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; static HANDLE hDll; //供动态调用DLL使用 static PEDRCENTERTEXT pEdrCenterText; //供动态调用DLL使用 switch (message) { case WM_CREATE: //动态调用(显式链接) hDll = LoadLibrary(TEXT("EdrLib.dll")); if (hDll) pEdrCenterText = (PEDRCENTERTEXT)GetProcAddress(hDll, EdrCenterText); return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; //静态调用(隐式链接) //EdrCenterText(hdc, &rect, TEXT("This string was displayed by a DLL")); //动态调用(显式链接) pEdrCenterText(hdc, &rect, TEXT("This string was displayed by a DLL")); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hDll) FreeLibrary(hDll); PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
21.3 在DLL中共享内存
(1)动态链接库存在于不同进程的地址空间,它们代表“宿主”程序工作,其代码段会被不同进程共享,但数据段会被映射到不同的物理内存,也就是这些数据是不能共享的,但有时我们需要数据在程序间共享,怎么办?
(2)创建数据共享区的步骤。
①声明并创建的一个数据区——表现在dll文件中会出现一个自定义的myshared数据区
#pragma data_seg(“myshared”) int iTotal =0; //一定要初始化,否则被编译到未初始化区,而不是myshared区 WCHAR szString[MAX_STRING]={‘0’}; //一定要初始化。 #pragma data_seg(); //数据区定义结束
②将数据区设为可读、可写和可共享的属性
#pragma comment(linker, "/SECTION:myshared,RWS")
【StrProg程序】
效果图
//DLL动态链接库代码
/*------------------------------------------------------ STRLIB.H header file -------------------------------------------------------*/ #pragma once #include <Windows.h> #ifdef __cplusplus #ifdef API_EXPORT #define EXPORT extern "C" __declspec(dllexport) #else #define EXPORT extern "C" __declspec(dllimport) #endif #else #ifdef API_EXPORT #define EXPORT __declspec(dllexport) #else #define EXPORT __declspec(dllimport) #endif #endif //字符串的最大行数和字符长度 #define MAX_STRINGS 256 #define MAX_LENGTH 63 //用于显示字符串的函数(回调函数,由调用者决定如何显示) /* 函数指针名称:GETSTRCB,其中CB表示CALLBACK 第1个参数:为指向要显示的字符串的指针 第2个参数:指定自定义的数据结构的指针,用来决定如何显示或处理字符串 */ typedef BOOL(CALLBACK* GETSTRCB)(PCTSTR, PVOID); //每个函数都有两个版本:ANSI和UNICODE EXPORT BOOL CALLBACK AddStringA(PCSTR); EXPORT BOOL CALLBACK AddStringW(PCWSTR); EXPORT BOOL CALLBACK DeleteStringA(PCSTR); EXPORT BOOL CALLBACK DeleteStringW(PCWSTR); EXPORT int CALLBACK GetStringA(GETSTRCB,PVOID); EXPORT int CALLBACK GetStringW(GETSTRCB, PVOID); //根据UNICODE标识符的情况来使用相应版本的函数 #ifdef UNICODE #define AddString AddStringW #define DeleteString DeleteStringW #define GetStrings GetStringW #else #define AddString AddStringA #define DeleteString DeleteStringA #define GetStrings GetStringA #endif
//StrLib.c
/*----------------------------------------------------------- STRLIB.C —— Library module for STRPPROG program (c)Charles Petzold,1998 -----------------------------------------------------------*/ #define API_EXPORT #include <Windows.h> #include "StrLib.h" //创建DLL共享内存,会在DLL文件中创新一个新的数据区,名为"myshared"区,就像 //一般的文件有代码区、常量区等。 //(需要链接选项: /SECTION:myshared,RWS) #pragma data_seg(".myshared") //共享数据区定义开始 int iTotal = 0; //必须初始化,否则分配在未初始化数据区中,该变量用于存在当前的字符串(串数)(不是每个字符串的字符个数) WCHAR szStrings[MAX_STRINGS][MAX_LENGTH + 1] = { '