windows动态链接库入门
在DOS环境下编过程序的读者一定知道静态库的含义——程序员将实现各种功能的代码写成一个个子程序(函数),编译成obj文件后,将多个obj文件组合成一个lib文件,当程序中要用到这些函数的时候,只需要指定函数名称,编译器就可以从库中抽出对应的子程序代码插入到可执行文件中去,这样就可以不必一遍遍地重写相同的功能代码。这种链接方法就是静态链接,
静态链接的缺点显而易见,如果有多个程序用到库中的同样函数,那么所有这些可执行文件中都会包含一份同样的代码,对于每个程序几乎必须使用的一些函数来说,如果硬盘上有一万个程序用到这个函数,那么就存在一万份相同的代码,这显然是很浪费空间的。静态链接的另外一个缺点是:如果某个函数因为发现有错或更新算法等种种原因需要升级版本时,必须把所有用到此函数的可执行文件都找回来重新编译一遍,遗漏的程序中存在的还是旧版本的代码。
DOS操作系统是单任务的操作系统,每时每刻只能有一个程序在运行,所以使用静态链接浪费的空间仅表现在磁盘空间的浪费上;而Windows操作系统是多任务的,内存中会同时装入多个程序的代码,如果使用静态链接的话,意味着有多份相同的代码被装入内存,这种浪费代价将是更昂贵的。
Windows的解决办法就是使用动态链接库,动态链接库从表面上看也是提供了一大堆通用的函数,也可以被多个程序使用,但它和静态库的使用上有很多的不同点。
静态库仅在编译的时候使用,编译完成后,可执行文件就可以脱离库文件单独使用了,而动态链接库中的代码在程序编译的时候并不会被插入到可执行文件中,在程序运行的时候才将整个库的代码调入内存,所以称为“动态链接”。如果有多个程序用到同一个动态链接库,Windows在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同进程的地址空间中,这样不管有多少程序在使用一个库,库代码实际占用的物理内存永远只有一份。当然,这时候库使用的数据段还是会被映射到不同的物理内存中,多少个程序在使用动态链接库就会有多少份数据段。
当应用程序装载动态链接库的时候,程序中仅包括库的名称和函数的名称,这些信息是动态寻找对应函数所必须的,程序在编译和链接的时候必须插入这些定位信息,定位信息取自导入库文件。
动态链接库的缩写为DLL(Dynamic Link Library),大部分动态链接库是以扩展名为dll的文件形式存在的,但并不是只有dll扩展名的文件才是动态链接库,系统中的某些exe文件、字体文件(*.fon)、一些驱动程序(*.drv)、各种控件(*.ocx)和输入法模块(*.ime)等都是动态链接库。实际上,系统中大部分包含公用代码的模块——不管扩展名是什么——都有可能是动态链接库。
一个文件是否是动态链接库取决于它的文件结构,动态链接库文件和可执行文件同样使用标准的PE(Portable Extutable)文件格式,仅文件头中的属性位不同而已,所以exe文件的一些特征也存在于动态链接库中,比如在动态链接库中也可以定义并使用各种资源,可以导入并使用其他动态链接库中的函数等。
有一个最重要的概念一定要牢记:动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一体”的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。
与可执行文件一样,动态链接库需要一个入口点,动态链接库的入口点是一个函数,函数的名称并不重要,通常入口函数命名为“DllEntry”,你也可以把它取名为其他任何合法的名字,但入口函数的格式是有规定的。
库的入口函数对调用动态链接库的应用程序来说是不可见的,它仅供操作系统使用。Windows在库装载、卸载、进程中线程的创建和结束等时候调用入口函数,以便动态链接库可以采取相应的动作。在入口函数中可以通过参数来判别Windows的本次调用究竟是在哪种情况下发生的。入口函数的结构一般如下面所示:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ return TRUE;}与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def,它的内容一般如下:
; Library CommentLIBRARY LibraryNameDESCRIPTION LibraryDescriptionEXPORTS Func1 Func2分号";"表示注释, LIBRARY表示库的名称,DESCRIPTION表示库的描述信息,EXPORTS表示导出的函数列表。如:
; This is an example of windows 32 dynamic link libraryLIBRARY "Example"DESCRIPTION "Windows 32 dynamic link library Example"EXPORTS Function1 Function2下面通过一个例子来看看动态链接库的写法:
在VC6++中新建一个Windows 32 Dynamic Link Library的工程,然后添入以下文件:
// Counter.c#include DWORD dwCounter = 0;BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ return TRUE;}void SetText(HWND hWnd, UINT uID, DWORD dwCount){ SetDlgItemInt(hWnd, uID, dwCount, TRUE);}void WINAPI IncCount(HWND hWnd, UINT uID){ dwCounter++; SetText(hWnd, uID, dwCounter);}void WINAPI DecCount(HWND hWnd, UINT uID){ dwCounter--; SetText(hWnd, uID, dwCounter);}//counter.h#ifndef __COUNTER_H_#define __COUNTER_H_#ifdef __cplusplusextern "C"{#endif void WINAPI IncCount(HWND hWnd, UINT uID); void WINAPI DecCount(HWND hWnd, UINT uID); #ifdef __cplusplus}#endif#endif // __COUNTER_H_; counter.def; Win32 Dynamic link libraryLIBRARY "Counter"DESCRIPITION "Windows 32 Dynamic link library counter.dll"EXPORTS IncCount DecCount上面有3个文件,一个是动态链接库的实现文件counter.c,一个是头文件counter.h,一个是导出函数文件counter.def,count.h文件不是必须的,只是为了方便应用程序调用而建立的,可以不要。在这个动态链接库中有4个函数,一个是必须的入口函数DllMain, 一个是内部使用的函数SetText,设置窗口上面控件文本,2个导出函数,IncCount和DecCount用于添加和减少计数,这2个导出函数是为其它程序提供的。
下面我们来看看如何来使用这个动态链接库。
方法一:利用动态链接库提供的.dll和.lib以及.h文件
新建一个VC6++的win32应用程序,加入以下文件:
// resource.h#define IDD_MAIN 101#define IDC_COUNT 1000#define IDC_INC 1001#define IDC_DEC 1002#define IDC_STATIC -1// resource.rc#include "resource.h"#include "afxres.h"///////////// Dialog//IDD_MAIN DIALOG DISCARDABLE 0, 0, 187, 57STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENUCAPTION "DLL使用方法一"FONT 10, "宋体"BEGIN LTEXT "当前计数:",IDC_STATIC,7,7,42,8 EDITTEXT IDC_COUNT,55,7,105,14,ES_AUTOHSCROLL | ES_READONLY PUSHBUTTON "增加(&A)",IDC_INC,27,33,50,14 PUSHBUTTON "减少(&D)",IDC_DEC,97,34,50,14END// UseDll1.c#include #include "Counter.h"#include "resource.h"#pragma comment(lib, "Counter.lib")LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd ){ DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, 0); return 0;}LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch ( uMsg ) { case WM_CLOSE: EndDialog(hWnd, 0); break; case WM_COMMAND: if ( LOWORD(wParam) == IDC_INC ) IncCount(hWnd, IDC_COUNT); else if (LOWORD(wParam) == IDC_DEC ) DecCount(hWnd, IDC_COUNT); break; default: return FALSE; } return TRUE;}在编译文件的时候,需要把counter.h文件拷贝到到工程里面,并且把生成的.dll和.lib文件拷贝到可执行文件的目录。该程序很简单,就是调用动态链接库的函数,通过点击按钮增加和减少计数,而这个计数是在动态链接库内部实现的。即使你运行多个副本,程序显示的也是各自的计数,这也说明了,动态链接库只共享代码,而不共享数据。
#include "Counter.h"包含了动态链接库的导出函数声明,#pragma comment(lib, "Counter.lib")指出了使用的导出库,注意,这个只是在编译的时候需要,但是在运行的时候,需要的不是这个.lib文件,而是Counter.dll文件。
如果我们没有.lib文件,而只有.dll文件,那么只要知道了动态链接库导出的函数有哪些以及它们的输入輸出参数,也可以使用,下面就展示如何在只有.dll文件以及知道函数原型的情况下如何使用动态链接库:
同例子一,也建立一个VC6++的win32应用程序,资源文件同上,只有.c 文件不同:
// UseDll2.c#include #include "resource.h"char szErr1[] = TEXT("Dll 装载失败");char szErr2[] = TEXT("DLL导出函数提取失败");char szTitle[] = TEXT("DLL动态装载");typedef void (WINAPI *PINCCOUNT)(HWND hWnd, UINT uID);typedef void (WINAPI *PDECCOUNT)(HWND hWnd, UINT uID);PINCCOUNT pIncCount = NULL;PDECCOUNT pDecCount = NULL;HMODULE hLib = NULL;LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd ){ if ( (hLib = LoadLibrary("Counter.dll")) == NULL ) { MessageBox(NULL, szErr1, szTitle, MB_OK | MB_ICONERROR); return -1; } pIncCount = (PINCCOUNT)GetProcAddress(hLib, "IncCount"); pDecCount = (PDECCOUNT)GetProcAddress(hLib, "DecCount"); if ( pIncCount == NULL || pDecCount == NULL ) { MessageBox(NULL, szErr2, szTitle, MB_OK | MB_ICONERROR); return -1; } DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, 0); return 0;}LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch ( uMsg ) { case WM_CLOSE: EndDialog(hWnd, 0); if ( hLib) FreeLibrary(hLib); break; case WM_COMMAND: if ( LOWORD(wParam) == IDC_INC && pIncCount) pIncCount(hWnd, IDC_COUNT); else if (LOWORD(wParam) == IDC_DEC && pDecCount) pDecCount(hWnd, IDC_COUNT); break; default: return FALSE; } return TRUE;}在上面的例子中,我们通过LoadLibrary来装载动态链接库,然后通过GetProcAddress来获取动态链接库中导出函数的地址,调用该函数之后,在程序退出的时候,用FreeLibrary释放动态链接库。在程序中:
typedef void (WINAPI *PINCCOUNT)(HWND hWnd, UINT uID);定义了一个函数指针类型,该函数的输入参数类型为HWND和UINT,輸出参数类型为void,调用方式为WINAPI即__stdcall,标准调用方式,这种调用方式表明函数可以被其它的语言比如pascal或者basic调用。
注意:调用方式WINAPI一定要和PINCCOUNT放在一个括号里面,不能写成:
typedef void WINAPI (*PINCCOUNT)(HWND hWnd, UINT uID);
这样会有语法错误。
PINCCOUNT pIncCount = NULL;表示我们定义了一个函数指针变量,并初始化为NULL。
pIncCount(hWnd, IDC_COUNT);
表示我们调用这个函数。有的人可能对这种写法感到奇怪,其实没有什么奇怪的,这个和你调用
int x = max(a, b)这样的函数是一样的,只是我们这里使用了函数指针,其实我们应该知道函数的名称其实就是一个指针,我们也可以这样调用:
(*pIncCount)(hWnd, IDC_COUNT);如果我们变一下:
#define IncCount (*pIncCount)IncCount(hWnd, IDC_COUNT);这样是不是好理解一些, 实际上
*pIncCount == pIncCount。
这是windows32的动态链接库,当然你也可以写基于MFC的动态链接库。文中的内容以及例子是根据罗云彬的32位汇编语言改写而来的,如果你想知道的更详细或者是汇编语言的写法,则可以参考此书。
author:cnhnyu
E-Mail:cnhnyugmailcom
QQ:94483026转贴情注明出处。
阅读(1643) | 评论(0) | 转发(0) |