第二十一章 动态链接库

多数与Windows相关的磁盘文件如果不是程序模块,就是动态链接程序。

21.1动态链接库的基本知识

动态链接库并不能直接执行,也不接收消息。它们一些独立的文件,其中包含能被程序或其它DLL呼叫来完成一定作业的函数。只有在其它模块呼叫动态链接库中的函数时,它才发挥作用。

在程序开发中,您将各种目标模块(.OBJ)、执行时期链接库(.LIB)文件,以及经常是已编译的资源(.RES)文件连结在一起,以便建立Windows的.EXE文件,这时的连结是「静态连结」。动态链接与此不同,它发生在执行时期。

KERNEL32.DLL 、 USER32.DLL 和 GDI32.DLL、各种驱动程序文件如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和视讯及打印机驱动程序都是动态链接库。这些动态链接库能被所有Windows应用程序使用。

尽管一个动态链接库模块可能有其它扩展名(如.EXE或.FON) ,但标准扩展名是.DLL。如果文件有其它扩展名,则程序必须另外使用LoadLibrary或者LoadLibraryEx函数加载该模块。

链接库:一词多义

目的码链接库是带.LIB扩展名的文件。在使用连结程序进行静态连结时,它的程序代码就会加到程序的.EXE文件中。引用链接库是目的码链接库文件的一种特殊形式。像目的码链接库一样,引用链接库有.LIB扩展名,并且被连结器用来确定程序代码中的函数呼叫来源。但引用链接库不含程序代码,而是为连结程序提供信息,以便在.EXE文件中建立动态链接时要用到的复位位表。包含在Microsoft编译器中的KERNEL32.LIB、USER32.LIB和GDI32.LIB文件是Windows函数的引用链接库。

一个简单的DLL

EDRLIB工程(.h)

#ifdef __cplusplus #define EXPORT extern "C" __declspec(dllexport) #else #define EXPORT __declspec(dllexport) #endif //EdrCenterTextA(ANSI版)和EdrCenterTextW(宽字符版) 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工程(.cpp)

#include <windows.h> #include "edrlib.h" //HINSTANCE:链接库的执行实体句柄 //DllMain的最后一个参数由系统保留 //fdwReason参数可以是四个值之一,说明为什么Windows要呼叫DllMain函数。 //DLL_PROCESS_ATTACH表示动态链接库被映像到一个程序的地址空间 //DLL_THREAD_ATTACH意味着某个程序建立了一个新的线程。 //当线程中止时,Windows以DLL_THREAD_DETACH为fdwReason参数呼叫DllMain。 //当使用一个DLL_THREAD_DETACH参数呼叫DllMain时,线程仍然存在。 int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { return TRUE; } //EXPORT:确保函数名添加到EDRLIB.LIB的一个关键词 //PCSTR 指向const字符串的指针 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); }

链接库入口/出口点

#include <windows.h> #include "edrlib.h" 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, TEXT("DLL Demonstration Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); 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; switch(message) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); EdrCenterText(hdc, &rect, TEXT("This string was displayed by a DLL")); EndPaint(hwnd, &ps); return 0 ; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }

在DLL中共享内存

Windows能够将同时使用同一个动态链接库的应用程序分开.

STRLIB.h

#ifdef __cplusplus #define EXPORT extern "C" __declspec(dllexport) #else #define EXPORT __declspec(dllexport) #endif #define MAX_STRINGS 256 #define MAX_LENGTH 63 typedef BOOL (CALLBACK * GETSTRCB)(PCTSTR, PVOID); EXPORT BOOL CALLBACK AddStringA(PCSTR); EXPORT BOOL CALLBACK AddStringW(PCWSTR); EXPORT BOOL CALLBACK DeleteStringA(PCSTR); EXPORT BOOL CALLBACK DeleteStringW(PCWSTR); EXPORT int CALLBACK GetStringsA(GETSTRCB, PVOID); EXPORT int CALLBACK GetStringsW(GETSTRCB, PVOID); #ifdef UNICODE #define AddString AddStringW #define DeleteString DeleteStringW #define GetStrings GetStringsW #else #define AddString AddStringA #define DeleteString DeleteStringA #define GetStrings GetStringsA #endif

STRLIB.cpp

#include <windows.h> #include <wchar.h> // for wide-character string functions #include "strlib.h" //STRLIB共享两个变量:一个字符数组和一个整数(记录已储存的有效字 //符串的个数) 。STRLIB将这两个变量储存在共享的一个特殊内存区段中 //第一个#pragma叙述建立数据段,这里命名为shared。 #pragma data_seg("shared") int iTotal = 0; WCHAR szStrings[MAX_STRINGS][MAX_LENGTH + 1] = {'/0'}; #pragma data_seg() //字母RWS表示段具有读、写和共享属性 #pragma comment(linker,"/SECTION:shared,RWS") //共享的内存段允许iTotal变量和 //szStrings字符串数组在STRLIB的所有例程之间共享。 //使用共享内存段可能是在多个应用程序间共享数据的最简单的方法。 int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { return TRUE; } EXPORT BOOL CALLBACK AddStringA(PCSTR pStringIn) { BOOL bReturn; int iLength; PWSTR pWideStr; // Convert string to Unicode and call AddStringW iLength = MultiByteToWideChar(CP_ACP, 0, pStringIn, -1, NULL, 0); pWideStr = (PWSTR)malloc(iLength); MultiByteToWideChar(CP_ACP, 0, pStringIn, -1, pWideStr, iLength); bReturn = AddStringW(pWideStr); free (pWideStr); return bReturn; } EXPORT BOOL CALLBACK AddStringW(PCWSTR pStringIn) { PWSTR pString; int i,iLength; if (iTotal == MAX_STRINGS - 1) return FALSE; if ((iLength = wcslen(pStringIn)) == 0) return FALSE; // Allocate memory for storing string, copy it, convert to uppercase pString = (PWSTR)malloc(sizeof (WCHAR) * (1 + iLength)) ; wcscpy(pString, pStringIn); _wcsupr(pString); // Alphabetize the strings for (i = iTotal ; i > 0 ; i--) { if (wcscmp(pString, szStrings[i - 1]) >= 0) break; wcscpy_s(szStrings[i], szStrings[i - 1]); } wcscpy_s(szStrings[i], pString); iTotal++; free (pString); return TRUE; } EXPORT BOOL CALLBACK DeleteStringA(PCSTR pStringIn) { BOOL bReturn; int iLength; PWSTR pWideStr; // Convert string to Unicode and call DeleteStringW iLength = MultiByteToWideChar(CP_ACP, 0, pStringIn, -1, NULL, 0); pWideStr = (PWSTR)malloc(iLength); MultiByteToWideChar(CP_ACP, 0, pStringIn, -1, pWideStr, iLength); bReturn = DeleteStringW (pWideStr); free (pWideStr); return bReturn; } EXPORT BOOL CALLBACK DeleteStringW (PCWSTR pStringIn) { int i, j ; if (0 == wcslen(pStringIn)) return FALSE; for (i = 0 ; i < iTotal ; i++) { if (_wcsicmp(szStrings[i], pStringIn) == 0) break ; } // If given string not in list, return without taking action if (i == iTotal) return FALSE; // Else adjust list downward for (j = i ; j < iTotal ; j++) wcscpy_s(szStrings[j], szStrings[j + 1]) ; szStrings[iTotal--][0] = '/0'; return TRUE; } EXPORT int CALLBACK GetStringsA(GETSTRCB pfnGetStrCallBack, PVOID pParam) { BOOL bReturn; int i, iLength; PSTR pAnsiStr; for (i = 0 ; i < iTotal ; i++) { // Convert string from Unicode iLength = WideCharToMultiByte(CP_ACP, 0, szStrings[i], -1, NULL, 0, NULL, NULL); pAnsiStr = (PSTR)malloc(iLength); WideCharToMultiByte(CP_ACP, 0, szStrings[i], -1, pAnsiStr, iLength, NULL, NULL); // Call callback function bReturn = pfnGetStrCallBack((PCTSTR)pAnsiStr, pParam); if (bReturn == FALSE) return i + 1; free(pAnsiStr); } return iTotal; } EXPORT int CALLBACK GetStringsW(GETSTRCB pfnGetStrCallBack, PVOID pParam) { BOOL bReturn; int i; for (i = 0 ; i < iTotal ; i++) { bReturn = pfnGetStrCallBack(szStrings[i], pParam); if (bReturn == FALSE) return i + 1; } return iTotal; }  

STRPROG.cpp

#include <windows.h> //STRLIB是一个动态链接库模块,它储存并排序了最多256个字符串。 //在STRLIB中,这些字符串均为大写,并由共享内存维护。 //利用STRLIB的三个函数,STRPROG能够添加字符 //串、删除字符串以及从STRLIB获得目前的所有字符串。 #include "../STRLIB/strlib.h" #include "resource.h" typedef struct { HDC hdc; int xText; int yText; int xStart; int yStart; int xIncr; int yIncr; int xMax; int yMax; }CBPARAM; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); TCHAR szAppName [] = TEXT ("StrProg"); TCHAR szString [MAX_LENGTH + 1] ; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { 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 = szAppName; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0; } HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)); hwnd = CreateWindow(szAppName, TEXT("DLL Demonstration Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: SendDlgItemMessage(hDlg, IDC_STRING, EM_LIMITTEXT, MAX_LENGTH, 0); return TRUE; case WM_COMMAND: switch (wParam) { case IDOK: GetDlgItemText(hDlg, IDC_STRING, szString, MAX_LENGTH); EndDialog(hDlg, TRUE); return TRUE; case IDCANCEL: EndDialog(hDlg, FALSE); return TRUE; } } return FALSE; } BOOL CALLBACK GetStrCallBack(PTSTR pString, CBPARAM * pcbp) { TextOut(pcbp->hdc, pcbp->xText, pcbp->yText, pString, lstrlen(pString)); if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax) { pcbp->yText = pcbp->yStart; if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax) return FALSE; } return TRUE; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HINSTANCE hInst; static int cxChar, cyChar, cxClient, cyClient; static UINT iDataChangeMsg; CBPARAM cbparam; HDC hdc; PAINTSTRUCT ps; TEXTMETRIC tm; switch (message) { case WM_CREATE: hInst = ((LPCREATESTRUCT)lParam)->hInstance; hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm); cxChar = (int)tm.tmAveCharWidth; cyChar = (int)(tm.tmHeight + tm.tmExternalLeading); ReleaseDC(hwnd, hdc); // 用户必须通过调用RegisterWindowMessage 函数来注册一个消息句柄。 // RegisterWindowMessage用于进程之间的通讯 iDataChangeMsg = RegisterWindowMessage(TEXT("StrProgDataChange")); return 0; case WM_COMMAND: //PostMessage: //该函数将一个消息放入(寄送)到与指定窗口创建的线程相联系消息队列里, //不等待线程处理消息就返回,是异步消息模式。 //消息队列里的消息通过调用GetMessage和PeekMessage取得。 switch(wParam) { //这两个菜单项将启动不同的对话框来添加或删除字符串。 case IDM_ENTER: if(DialogBox(hInst, MAKEINTRESOURCE(ENTERDLG), hwnd, &DlgProc)) { if (AddString(szString)) PostMessage(HWND_BROADCAST, iDataChangeMsg, 0, 0); else MessageBeep(0); } break; //HWND_BROADCAST:消息被寄送到系统的所有顶层窗口 //包括无效或不可见的非自身拥有的窗口、 //被覆盖的窗口和弹出式窗口。消息不被寄送到子窗口。 case IDM_DELETE: if(DialogBox(hInst, MAKEINTRESOURCE(DELETEDLG), hwnd, &DlgProc)) { if (DeleteString(szString)) PostMessage(HWND_BROADCAST, iDataChangeMsg, 0, 0); else MessageBeep(0); } break ; } return 0 ; case WM_SIZE: cxClient = (int)LOWORD(lParam); cyClient = (int)HIWORD(lParam); return 0 ; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); cbparam.hdc = hdc ; cbparam.xText= cbparam.xStart = cxChar; cbparam.yText= cbparam.yStart = cyChar; cbparam.xIncr= cxChar * MAX_LENGTH ; cbparam.yIncr= cyChar ; cbparam.xMax = cbparam.xIncr * (1 + cxClient / cbparam.xIncr); cbparam.yMax = cyChar * (cyClient / cyChar - 1); //呼叫GetStrings并使用函数GetStrCallBack来列出所列 //举的字符串。 GetStrings((GETSTRCB)GetStrCallBack, (PVOID) &cbparam); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: if (message == iDataChangeMsg) InvalidateRect(hwnd, NULL, TRUE); break; } return DefWindowProc(hwnd, message, wParam, lParam); }  

 

21.2各式各样的 DLL 讨论

动态链接库模块不接收消息,但是,动态链接库模块可呼叫GetMessage和PeekMessage。

在链接库中登录窗口类别和建立窗口需要一点技巧。窗口类别结构和CreateWindow呼叫都需要执行实体句柄。如果使用必须在链接库中建立窗口类别和窗口,最好的方法可能是使用呼叫程序的执行实体句柄。

因为模态对话框的消息是在程序的消息循环之外接收到的,因此使用者可以在链接库中呼叫DialogBox来建立模态对话框。执行实体句柄可以是链接库句柄,并且DialogBox的hwndParent参数可以为NULL。

不用输入引用信息的动态链接 

程序执行时也可以把程序同动态链接库模块连结到一起。

typedef BOOL (WINAPI * PFNRECT)(HDC, int, int, int, int); //然后定义两个变量: HANDLE hLibrary; PFNRECT pfnRectangle; //现在将hLibrary设定为链接库句柄, //将lpfnRectangle设定为Rectangle函数的地址: hLibrary = LoadLibrary(TEXT("GDI32.DLL")); pfnRectangle = (PFNPRECT)GetProcAddress(hLibrary,TEXT("Rectangle")); //如果找不到链接库文件或者发生其它一些错误, //LoadLibrary函数传回NULL。 //现在您可以呼叫函数然后释放链接库: pfnRectangle(hdc, xLeft, yTop, xRight, yBottom); FreeLibrary(hLibrary); /* 如果直到执行时还不知道程序动态链接库模块的名称, 这时就需要使用上面的方法。 */

Windows为所有的动态链接库模块提供「引用计数」 ,LoadLibrary使引用计数递增。当Windows加载任何使用了链接库的程序时,引用计数也会递增。

纯资源链接库

由Windows程序或其它链接库使用的动态链接库中的任何函数都必须被输出。 然而,DLL也可以不包含任何输出函数。DLL还可以包含资源透过链接库句柄和位图号码来呼叫LoadBitmap,从而得到一个位图句柄:

hBitmap = LoadBitmap(hLibrary, MAKEINTRESOURCE(iCurrent)); //号码iCurrent对应的位图就可以载入了

DLL载入方式的比较:

■dll静态加载法:lib是dll的导入库,这个方法很简单,但是有2个缺点:1程序一开始运行就需要载入整个dll,无法载入程序就不能开始运行;2由于载入的是整个dll,需要耗费资源。

■动态加载法:它不在程序运行时候载入dll,不需要lib导入,使用程序函数LoadLibrary载入库,并使用API获得dll中的函数入口地址。相对的,它只导入需要的函数,资源节俭,程序启动无需载入dll,效率高,即使dll没有,程序也可以正常启动(但是执行到需要dll部分就over了)问题是,它的操作方式比较麻烦,从载入、获得函数入口、释放资源,全部需要手动控制。

 

你可能感兴趣的:(windows,String,null,dll,callback,winapi)