王朝网络
分享
 
 
 

Windows动态链接库基础知识,及简单例子

王朝system·作者佚名  2006-01-10
宽屏版  字体: |||超大  

如我们所知,Windows程序都是一些可执行文件,它们可以创建并显示一个或多个窗体,使用消息循环来接收用户的输入。但是动态链接库并不能直接被执行,它们一般也不会接收消息。它们只是一些包含着函数的独立文件,这些函数可以被Windows程序或者其它DLL调用以完成某项任务。

“动态链接”是指Windows程序在运行时才把自己需要存在于某个库中的函数链接进来。“静态链接”是指Windows程序在编译阶段就把各种对象模块(.OBJ)、运行时库(.LIB)和资源文件(.RES)链接到一起以创建一个可执行文件(.EXE)。

DERNAL32.DLL,USER32.DLL,GDI32.DLL,各种驱动程序如KEYBOARD.DRV,SYSTEM.DRV和MOUSE.DRV,显卡和打印机驱动程序等都是动态链接库。这些库可以被所有的Windows程序共同使用。

有某些动态链接库(如字体文件)称为“resource-only”。它们只包括数据,而不包括代码。因此,动态链接库的目的之一就是为许多不同的程序提供函数和资源。在传统的操作系统里,用户程序在运行时只能调用操作系统自身的某些函数。而在Windows操作系统下,模块或程序调用另一个模块中的函数来执行是一种非常普遍的操作。因此,从某种角度看,对DLL进行编程,其实是在对Windows操作系统作扩展,也可以看作是在对用户程序作扩展。

动态链接库模块可以有其它的扩展名,但是标准的扩展名是.DLL。只有具有标准扩展句的动态链接库模块才可以被Windows自动加载。而如果是其它扩展名的动态链接库模块,程序必须使用LoadLibrary或者LoadLibraryEx函数来显示加载。

我们可以发现,在大型的应用软件中,会常常使用到动态链接库技术。举个例子,假如我们要写一个大型的应用软件,其中包括了多个程序。我们可以发现很多程序可能都会使用到一些同样的通用的函数。我们可以把这些通用的函数放到某个目标库文件中(.LIB),然后在链接是把它加到每个程序中进行静态链接。但是这是一种非常浪费的方法,因为每个程序模块中都会包括这些通用函数的独立拷贝。另外,如果我们要改变库文件中的某个函数,就必须把所有使用到这个函数的程序都重新编译一遍。但是,如果我们使用动态链接库的技术,把所有这些通用函数都放到一个动态链接库文件当中,我们就可以解决以上提到的各种问题。首先,动态链接库在硬盘上只保留一个拷贝,程序只是在运行时才会调用其中使用到的函数,这样我们就可以节省大量的程序存储和运行空间。其次,如果要修改某个通用函数时,只要调用接口没有改变,只是改变它的实现方法,那么我们就不必对每个用到它的程序都进行重新编译,而只要把动态链接库模块重新编译一遍就可以了。

动态链接库模块也可以作为一个单独的产品来发布。这样程序开发人员就可以使用第三方的模块来开发自己的应用程序,提高了程序的复用程序,也节省了大量的时间和精力。

在很多时候时候,我们都会用到“库”(Library)这个词,除了动态链接库(Dynamic-Link Libraries)之外,还有目标库(Object Libraries)和导入库(Import Libraries)。下面,我们分别了解一下这三种库的异同点。

目标库是扩展名为.LIB的文件,包括了用户程序要用到的各种函数。它在用户程序进行链接时,“静态链接”到可执行程序文件当中。例如,在VC++中最常使用到的C运行时目标库文件就是LIBC.LIB。

导入库是一种特殊形式的目标库文件形式。和目标库文件一样,导入库文件的扩展名也是.LIB,也是在用户程序被链接时,被“静态链接”到可执行文件当中。但是不同的是,导入库文件中并不包含有程序代码。相应的,它包含了相关的链接信息,帮助应用程序在可执行文件中建立起正确的对应于动态链接库的重定向表。比如KERNEL32.LIB、USER32.LIB和GDI32.LIB就是我们常用到的导入库,通过它们,我们就可以调用Windows提供的函数了。如果我们在程序中使用到了Rectangle这个函数,GDI32.LIB就可以告诉链接器,这个函数在GDI32.DLL动态链接库文件中。这样,当用户程序运行时,它就知道“动态链接”到GDI32.DLL模块中以使用这个函数。

目标库和导入库都是在程序开发过程中才使用到的,而动态链接库是在程序运行时才使用的。在程序运行时,相应的动态链接库文件必须已经保存在硬盘上了。另外,如果要使用动态链接库文件,该文件必须要保存在同.EXE文件同一个目录下,或者保存在当前目录、Windows系统目录、Windows目录或环境变量中PATH参数指定的目录下。程序也是按照这个顺序来搜寻它需要的动态链接库文件的。

PS:以上内容基本上都是对《Programming Windows Fifth Edition》一书第21章前面两个小节的翻译和整理,如果各位朋友还想了解关于Windows DLL的更多知识和内容,可以参看此书。

我们来看一个简单的DLL的例子(还是书上的例子,简单而又能说明问题)。

虽然DLL的设计思想是使得多个程序可以共享模块,但是在这个例子里,我们不妨只写一个应用测试程序,来对我们开发的DLL模块进行访问、测试和演示。接下来,我们将要创建一个名为EDRLIB.DLL的程序模块。“EDR”的意思就是“easy drawing routines”。在这个DLL模块中,只包含一个函数(EdrCenterText),当然你也可以自己加入一些其它函数。另外,我们还会创建一个名为EDRTEST.EXE的应用程序,通过它来访问EDRLIB.DLL模块并访问其中的函数。

首先,我们通过VC来创建一个空的DLL项目(详细过程见书),然后加入EDRLIB.H和EDRLIB.C两个文件,源代码如下:

/*----------------------

EDRLIB.H header file

----------------------*/

#ifdef __cplusplus

#define EXPORT extern "C" __declspec (dllexport)

#else

#define EXPORT __declspec (dllexport)

#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 -- Easy Drawing Routine Library module

(c) Charles Petzold, 1998

-------------------------------------------------*/

#include <windows.h>

#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) ;

}

写完代码之后,我们就可以开始编译Release或者Debug版本的DLL模块了,编译之后,我们就会在RELEASE或者DEBUG目录下看到EDRLIB.LIB和EDRLIB.DLL两个文件。前者就是我们之前提到的导入库文件,必须通过“静态链接”加入到要调用此DLL模块的应用程序中,而后者则是生成的DLL模块本身了。

在EDRLIB.C中,我们可以看到有两个函数,分别是EdrCenterTextA 和EdrCenterTextW。前者支持非Unicode程序的调用,而后者支持Unicode程序的调用。通过EDRLIB.H中的条件编译选项,把函数的调用形式都统一成EdrCenterText。这样,不管是Unicode程序还是非Unicode程序,在调用该DLL模块中的函数时,至少在形式上都是一样的。关于Unicode的概念,不是这篇文章的重点,因此在这里就不多讲了,需要了解的朋友可以参考本书的第2章。

我们可以看到,除了EdrCenterText函数外,还有一个函数叫作DllMain,它相当于Win32程序当中的WinMain函数。在应用程序装载DLL模块时要进行的初始化工作,以及卸载DLL模块时要进行的注销工作都是在这个函数里实现的。在这里,为了简单起见,只需要返回TRUE就可以了。详细内容,我们稍后讨论。

下面我们简单解释一下“EXPORT”标识符的意思。“EXPORT”字面的意思就是导出,也就是说DLL模块中的函数,如果要想被外部的其它程序或模块调用的话,就必须通过这个标志符来导出。导出以后,在EDRLIB.LIB中才会包含这个函数的函数名,调用它的应用程序也才可以知道该DLL中有哪些函数是可以被调用的。另外,EDRLIB.H开头的几条条件编译语句,我也理解得不是很透彻,反正其结果就是,不管是使用C语言写的程序,还是使用C++语言写的程序,都可以调用它。如果有朋友还想刨根问底的话,我目前也无能为力了,自己写的时候照抄就是了,呵呵!

当DLL模块开始和结束的时候,Windows都会调用DllMain函数。这个函数的第一个参数hInstance是一个“句柄”(instance handle,应该是这样翻译吧?)。如果你的DLL使用到的资源需要一个句柄的话(比如一个对话框),那么把hInstance作为一个全局变量来保存。DllMain的最后一个参数是保留参数,暂时没有任何用处。

DllMain函数的第二个参数fdwReason共有四个可能的取值,用来解释Windows为什么要调用DLL的原因,这四个值分别是DLL_PROCESS_ATTACH、DLL_PROCESS_DETACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH。我们必须意识到,一个程序可以被用户或者操作系统调用很多次,并且同时运行,进行着并行的操作。每次调用一个程序,都会被当作一个独立的进程。(后面几段会涉及到Windows进程和线程的相关概念,请朋友们参看本书第20章。)

DLL_PROCESS_ATTACH表示动态链接库被映射到一个进程的地址空间。这时,我们可以做些初始化的工作,比如分配内存。在DLL的生存周期内,Windows通过这个参数调用DllMain函数只有一次。当然,其它使用同一个DLL模块的进程也会通过这个参数来调用DllMain函数,但是这已经属于另一个新进程的行为了。

如果初始化工作成功完成,DllMain会返回一个非零值,否则返回0。

如果Windows通过DLL_PROCESS_DETACH来调用DllMain函数时,表示这个进程不再需要该DLL模块了。这时,我们可以做一些注销的工作。虽然在Win32编程中,这已经不是很必要了,但这却是一个良好的编程习惯。

DLL_THREAD_ATTACH和DLL_THREAD_DETACH和前面说是非常类似,只是把进程换成了线程。

现在,我们可以创建一个测试程序,来看看之前生成的DLL模块是不是可以被正确调用并执行。我们首先通过VC来创建一个空的Win32应用程序,然后加入以下的代码,文件名为EDRTEST.C。

/*--------------------------------------------------------

EDRTEST.C -- Program using EDRLIB dynamic-link library

(c) Charles Petzold, 1998

--------------------------------------------------------*/

#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) ;

}

我们可以看到,在程序中包含了EDRLIB.H头文件,并且在处理WM_PAINT消息时调用了EdrCenterText函数。这个项目的编译细节,这里就不多说了,朋友们可以自己看书(太懒了吧,呵呵!)。值得提醒的是,要注意EDRLIB.DLL和EDRTEST.EXE的目录的问题,前面已经讲过,应用程序会按照一定的目录顺序去查找它所需要的DLL文件的。另外,还要在依赖性关系的选项里,指明要引用的DLL模块。

我们需要重点理解的是,EDRTEST.EXE程序中并不包括EdrCenterText函数的程序代码,它只是指向了EDRLIB.DLL模块中的EdrCenterText函数。

另外我们还要理解一个重要的概念。虽然DLL模块是可以被许多程序来共享的,但是每个程序都是相对独立地调用DLL模块中的函数的。这是什么意思呢?也就是说,假如调用者使用DLL模块中的函数分配的每一块内存、创建的每一个窗体和打开的每一个文件,都是属于调用者自己独享的。换句话说,多个应用程序可以同时使用同一个DLL模块,但是都是在自己的地址空间内执行的。

多个程序可以共享DLL模块中的程序代码,但是数据空间却是各个调用程序独享的。那么有没有办法在各个程序间也共享数据空间呢?当然有办法啦!不过要套用一句俗话:欲知后事如何,且听下回分解。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有