动态链接库(也称为DLL、动态库或库模块)是Microsoft Windows最重要的组成要素之一。大多数与Windows相关的磁盘文件要么是程序模块,要么是动态链接库模块。迄今为止,我们都是在编写Windows应用程序,现在是尝试编写动态链接库的时候了。许多您已经学会的编写应用程序的规则同样适用于编写这些库模块,但也有一些重要的不同。
动态链接库基础知识
库的基本知识
正如前面所看到的,Windows应用程序是一个可执行文件,它通常创建一个或几个窗口,并使用消息循环接收用户输人。通常,动态链接库井不能直接执行,也不接收消息。它们是一些独立的文件,其中包含能被程序或其他DLL调用来完成一定作业的函数。只有在其他模块调用动态链接库中的函数时,它才发挥作用。
所谓“动态链接”,是指Windows把一个模块中的函数调用链接到库模块中的实际函数上的过程。在程序开发中,将各种目标模块(.OBJ)、运行库(.LIB)文件,以及经常是已编译的资源(.RES)文件链接在一起,以便创建 Windows的.EXE文件,这时的链接是“静态链接”。动态链接与此不同,它发生在运行时刻。
KERNEL32、DLL、USER32.DLL和GDI32.DLL,各种驱动程序文件如KEYBOARD,DRV、SYSTEM.DRV和MOUSE.DRV.以及视频和打印机驱动程序都是动态链接库。这些库都被所有Windows应用程序使用。
有些动态链接库(如字体文件等)被称为“纯资源”。它们只包含数据(通常是资源的形式),而不包含代码。自此可见,动态链接库的目的之一就是提供能被许多不同的应用程序使用的函数和资源。在常规的操作系统中,只有操作系统本身才包含其他应用程序能够调用来完成某一作业的例程。在Windows中.一个模块调用另一个模块函数的过程被推广了。事实上编写一个动态链接库,也就是在扩充Windows。当然,也可认为动态链接库(包括构成Windows的那些库例程)是对用户程序的扩充。
尽管一个动态链接库模块可能有其他扩展名(如.EXE或.FON),但标准扩展名是DLL。只有带DLL扩展名的动态链接库才能被Windows自动加载。如果文件有其他扩展名,则程序必须显式地使用LoadLibrary或者LoadLibraryEx函数来加载该模块。
通常您会发现,动态链接库在大型应用程序中最有意义。例如,假设要为Windows编写一个由几个不同的程序组成的大型财务软件包.就会发现这些应角程序会使用许多共同的例程。可以把这些公共例程放入一个常规的目标库(带.LIB扩展名)中,并在使用LINK静态链接时把它们添加到各程序模块中。但这种方法是很浪费的,因为软件包中的每个程序都包台与公共例程相同的代码。而且,如果修改了库中的某个例程,就要重新链接使用此倒程的所有程序。然而,如果把这些公共例程放到被称为ACCOUNT.DLL的动态链接库中,就可以解决这两个问题。只有库模块才包含所有程序都要用到的例程。这样能为存储文件节省磁盘空间,在同时运行多个应用程序时节省内存,并且可以修改库模块,而不用重新链接各个程序。
动态链接实际上是一种可以独立自存的产品。例如,假设您编写了一系列三维绘图例程,并把它们放入名为GDI3.dll的Dll中。如果其他软件开发者对此库很感兴趣,您就可以授权他们将其加^他们的图形程序中。使用多个这样的图形程序的用户只需要一个GDI3.DLL文件。
库:一词多义
关于动态链接库所存在的模糊认识,部分原因是由于“库”这个词被用在几种不同的上下文中。除了动态链接库之外,我们也用它来称呼“目标库”或“输入库”。
目标库是带.lib扩展名的文件。在用链接程序进行静态链接时,它的代码就会加到程序的.EXE文件中。例如,在Microsoft Visual C++中,与程 序链接的常规C运行目标库被称为LIBC.LIB。
输入库是目标库文件的一种特殊形式。像目标库一样,输入库有.LIB扩展名,并且被链接程序用来确定源程序代码中的函数调用。但输入库不含代码,而是为链接程序提供信息,以便在EXE文件中建立动志链接时要用到的重定位表。包含在Microsoft编译器中的KERNEL32.LIB、USER32.LIB和GDI32.LIB文件是Windows函数的输入库。如果一个程序调用Rectangle函数,那么Rectangle将告诉LINK,该函数在GDI32.DLL动态链接库中。该信息被记录在.EXE文件中,使得程序运行时,Windows能够和GDI32DLL动态链接库进行动态链接。
目标库和输入库只用在程序开发期间,而动态链接库在运行期间使用。当一个使用动态链接库的程序运行时,该动态链接库必须在磁盘上。当Windows需要运行一个使用了动态链接库的程序而需要如载该时,库文件必须存储在含有该.EXE程序的目录下,或者是在当前的目录下,或Windows系统目录下,或Windows目录下,或者是在通过MS-DOS环境中的PATH可以访问到的目录下(将此顺序搜索这目录)。
一个简单的DIL
虽然动态链接库的整体概念是它们可以被多个应用程序所使用,但您通常最初设计的动态链接库只与一个应用程序相联系,可能是一个text程序在使用DLL。
下面就是我们要做的。我们创建一个名为“EDRLIB。DLL”的DLL。文件名中的“EDR”代表“简便的绘图例程(easy drawing routines)”。这里的EDRLIB只含有一个函数(名称为EdrCenterText),但是您还可以将应用程序中其他简单的绘图函数添加进去。应用程序EDRTEST.EXE将通过调用EDRLIB.DLL中的函数来利用它。
要做到这一点,需要与我们以前所做的略有不同的方法,也包括Visual C++ 中我们没有介绍过的特性。在Visual C++中“工作空间(worksdaces)”和“项目(projects)”不同。项目通常与创建的应用程序(.EXE)或者动态链很接库(.DLL)相联系。一个工作空间可以包含一个或多个项目。迄今为止,我们所有的工作空间都只包含一个项目。我们现在就创建一个包含两个项目的工作空间EDRTEST----一个用于创建EDRTEST.EXE,而另一个用于创建EDRLIB.DLL,即EDRTEST使用的动态链接库。现在就开始。在Visual C++中,从地File菜单选择New,然后选择Workspaces选项卡。(我们以前从来没有选择过。)在Location域选择工作空间要保存的目录,然后Workspace Name域输入EDRTEST,按Enter键。
这样就创建了一个空的工作空间。Developer Studio还创建了一个名为EDRTEST的子目录,以及工作空间文件EDRTEST。DSW(以及其他文件)。
现在让我们在此工作空间创建一个项目。从File菜单选择New,然后选择Projects选项卡。以前您选择Win32 Application,但现在需要选择Win32 Dynamic-Link Library。另外,单击单选钮Add To Current Workspace,这使得此项目是EDRTEST工作空间的一部分。在Project Name 域输入有EDRLIB,但先不要按炽OK按钮。当您在ProjectName域输入EDRLIB时,Visual C++将改变Location域,以显示EDRLIB作为EDRTEST的一子目录。您不希望如此!在Location域,删除EDRLIB子目录以便项目创建在EDRTEST目录。现在按OK。屏幕将显示一个对话框,询问您创建什么类型的DLL。选择An Empty DLL Project,然后按Finish。Visual C++将创建一个项目文件EDRLIB.DSP和一个构造文件EDRLIB.MAK(如果Tools Options 对话框的Build 选项卡中选中了ExportMakefile选项)。
现在您可以在此项目中加入一些文件。从File菜单选择New,然后选择Files选项卡,选择C/C++Header File,然后输入文件名EDRLIB.H。
再次从File菜单中选择New,然后选择Files选项卡,这次选择C++ Source File,然后输入文件名EDRLIB.C,继续输入程序
EDRLIB.H
/*-----------------------
EDRLIB.H header file
-------------------------*/
#ifdef _cplusplus
#define EXPORT extem "C" _declspec (dllexport)
#else
#define EXPORT _declspec (dllexport)
#endif
EXPORT BOOL CALLBACK EdrCenterTextA(HDC.PRECT.PCSTR):
#ifdef UNICODE
#define EdrCenterText EdrCenterTextW
#else
#define EdrCenterText EdrCenterTextA
#endif
EDRLIB.C
/*---------------------------------------------
EDRLIB.C——Easv Drawing Routine Library module
(c)Chades Petzold,1998
-----------------------------------------------*/
#include <windows.h>
#include "edrlib.h"
int WINAPI DIIMain(HINSTANCE hlnstance,DWORD fdwReason,PVOID pvReserved)
{
return TRUE;
}
EXPORT BOOL CALLBACK EdrCenterTextA(HDC hdc,PRECT prc,PCSTR pString)
{
int iLenglh;
SIZE size;
iLength=IstrlenA(pString);
GetTextExtentPoint32A(hdc,pString,iLength,&size);
return TextOutA(hdc,(prc->right-pro->left-size.cx)/2,
(prc->bottom-prc->top-size.cy)/2,
pString,iLeogth);
GetTextExtentPoint32A(hdc,pString,iLength,&size);
return TextOutA(hdc,(prc->right-pro->left-size.cx)/2,
(prc->bottom-prc->top-size.cy)/2,
pString,iLeogth);
}
EXPORT BOOL CALLBACK EdrCenterTextW(HDC hdc,PRECT prc,PCWSTR pString)
{
int iLength;
SIZE size;
iLength=IstrlenW(pString);
GetTextExtentPoint32W(hdc,pString,iLength,&size);
return TextOutW(hdc,(prc->right-prc->left-size.cx)/2,
(prc->bottom-prc->top-size.cy)/2,
pShing。iLength);
}
这是您可以按Release配转转置,也可以按Debug配置来建立EDRLIB.DLL。之后,RELEASE和DEBUG目录将包含EDRLIB.LIB(即动态链接库的输入库)和EDRLIB.DLL(动态链接库本身)。
纵观全书,我们创建的所有程序都可以根据Unicode标识符来编译成面向Unicode或非Unicode字符串。当您创建一个DLL时,它应该包括处理字符和字符串的Unicode和非Unicode版的所有函数。因此,EDRLIB.C就包含函数EdrCenterTextA(ANSI版)和EdrCenterTextW(宽字符版)。EdrCenterTextA定义带有参数PCSTR(指向const串的指针),而EdrCenlerTextW则定义为带有参数PCWSTR(指向conet宽串的指针)。EdrCenter TextA函数将调用lstrlenA、GetTextExtentPoint32A和TextOutA。EdrCenterTextW将调用lstrlenW、GetTextExtentPoint32W和TextOutW。如果定义了UNICODE标识符,则EDRLIB.H将EdrCenterText定义为EdrCenterTextW,否则定义为FdrCenterTextA。这很像Windows头文件。
EDBLIB.H也包含函数DllMain,取代了 DLL中的 WinMain。此函数用于执行初始化和取消始化,我将在本章的下一节讨论此问题。我们现在所需要的就是从DllMain返回TRUE。
在这两个文件中,最后一点特殊之处就是定义EXPORT标识符。DLL中应用程序使用的数必须是“导出(exported)”的。这不是任何税务或者商业制度,只是确保函数名添加到EDRLIB.LIB的一个关键词(以便连接器在连接使用此函数的应用程序时,能够解析函数名),而且该函数在EDRLIB.DLL中也是可视的。EXPORT标识符包括保存类说明_declspec(dllexport),以及当头文件按C++模块编译时附加的“C”。这将防止编译程序破坏C++的函数名,而且能名允许C和C++程序都使用这个DLL。
库入口/出口点
当库首次启动和结束时,我们调用了DllMain函数。DllMain的第一个参数是库的实例旬柄。如果您的库使用需要实例句柄(诸如DialogBox)的资源,那么您应该将hInstance保存为一个全局变量。DllMain的最后一个参数由系统保留。
fdwReason参数可以是4个值这一,说明为什么Windows要调用DllMain函数。在下面的讨论中,请记住一个程序可以被多次加载,并在Windows下一起运行。每当加载一个程序时,它都被认为是一个独立的进程。
fdwReason的一人值DLL_PROCESS_ATTACH表示动态链接库被映射到一个进程的地址空目。链接库可以根据这个线索进行初始化,为以后来自该进程的请求提供服务。倒如.这类初始化可能包括内存分配。在一个进程的生命周期内,只有一次对DllMain的调用以DLL_PROCESS_ATTACH为参数。使用同一DLL的其他任何进程都将导致另一十使用DLL_PROCESS_ATTACH参数的DllMain调用,但这是对新进程的调用。如果初始化成功,DllMain应该返回一个非0值。返回0值将导致Windows不运行该程序。当fdwReason的值为DLL_PROCESS_DETACH时,意味着进程不再需要DLL,从而提供给库自已清除自已的机会。在32位的Windows下,这种处理并不是严格必须的,但这是一种好的编程习惯。
类似地,当以DLL_THREAD_ATTACH为fdwReason参数调用DllMain时,意味着某个进程创建了一个新的线程。当线程中止时,Windows以 DLL_THREAD_DETACH为fdwReason参数调用DllMain。请注意,如果动志链接库是在线程被创建之后和一个进程链接的,那么可能会得到一个没有相应的先行DLL_THREAD_ATTACH调用的DLLTHREAD_DETACH调用。当使用一个DLL_THREAD_DETACH参数调用DllMain时,线程仍然存在。动态链接库甚至可以在这个过程期间发送线程消息。但是它不应该使用PostMessage,因为线程可能在此消息被检索到之前已经退出了。
测试程序
现在让我们在EDHTEST工作空间里创建第二个项目,程序名你为EDRTEST,而且使用EDRLIB.DLL在Visual C++中加载EDRTEST工作空间时,请从File菜单选择New然后在New对话框中选择Projects选项卡。这次选择Win32 Applieation,并确保选中Add To Current Workspace 按钮,输入项目名称EDRTEST,然后在Locations 域删除第二个EDRTEST子目录。按下OK,然后在下一个对框选择An Empty Project,按Finish。
从File菜单再次选择New,选择Files选项卡,然后选择C++ Source File。确保AddToProject列表框显示的是EDRTEST而不是EDRLIB,输入文件名EDRTEST.C,然后输入程序。此程序用EdrCenterText函数将客户区中的文本串居中。
EDRTEST.C
/*-------------------------------------------------
EDRTEST.C----Program using EDRLIB dynamic—link library
(c)Chatles Petzold,1998
---------------------------------------------------*/
#include <windows.h>
#include "edrlib.h"
LRESULT CALLBACK WndProc(HWND,UINT,WPPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hlnstance,HINSTANCE hPrevlnstance,PSTR szCmdLine,int iCmdShow)
{
static TCHAR szAppName[]=TEXT("StrProg");
HWND hWnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style=CS_HREDPAW | CS_VREDRAW;
wndcless.lpfnWndProc =WndPres;
wndclass cbClsExtra =0;
wndcless.cbWndExtra =0;
wndclass.hlnstance =hlnstanes;
wndclass.hicon =Londicon(NULL,IDI_APPLlCATION);
wndclass.hCursor =LoadCursor(NULL,IDC_ARROW);
wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName=NULL;
wndcless.lpszClessName=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_OVEPLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEOEFAULT,
CW_USEDEFAULT.CW_USEOEFAULT,
NULL,NULL,hlnstance,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,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);
}
注意,为定义EdrCenterText函数,EDRTEST.C包括EDRLIB.H头文件,此函数将在WM_PAINT消息期间调月。
在编译此程序之前,您可能希望做以下几件事。首先,在Project菜单选择Select Active Project。这时您将看到EDRLIB和EDRTEST,选择EDRTEST。在连编此工作空间时,您真正要连编的是程序。另外,在Project菜单中,选择Dependencies,在Select Project To Modify 列表框中选择EDRTEST。在Dependent On The Following Project(s)列表选中EDRLIB。此操作的意思是:EDRTEST需要EDRLIB动态链接库。以后每次连编EDRTEST时,如果必要的话,都将在编译和连接EDHTEST之前重新连编EDRLIB。
从Project菜单选择Settings,单击General标签。当您在左边的窗格中选择EDRLIB或者EDRTEST项目时,如果配置为Win32 Release,则显示在右边窗格中的IntermediateFiles和Output Files将位于RELEASE目录;如果配置为Win32 Debug,则位于DEBUG目录。如果不是,请按此修改。这样可确保 EDRILB.DLL与EDRTEST.EXE在同一个目录中,而且程序在使用DLL时也不会产生问题。
在Project Setting对话框中依然选中EDRTEST,单击C/C++选项卡。按本书的惯例,在Preprocessor Definitions 中,将 UNICODE 添加到 Debug 配置。
现在您就可以在 Debug 或 Release 配置中连编 EDRTEXT.EXE 了。必要时,VisualC++将首先编译和连接EDRLIB。RELEASE和DEBUG目录都包含有EDRLIB.LIB(输入库)和 EDRLIB.DLL。当Developer Studio 连接EDRTEST时,将自动包含输入库。
了解EDRTEST.EXE文件中不包含EdrCenterText代码很重要。事实上,要证明运行了 EDRLIB.DLL 文件和EdrCenterText函数很简单:运行 EDRTEST.EXE 需要 EDRLIB.DLL。
运行EDRTEST.EXE时,Windows按外部库模块执因定的函数,其中许多函数都在Windows常规动态链接库中。但 Windows 也看到程序从 EDRLIB 调用了函数,因此Windows将EDRLIB.DLL文件加载到内存中,然后调用EDRLIB的初始化例程。EDRTEST调用EdrCenterText函数是动态链接到EDRLIB中的函数的。
在EDRTEST.C源代码文件中包含EDRLIB.H与包含WINDOWS.H类似。连接EDRLIB.LIB与连接Windows输入库(例如USER32.LIB)类似。当您的程序运行时,它连接EDLIB.DLL的方式与连接USER32.DLL的方式相同。恭喜您!您已经扩展了 Windows!
在继续之前,我还要对动态链接库说几句:
首先,虽然我们将DLL作为Windows的延伸,但它也是你的应用程序的延伸。DLL完成的每件工作对于应用程序来说都是好的。例如,应用程序拥有它分配的全部内存、它创建的全部窗口,以及它打开的所有文件。多个应用程序可以同时使用同一个DLL,但在 Windows 下,这些应用程序不会相互影响。多个进程能够共享一个动态链接库中相同的代码。但是,DLL为每个进程保存的数据都不同。每个进程都为DLL使用的全部数据分配了自已的地址空间。我们将在下一节看到,在进程间共享内存还需要额外的工作。
在DLL中共享内存令人兴奋的是,Windows能名将同时使用同一个动态链接库的应用程序分开。不过,有时却不太令人满意。您可能希望写一个DLL,其中包含能够被不同应用程序或者同一个程序的不同例程共享的内存。这包括使用共享内存,实际上是一个内存映射文件。
让我们测试一下,这项工作是如何在程序STRPROC("串程序(string program)")和动志链接库STRLIB(“串库(string library)”)中完成的。STRLIB有三个输出函数被STRPROG调用,我们只对此感兴趣,STRLIB中的一个函数使用了在STRPROC定义的回调函数。
STRLIB是一个动态链接库模块,它保存并排序最多256个字符串。在STRLIB中,这些串均为大写,并由共享内存维护。利用STRILB的三个函数,STRPROG能够添加串、删除串,以及从STRLIB获得当前的所有串。STRPROC测试程序有两个菜单项(Enter和Delete),这两个菜单项将激活不同的对话框来添加或删除串。STRPROG在其客户 区列出当前保存在STRLIB中的所有串。
下面这个函数在STRLIB定义,它将一个串添加到STRLIB的共享内存。
EXPORT BOOL CALLBACK AddString(pStringln)
参数pStringIn是串的指针。串在AddString函数中变成大写。如果在STRLIB的列表中有一个相同的串,那么此函数将加一个串的复本。如果成功AddString返回TRUE(非O),否则返回FALSE(0)。如果串的长度为0,或者不能分配保存串的内存,或者已经保存了 256 个串,则返回值将都是FALSE。
STRLIB函和从STRLIB的共享内存中删除一个串EXPORT BOOL CALLBACK DeleteString(pString)另外,参数 pStringIn 是一个串指针。如果有多个相匹配的串,则删除第一个。如果成功,那么 DeleteString 返回TRUE(非0),否则返回FALSE(O)。返回FALSE表明串和长度为0,或者找不到相匹配的串。
STRLIB函数使用了调用程序中的一个回调函数,以便列出目前保在STRLIB共享内存中的串EXPORT int CALLBACK GetStrings(pfnGetStrCallBack,pParam)在调用程序中,回调函数必须像下面那样定义:
EXPORT BOOL CALLBACK GetStrCallBark(PSTR pString,PVOID pParam)
GetStrings 的参数 pfnGetStrCallBack。指向回调函数。直到回调函数返回FALSE(0),GetStrings将为每个串都调用一次GetStrCallBack。GetStrings 返回传递给回调函数的串数。
pParam参数是一个远指针,指向程序员定义的数据。
当然,此程序可以编译成Unicode程序,或者在STRLIB的支持下,编译成Unicode和非Unicode应用程序。与EDRLIB一样,所有的函数都有"A"和"W"两种版。在内部,STRLIB按Unicode保存所有的串。如果非Unicode程序使用了STRLIB(也就是说,程序将调用AddStringA、DeleteStringA和GetStringsA)、串将在Unicode和非Unicode之间转换。
与STRPROCG和STRLIB项目相关的工作空间名为STRPROG。此文件按EDRTEST工作空间的方式组合。程序显示了创建STRLIB.DLL动态链接库所必需的两个文件。
程序STRLIB库
STRLIB.H
/*---------------------------
STRLIB.H header file
-----------------------------*/
#ifdef_cplusplus
#define EXPORT extern "C" _dectspec(dllexport)
#else
#define EXPORT extern "C" _declspec(dllexport)
#endif
//The maximum number of strings STRLIB will store and their lengths
#define MAX_STRINGS 256
#define MAX_LENGTH 63
//The callback function type definition uses generic strings
typedef BOOL(CALLBACK * GETSTRCB)(PCTSTR,PVOID);
//Each function has ANSI and Unicode versions
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);
//Use the correct version depending on the UNICODE identifier
#ifdef UNICODE
#define AddString AddStringW
#define DeleteString DeleteStringW
#define GetStrings GetStringsW
#else
#define AddString AddStringA
#define DeleteString DeleteStringA
#define GetString GetStringsA
#endif
STRLIB.C
/*---------------------------------------------
STRLIB.C----Library module for STRPROG prograrm
(c)Charles Petzold,1998
-----------------------------------------------*/
#include <windows.h>
#include <wchar.h>
#inclube "strlib.h"
//shared memory section (requires/SECTION:shared,RWS in link options)
#pragma data_seg("shared")
int iTotal=0;
WCHAR szStrings[MAX_STRINGS][MAX_LENGTH+1]={'\0'};
#pragma data_seg()
#pragma data_seg ()
int WINAPI DIMain(HINSTANCE hinstance,DWORD fdwReason,PVOID pvReserved)
{
return TRUE;
}
EXPORT BOOL CALLBACK AddStringA(PCSTR pStringin)
{
BOOL bBeturn;
int iLength;
PWSTR pWideStr;
//convert string to Unicode and call AddStringW
iLength=MuitiByteToWideChar(CP_ACP,0,pStringln,-1,NULL,0);
pWideStr=mailoc(iLength);
MuitiByteToWideChar(CP_ACP,0,pStringln,-1,pWideStr,iLength);
bRetum=AddStringW(pWideStr);
free(pWideStr);
return bRetum;
}
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 = malloc(sizeof(WCHAR)*(1+iLength));
wcscpy(pString,pStringln);
_wcsupr(pString);
//Alphabetize the strings
for(i=iTotal;i>0;i-)
{
if(wcscmp(pString,szStrings[i-1])>=0)
break;
wcscpy(szStrings[i],szStrings[i-1]);
}
wcscpy(szStrings[i],pString);
iTotal++;
flee(pString);
return TRUE;
}
EXPORT BOOL CALLBACK DeleteStringA(PCSTR pStringln)
{
BOOL bRetum;
int iLength;
PWSTR pWideStr;
//Convert string to Unicode and call DeleteStringln,-1,NULL,0);
iLength=MuitiByteToWideChar(CP_ACP,0,pStringln,-1,NULL,0);
pWideStr=malloc(iLength);
MuitiByteToWideChar(CP_ACP,0,pStringln,-1,pWideStr,iLength);
bReturn=DeleteStringW(pWideStr);
free(pWideStr);
return bRetum;
}
EXPORT BOOL CALLBACK DeleteStringW(PCWSTR pStringln)
{
int i,j;
if(0==wcslen(pStringln))
return FALSE
for(i=0;i<iTotal;i++)
{
if(szStrings[i],pStringln)==0)
break;
}
//if given string not in list,retum without taking action
if(i==iTotal)
return FALSE;
//Else adjust list downward
for(j=i ; j<iTotal ; j++)
wcscpy(szStrings[i],szStrings[j+1]);
szStrings[iTotal--][0]='\0';
wcscpy(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 fron Unicode
iLength=WideCharToMuitiByte(CP_ACP,0,szStrings[i],-1,NULL,0,NULL,NULL;
pAnsiStr=malloc(iLength);
WideCharToMuitiByte(CP_ACP,0,szString[i],-1,pAnsiStr,iLength,NULL,NULL);
//Call callback function
bReturn=pfnGetStrCallBack(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=pfnGetdStrCallBack(szStrings[i],pParam);
if(bRetum==FALSE)
return i+1;
}
return iTotal
}
除了DllMain函数以外,STRLIB中只有6个函数其他函数输出用。所有这函数都按EXPORT定义。这会使LINK在STRLIB.LIB输入库中列出它们。
STRPROG程序
STRPROG程序其内容是相当直观的。两个菜单选项(Enler和Delete)激活同一个对话框,让你输入一个串,然后格STRPROG调 用AddString或DeleteString。当程序需要新它的客户区时,请调用GetString,并使用函数GetStrCallBack来列出枚举的串。
STRPROG.C
/*----------------------------------------------------
STPRPOG.C----Program using STRLIB dynamic-link library
(c)Charles Petzold,1998
------------------------------------------------------*/
#include<windows.h>
#include "strlib.h"
#include "resource.h"
typedet struct
{
HDC hdc
int xText;
int yText;
int xStart;
int yStart;
int xlncr;
int ylncr;
int xMax;
int yMax;
}
CBPARAM;
URSULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
TCHAR szAppName[]=TEXT("StrProg");
TCHAR szString [MAX_LENGTH+1];
int WINAPI WinMain(HINSTANCE hlnstance,HINSTANCE hPrevlnstance,PSZR szCrndLine,int iCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style=CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc=WndProc;
wndclass.cbClsExtra=0;
wndclass.cbWndExtra=0;
wndclass.hlnstance=hlnstance;
wndclass.hlcon=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;
}
hwnd=CreateWindows(szAppName,TEXT("DLL Demonstration Program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hlnstance,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 iParam)
{
switch{message)
{
case WM_INITDIALOG:
SendDlgItemMessage(hDig,IDC_STRING,EM_LIMITTEXT,MAX_LENGTH,0);
return TRUE;
case WM_COMMAND:
switch(wParam)
{
case IDOK:
GetDigitemText(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->ylncr)>pcbp->yMax)
{
pcbp->yText=pcbp->yStart;
if((pcbp->xText+=pcbp->xlncr)>pcbp->xMax)
return FALSE;
}
return TRLE;
}
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM iParam)
{
static HINSTANCE hlnst;
static int cxChar,cyChar,cxClient,cyClient;
static UINT iDataChangeMsg;
CBPARAM cbparam;
HDC hdc;
PAINTSTRUCT ps;
TEXTMERIC tm;
switch(message)
{
case WM_CREATE:
hlnst=((LPCREATESTRUCT(iParam)->hlnstance;
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&trn);
cxChar=(int)trn.trnAveCharWidth;
cyChar=(int)(trn.trnHeight+trn.trnExternalLeading);
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(hlnst,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)LOWORD(lParam);
return 0;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
cbparam.hdc=hdc;
cbparam.xText=cbparam.xStart=cxChar;
cbparam.yText=cbparam.yStart=cyChar;
cbparam.xlncr=cxChar * MAX_LENGTH;
cbparam.ylncr=cyChar;
cbparam.xMax=cbparam.xlncr * (1 + cxClinet/cbparam.xlncr);
cbparam.yMax=cyChar * (cyClient/cyChar-1);
GetString((GETSTRCB)GetStrCallBack,(PVOID)&cbparam);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
defauit:
if(message==iDataChangeMsg)
lnvalidateRect(hwnd,NULL,TRUE);
break;
}
return DefWindowProc(hwnd,message,wParam,lparam);
}
STRPROG.RC(摘录)
//Microsoft Developer Strudio 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
LIEXT "&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 DISCARUABLE
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在程序的所有实例这间共享数据是毫无问题的。当您在一个要STPRPOG窗口中添加或删除一个串时,这种改变将立即反映在其他的窗口中。
在它的所有例程这间,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段。在Proiect Settings对话框选择Link选项卡,选中STRLIB时在Project Options域(在Release和Debug配置中均可),包含下面的连接语句:
/SECTION:shared,HWS
字母RWS表明段具读、写和共享属性。或者,您也可以直接用DLL源代码指定连接选项,就像我们在STRLIB.C 那样:
#pragma comment(linker,"/SECTION:shared,RWS")
共享的内存段允许不iTotal变量和szStrings字符串数组在STRLIB的所有例程之间共享。因为MAX_STRINGS字符串数组等于256,而MAX_LENGTH等于63,所以共享内存段的长度应为32772字节----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 hLinbrary;
PFNRECT pfnRectangle;
现大将hLibrary设置为库句柄,将lpfnRectangle设置为Rectangle函数的地址:
hLibrary=LoadLibrary(TEXT("GDI32.DLL"))
pfnRectangle=(PRNPRECT)GetProcAddress(hLibrary,TEXT("Rectangle"))
如果找不到库文件,或者发生其他一些错误,LoadLibrary函数返回NULL。现在您可以调用如下函数,然后释放库:
pfnRectangle(hdc,xLeft,yTop,xRight,yBootom);
Freelibrary(hLiBrary);
尽管这项运行时动态链接的技术并没有为Rectangle函数增加多大好处,但它肯定是有用的,如果直到运行时还不知道库模块的名称,这时就需要用它。
上面的代码使用了LoadLibrary和FreeLibrary函数。Windows为所有的库模块提供"引用计数",LoadLibrary使引用计算递增。当Windows加载任何使用库的程序时,引用计数也会递增。FreeLibrary使引用计数递减,在使用库的程序实例结束时也是如此。当引用计数为0时,Windows将从内存中把库删除掉,因为不再需要它了。
纯资源库
可由Windows程序或其他库使用的动态链接库中的任何函数都 必须被 输出。然而.DLL也可以不包含任何输出函数。那么,DDL到底包含什么呢?答案是资源。
假设用户正在使用需要几幅位图的Windows应用程序进行工作。通常要在程序的资源描述文件中列出资源,并用LoadBitmap函数把它们加载到内存中。但用户可能希望创建若干套位图,每一套均适用于Windows所使用的不同显示适配器。将不同套的位图存放到不同文件中可能是明智的,因为只需要在硬盘上但留一套位图。这些文件就是纯资源文件。
程序说明如何创建包含9幅位图的名为BITLIB.DLL的纯资源库文件。BITLIB.RC文件列出了所有独立的位图文件,并为每个文件赋予一个序号。为了创建BITLIB.DLL,需要9幅名为BITMAP1.BMP、BITMAP2.BMP等位图。您可使用随书附带的光盘上提供的位图,或者在Visual C++中创建这些位图。它们与ID号从1到9相对应。
程序BITLIB库
BITLIB.C
/*----------------------------------------------------------
BITLIB.C----Code entry point for BITLIB dynamic-link library
(c)Charles Petzold,1998
------------------------------------------------------------*/
#include<windows.h>
int WINAPI DllMain(HINSTANCE hlnstance,DWORD fdwReason,PVOID pvReserved)
{
return TRUE;
}
BITLIB.RC(摘录)
//Microsoft Developer Studio generated resource script。
#include "resource.h"
#include "afxres.h"
////////////////////////////////////////Bitmap
1 BITMAP DISCARDABLE "bitmap1.bmp"
2 BITMAP DISCARDABLE "bitmap2.bmp"
3 BITMAP DISCARDABLE "bitmap3.bmp"
4 BITMAP DISCARDABLE "bitmap4.bmp"
5 BITMAP DISCARDABLE "bitmap5.bmp"
6 BITMAP DISCARDABLE "bitmap6.bmp"
7 BITMAP DISCARDABLE "bitmap7.bmp"
8 BITMAP DISCARDABLE "bitmap8.bmp"
9 BITMAP DISCARDABLE "bitmap9.bmp"
在名为SHOWBIT的工作空间中创建BITLIB项目。在名为SHOWBIT的另一个项目中,创建程序所示的SHOWBIT程序,这与前面的一样。不过,不要使BITLIB依靠SHOWBIT:否则,连接过程中将需要BITLIB.LIB文件,并且因为BITLIB没有任何输出函数,它也不会创建。事实上分别连编BITLIB和SHOWBIT,可以交替设置其中一个为"Active Project",然后再连编。
SHOWBIT.C从BITLIB读取位图资源,然后顺其客户区显示。按键盘上的任意键可以循环显示。
程序SHOWBIT程序
SHOWBIT.C
/*-------------------------------------------------------
SHOWBIT.C----Shows bitmaps in BITLIB dynamic-link library
(c)Charles Petzold,1998
---------------------------------------------------------*/
#include<windows.h>
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
TCHAR szAppName[]=TEXT("ShowBit");
int WINAPI WinMain(HINSTANCE hlnstance,HINSTANCE hPrevlnstance,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.hlnstance=hlnstance;
wndclass.hlconc=Loadlcon(NULL,lDI_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("Show Bitmaps from BITLIB(Press Key)"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hlnstance,NULL);
if(!hwnd)
return 0;
ShowWindow(hvmd,iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void DrawBitmap(HDC hdc,int xStart,int yStart,HBITMAP hBitmap)
{
BITMAF bm;
HDC hMemDC;
POINT pt;
hMemDC=CreateCompatibleDC(hdc);
SelectObject(hMemDC,hBitmap);
GetObject(hBitmap,sizeof(BITMAP),&bm);
pt.x=bm.bmWitdth;
pt.y=bm.bmHeight;
BitBit(hdc,xStart,yStart,pt.x,pt.y,hMemDC,0,0,SRCCOPY);
DeleteDC(hMemDC);
}
LRESULT CALLBACK WndProc(HWND HWND,UINT message,WPARAM wParam,LPARAM lParam)
{
static HINSTANCE hLibrary;
static int iCurrent=1;
HBITMAP hBitmap;
HDC hdc;
PAINTSTRUCT PS;
switct(message)
{
case WM_CREATE:
if((hLibrary=LoadLibrary(TEXT("BITLIB.DLL")))==NULL)
{
MessageBox(hwnd,TEXT("Can't load BITLIB.DLL."),
szAppName,0);
return -1;
}
return 0;
case WM_CHAR:
if(hLibrary)
{
iCurrent++;
InvalidateRect(hvmd,NULL,TRUE);
}
return 0;
case WM_PAINT;
hdc=BeginPaint(hwnd,&ps);
if(hLibrary)
{
hBitmap=LoadBitmap(hlibrary,MAKEINTRESOURCE(iCurrent));
if(!hBitmap)
{
iCurrent=1;
hBitmap=LoadBitmap,(hLibrary,MAKEINTRESOURCE(iCurrent));
}
if(hBitmap)
{
DrawBitmap(hdc,0,0,hBitmap);
DeleteObject(hBitmap);
}
}
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
if(hLibrary)
FreeLibrary(hLibrary);
PostOuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
在处理WM_CREATE消息期间,WHOWBIT获得了BITLIB.DLL的句柄:
if((hLibrary=LoadLibrary(TEXT("BITLIB.DLL")))==NULL)
如果BITLIB.DLL与SHOWBIT.EXE不在同一个目录,Windows将按本章前面讨论的方法搜索。如果LoadLibrary返回NULL,SHOWBIT显示一个消息框来报告错误,并从WM_CREATE消息返回-1。这将导致WinMain中的CreateWindow调用返回NULL,而且程序终止。
SHOWBIT通过库句柄和位图号来调用LoadBitmap, 从而得到一个位图句柄:
hBitmap=LoadBitmap(hLibrary,MAKEINTRESOURCE(iCurrent));
如果号码iCurrent对应的位图无效,或者没有足够的内存来加载位图,则返回一个错误。
在处理WM_DESTROY消息时,SHOWBIT释放库:
FreeLibrary(hLibrary);
当SHOWBIT的最后一个实例终止时,BITLIB.DLL的引用计数变为0,并且释放占用的内存。这就是实现“剪贴画”程序的一种简单方法,所谓的“剪贴画”程序就是能够将预先创建的位图(或者元文件、增强元文件)加载到供其他程序使用的剪贴板。
动态链接库允许Windows应用程序共离资源和代码,动态链接库DLL(Dynamics Link Library)实际上足一个可执行模块,它包台了Windows应用程序所需要的函数,DLL在程序远行时与应用程序动态他链接,在程序结束时DLL与应用程序动态地失去链接。
静态链接和动态链接
在程序中大量使用库函数,库函数的作用机制是一种静态链接的机制,当在创建可执行文件时,先将C/C++源代码编译成目标代码,然后用链接程序将目标码与库函数链接起来,最后生成可执行代码。这种用链接程序将目标代码和库函数柜链接的方式叫做静态链接,静态链接能方便地为所有的应用程序担可用的函数集,应用程序无须重写这些函数的源代码,链接程序在链序在链接期间将相应的信息拷贝到可执行文件中。
在Windows操作系统中,应用程序在多任务环境中共离内存资源,因此,静态链接往往导致资源浪费。例如,两个应用程序使用相同的C/C++库函数时,用静态链接的方式,则函数在链接时拷贝给每个应用程序,结果在内存中每个函数拷贝了两份。如果在程序中采用础奄铸接,两个应用程序共享资源和函数,因而可使内存和系统资源的使用率大大提高。
为了指示应用程序运行时从何处找到所需要的代码,Windows操作系统提供了一个称为输入库(Input Library)的机制。输入库帮助应用程序在DLL中寻找代码。输入库中包含了应用程序模块和DLL模块相联系的信息。一个应用程序模块是该应用程序的EXE文件,它包含了应用程序的代码;DLL模块是包含库代码的模块。并用往往具有下列的扩展名:.dll、.drv和.fon等,其中包含了可以进行动态链接的可执行模块。应用程序执行时,当需要从动志链接库中调用函数时,链接程序并不拷贝函数的代码,它只是从输入库中拷贝一些信息,这些信息用米指示程序运行时从何处找到所要求的代码。输入库文件的后缀一般为.lib。
一个动态链接库和一个可执行文件之间的别主要体现在以下几个方面
1、一个DLL要求个DEF文件:
2、一个DLL有一个DllMain的入口点,而不是传统的WinMain入口点:
3、一个DLL可以装入,但永远不能直接执行。
上一篇:asp基于web的教学系统
下一篇:MFC中常用类介绍