📄 21. 动态链接库.txt
字号:
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) ;
// Register message for notifying instances of data changes
iDataChangeMsg = RegisterWindowMessage (TEXT ("StrProgDataChange")) ;
return 0 ;
case WM_COMMAND:
switch (wParam)
{
case IDM_ENTER:
if (DialogBox (hInst, TEXT ("EnterDlg"), hwnd, &DlgProc))
{
if (AddString (szString))
PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ;
else
MessageBeep (0) ;
}
break ;
case IDM_DELETE:
if (DialogBox (hInst, TEXT ("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 ((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) ;
}
STRPROG.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
ENTERDLG DIALOG DISCARDABLE 20, 20, 186, 47
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Enter"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "&Enter:",IDC_STATIC,7,7,26,9
EDITTEXT IDC_STRING,31,7,148,12,ES_AUTOHSCROLL
DEFPUSHBUTTON "OK",IDOK,32,26,50,14
PUSHBUTTON "Cancel",IDCANCEL,104,26,50,14
END
DELETEDLG DIALOG DISCARDABLE 20, 20, 186, 47
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Delete"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "&Delete:",IDC_STATIC,7,7,26,9
EDITTEXT IDC_STRING,31,7,148,12,ES_AUTOHSCROLL
DEFPUSHBUTTON "OK",IDOK,32,26,50,14
PUSHBUTTON "Cancel",IDCANCEL,104,26,50,14
END
/////////////////////////////////////////////////////////////////////////////
// Menu
STRPROG MENU DISCARDABLE
BEGIN
MENUITEM "&Enter!", IDM_ENTER
MENUITEM "&Delete!", IDM_DELETE
END
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by StrProg.rc
#define IDC_STRING 1000
#define IDM_ENTER 40001
#define IDM_DELETE 40002
#define IDC_STATIC -1
STRPROG.C包含STRLIB.H表头文件,其中定义了STRPROG将使用的STRLIB中的三个函数。
当您执行STRPROG的多个执行实体的时候,本程序的奥妙之处就会显露出来。STRLIB将在共享内存中储存字符串及其指针,并允许STRPROG中的所有执行实体共享此数据。让我们看一下它是如何执行的吧。
在STRPROG执行实体之间共享数据
Windows在一个Win32程序的地址空间周围筑了一道墙。通常,一个程序的地址空间中的数据是私有的,对别的程序而言是不可见的。但是执行STRPROG的多个执行实体表示了STRLIB在程序的所有执行实体之间共享数据是毫无问题的。当您在一个STRPROG窗口中增加或者删除一个字符串时,这种改变将立即反映在其它的窗口中。
在全部例程之间,STRLIB共享两个变量:一个字符数组和一个整数(记录已储存的有效字符串的个数)。STRLIB将这两个变量储存在共享的一个特殊内存区段中:
#pragma data_seg ("shared")
int iTotal = 0 ;
WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { '\0' } ;
#pragma data_seg ()
第一个#pragma叙述建立数据段,这里命名为shared。您可以将这段命名为任何一个您喜欢的名字。在这里的#pragma叙述之后的所有初始化了的变量都放在shared数据段中。第二个#pragma叙述标示段的结束。对变量进行专门的初始化是很重要的,否则编译器将把它们放在普通的未初始化数据段中而不是放在shared中。
连结器必须知道有一个「shared」共享数据段。在「Project Settings」对话框选择「Link」页面卷标。选中「STRLIB」时在「Project Options」字段(在Release和Debug设定中均可),包含下面的连结叙述:
/SECTION:shared,RWS
字母RWS表示段具有读、写和共享属性。或者,您也可以直接用DLL原始码指定连结选项,就像我们在STRLIB.C那样:
#pragma comment(linker,"/SECTION:shared,RWS")
共享的内存段允许iTotal变量和szStrings字符串数组在STRLIB的所有例程之间共享。因为MAX_STRINGS等于256,而MAX_LENGTH等于63,所以,共享内存段的长度为32,772字节-iTotal变量需要4字节,256个指针中的每一个都需要128字节。
使用共享内存段可能是在多个应用程序间共享数据的最简单的方法。如果需要动态配置共享内存空间,您应该查看内存映像文件对象的用法,文件在/Platform SDK/Windows Base Services/Interprocess Communication/File Mapping。
各式各样的 DLL 讨论
如前所述,动态链接库模块不接收消息,但是,动态链接库模块可呼叫GetMessage和PeekMessage。实际上,从消息队列中得到的消息是发给呼叫链接库函数的程序的。一般来说,链接库是替呼叫它的程序工作的,这是一项对链接库所呼叫的大多数Windows函数都适用的规则。
动态链接库可以从链接库文件或者从呼叫链接库的程序文件中加载资源(如图标、字符串和位图)。加载资源的函数需要执行实体句柄。如果链接库使用它自己的执行实体句柄(初始化期间传给链接库的),则链接库能从它自己的文件中获得资源。为了从呼叫程序的.EXE文件中得到资源,程序链接库函数需要呼叫该函数的程序的执行实体句柄。
在链接库中登录窗口类别和建立窗口需要一点技巧。窗口类别结构和CreateWindow呼叫都需要执行实体句柄。尽管在建立窗口类别和窗口时可使用动态链接库模块的执行实体句柄,但在链接库建立窗口时,窗口消息仍会发送到呼叫链接库中程序的消息队列。如果使用者必须在链接库中建立窗口类别和窗口,最好的方法可能是使用呼叫程序的执行实体句柄。
因为模态对话框的消息是在程序的消息循环之外接收到的,因此使用者可以在链接库中呼叫DialogBox来建立模态对话框。执行实体句柄可以是链接库句柄,并且DialogBox的hwndParent参数可以为NULL。
不用输入引用信息的动态链接
除了在第一次把使用者程序加载内存时,由Windows执行动态链接外,程序执行时也可以把程序同动态链接库模块连结到一起。例如,您通常会这样呼叫Rectangle函数:
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
因为程序和GDI32.LIB引用链接库连结,该链接库提供了Rectangle的地址,因此这种方法有效。
您也可以用更迂回的方法呼叫Rectangle。首先用typedef为Rectangle定义一个函数型态:
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) ;
尽管这项执行时期动态链接的技术并没有为Rectangle函数增加多大好处,但它肯定是有用的,如果直到执行时还不知道程序动态链接库模块的名称,这时就需要使用它。
上面的程序代码使用了LoadLibrary和FreeLibrary函数。Windows为所有的动态链接库模块提供「引用计数」,LoadLibrary使引用计数递增。当Windows加载任何使用了链接库的程序时,引用计数也会递增。FreeLibrary使引用计数递减,在使用了链接库的程序执行实体结束时也是如此。当引用计数为零时,Windows将从内存中把链接库删除掉,因为不再需要它了。
纯资源链接库
可由Windows程序或其它链接库使用的动态链接库中的任何函数都必须被输出。然而,DLL也可以不包含任何输出函数。那么,DLL到底包含什么呢?答案是资源。
假设使用者正在使用需要几幅位图的Windows应用程序进行工作。通常要在程序的资源描述文件中列出资源,并用LoadBitmap函数把它们加载内存。但使用者可能希望建立若干套位图,每一套均适用于Windows所使用的不同显示卡。将不同套的位图存放到不同文件中可能是明智的,因为只需要在硬盘上保留一套位图。这些文件就是纯资源文件。
程序21-5说明如何建立包含9幅位图的名为BITLIB.DLL的纯资源链接库文件。BITLIB.RC文件列出了所有独立的位图文件并为每个文件赋予一个序号。为了建立BITLIB.DLL,需要9幅名为BITMAP1.BMP、BITMAP2.BMP等等的位图。您可以使用附带的光盘上提供的位图或者在Visual C++中建立这些位图。它们与ID从1到9相对应。
程序21-5 BITLIB
BITLIB.C
/*--------------------------------------------------------------
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -