我们编写Win32SDK程序时,需要弹出对话框以作出友好的选择,MessageBox这个API函数就可以实现该功能。在开头要添加<windows.h>,因为其包含了众多的API函数声明头文件。为了探究这个小小的MessageBox是怎么弹出来的,我们右击MessageBox,选择“Go to definition of MessageBox(转到定义) ”将打开<winuser.h>中的#define MessageBox MessageBoxW定义行,我们继续对MessageBoxW右击“Go to definition of MessageBox(转到定义) ”将转到MessageBoxW的函数原型声明处: int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType); 我们在使用Windows窗口操作系统时,经常会蹦出大大小小的窗口, MessageBox只是Windows作为提示的对话框窗口单元。那么,MessageBoxW这个API函数到底在哪里实现的呢?应用程序是如何调用系统接口函数的呢? 动态链接库(.dll) 实际上Windows API函数是定义在一些DLL中的, DLL 实现了代码封装,从这个角度来看DLL才是真正意义上的API函数包,它是非开源Windows操作系统提供给我们的底层接口。DLL的编制与具体的编程语言及编译器无关。 动态链接库dll文件(Linux中与之对应的是的.so)存放在C:\WINDOWS\system目录和C:\WINDOWS\system32目录下,它在被应用程序调用时才同程序相链接。 其中最重要的DLL是User32.dll、Gdi32.dll和Kernel32.dll这三个库文件。这三个库文件中的API函数都在Windows.h头文件中进行了声明。从功能上进行分类,User32.dll(Windows XP USER API Client DLL)定义了窗口管理函数,包括窗口的创建、显示、设置和移动等;Gdi32.dll(GDI Client DLL)定义了图形设备函数(GDI),实现与设备无关的绘图功能;Kernel32.dll(Windows NT BASE API Client DLL)定义了系统服务函数,包括诸如内存调度、进程管理等与操作系统有关的底层功能。我们可以通过VC6自带的Depends工具或Dll函数查看器来一窥内幕,见下图。 DLL文件包括了具体实现的代码编译后的结果(二进制的机器码),而头文件就是打开.dll库文件的钥匙。所以我们若要使用MessageBoxW,只需#include <winuser.h>(已被<windows.h>包含)。 VS编译器自带的标准函数库<stdio.h>,<stdlib.h>,<string.h>,<math.h>中声明的函数可以到C:\Program Files\Microsoft Visual Studio 8\VC\crt\src中查看相关实现源代码。例如strcat.c中实现了strcat和strcpy函数。有些函数的实现还是要调用底层API,例如C标准库函数create用于创建文件,但它是靠调用CreateFile(kernel32.dll)函数来完成创建文件功能的;beginthread(process.h,thread.c)需要调用 CreateThread(kernel32.dll)函数。
如果你在本机编写一个Windows应用程序,移植到其他机子上(当然也是Windows操作系统),有可能因为缺少相关DLL文件而无法执行。因为DLL是动态链接,就是随用随加载,这就是为什么我们玩3D游戏时经常弹出缺少d3dx9***.dll的错误提示。如果启动的程序调用了一个过期的DLL文件或不匹配的DLL文件,则会出现“未定义的动态链接调用”消息。
动态链接库除了实现代码的共享外,其模块封装特性使得应用程序在调用一个DLL的不同版本时,只要导出的函数名相同就不必进行重新编译链接。这样,软件产品在更新或升级时,客户程序不必进行改动。在开发软件产品时,对于通用功能的函数,一般以DLL的形式来实现。Windows设备驱动程序就是体现上述特点的动态链接库。
动态链接库的调用方式又分为隐式调用(也称静态调用,需要.lib文件)和显式调用(也称动态调用,LoadLibrary->GetProcAddress->FreeLibrary)。
静态链接库(.lib)
在早期库的组织形式相对简单,里面的目标代码只能够进行静态链接,所以我们称为“静态库”,静态库的结构比较简单,其实就是把原来的目标代码放在一起,链接程序LINK根据每一份目标代码的符号表查找相应的符号(函数和变量的名字),找到的话就把该函数里面需要定位的进行定位,然后将整块函数代码放进可执行文件里,若是找不到需要的函数就报错退出。标准Turbo C2.0中的C库函数,例如scanf、printf、memcpy、strcpy等,就是使用的静态库技术。
以下是C程序的编译链接过程:(1)执行cl /c main.c;cl /c lib1.c;cl /c lib2.c生成了main.obj lib1.obj lib2.obj三个文件;(2)执行link /lib lib1.obj;link /lib lib2.obj#生成了2个文件lib1.lib lib2.lib;(3)执行link main.obj lib1.lib lib2.lib生成main.exe.
静态链接lib库(Linux中与之对应的是的.a)的两个特点:
#1链接后产生的可执行文件包含了所有需要调用的函数的代码,因此占用磁盘空间较大。
#2如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。
我们可以用记事本打开C:\Program Files\Microsoft Visual Studio\VC98\Lib中的USER32.LIB文件,其中有
__imp__MessageBoxA@16 _MessageBoxW@16 // 这里16为参数的字节数
? _MessageBoxW@16 USER32.dll USER32.dll/ 889206797
静态链接库lib文件中存放的是接口函数申明的入口地址,dll中存放的是函数实体!当我们隐式调用dll时,需要在Link选项指明其对应的lib库。lib告诉编译器你的dll都导出了什么函数,以及这些函数的地址名称,运行的时候就根据这些信息到dll里面去找。
实际上用VC6.0新建一个Win32 Console Application时,我们查看Project Settings—>Link—>Object/library modules中发现VC默认已连接了kernel32.lib、user32.lib、gdi32.lib等常用的lib文件。如果需要显式设置的话,比如在网络编程中需要添加WS2_32.LIB库,则可以在文件的开头使用 #pragma comment(lib,"WS2_32.LIB")命令。
在编写MFC项目时,我们打开Project Settings—>General的Microsoft Foundation Classes,里面有两种链接方式:Use MFC in a Static Library ,Use MFC in a Shared Library。对应在Visual Studio 2005中项目属性—>配置属性—>常规—>MFC的使用中设置链接方式。
如果选择Use MFC in a Shared Library的话,你编译后的程序中不包含MFC库,所以文件会比较小,但是如果你的程序直接移到一个没有安装过MFC的机器上时,可能会导致找不到MFC的DLL,故发布时要带MFC得DLL文件。如果选择Use MFC in a Static Library,那么编译后的程序就直接包含了调用MFC的部分的库,文件可能会大一些,但是可以直接移到其他机器上运行,即发布时不用带MFC的DLL文件。
参考:《动态链接库DLL的创建和使用》
评论