由MessageBox透视Win32 API的调用

系统 1782 0

下面我们来看看 Windows 平台下应用程序是怎么调用 Windows 提供的底层 API 服务运行的。

我们编写 Win32SDK 程序时,需要弹出对话框以作出友好的选择, MessageBox 这个 API 函数就可以实现该功能。在开头要添加 <windows.h> 因为其包含了众多的 API 函数声明头文件。为了探究这个小小的 MessageBox 是怎么弹出来的,我们右击 MessageBox ,选择“ Go to definition of MessageBox( 转到定义 ) ”将打开 <winuser.h> MessageBox 定义处。 MessageBox(A/W) 的函数原型声明如下:

// WINUSER.H

WINUSERAPI

int

WINAPI

MessageBoxA (

HWND hWnd ,

LPCSTR lpText ,

LPCSTR lpCaption ,

UINT uType );

WINUSERAPI

int

WINAPI

MessageBoxW (

HWND hWnd ,

LPCWSTR lpText ,

LPCWSTR lpCaption ,

UINT uType );

#ifdef UNICODE

#define MessageBox MessageBoxW

#else

#define MessageBox MessageBoxA

#endif // !UNICODE

我们在使用 Windows 窗口操作系统时,经常会蹦出大大小小的 窗口, MessageBox 只是 Windows 作为提示的对话框窗口单元。那么, MessageBoxW 这个 API 函数到底在哪里实现的呢?应用程序是如何调用系统接口函数的呢?

动态链接库( .dll

初窥 DLL

实际上 Windows API 函数是定义在一些 DLL 中的, DLL 实现了代码封装,从这个角度来看 DLL 才是真正意义上的 API 函数包,它是非开源 Windows 操作系统提供给我们的底层接口。 DLL 的编制与具体的编程语言及编译器无关。

动态链接库 dll 文件( Linux 中与之对应的是的 .so 存放在 C:/WINDOWS/system 目录和 C:/WINDOWS/system32 目录下,它在被应用程序调用时才同程序相链接。

其中 Windows 系统最重要的 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 )定义了系统服务函数,包括诸如内存调度、进程管理等与操作系统有关的底层功能。我们可以通过三种方式来查看 DLL 文件中的导出函数信息:( 1 )使用 Windows 系统的 dumpbin 命令(在命令行中: C:/WINDOWS/system32> dumpbin ntdll.dll /exports );( 2 )使用 VC 自带的 Depends 工具( Microsoft Visual Studio 6.0 Tools à Depends Visual Studio 2005 命令行提示 à depends );( 3 Dll 函数查看器 ViewDll ExeScope )。以下使用 VC 自带的 Depends 工具查看 user32.dll MessageBox 函数系列。

由MessageBox透视Win32 API的调用

DLL API

正如 Java 的跨平台( Write Once, Run Everywhere )需要 JVM 的支持一样, C/C++ 成为跨平台的编程语言,依赖各个平台(包括操作系统和编译器)对 C/C++ 标准函数库的具体平台实现。

VS 编译器自带的标准 C 函数库 <stdio.h> <stdlib.h> <string.h> <math.h> 中声明的函数可以到 C:/Program Files/Microsoft Visual Studio/VC98/CRT/SRC( C:/Program Files/Microsoft Visual Studio 8/VC/crt/src) 中查看相关实现源代码。 strcat.c 文件提供了 strcat strcpy 函数的源码 , Windows 系统中, C: / WINDOWS/system32/ ntdll.dll 提供了 strcat strcpy 这两个 CRT API 的底层实现。用户对 C 函数的调用最终通过调用底层 API 来完成真正的功能。例如 C 标准库函数 create 用于创建文件,但它是靠调用 CreateFile kernel32.dll 函数来完成创建文件功能的; beginthread( process.h,thread.c ) 需要调用 CreateThread kernel32.dll 函数来完成线程的创建。

DLL 的移植升级

Windows 将遵循下面的搜索顺序来定位 DLL : 包含 EXE 文件的目录 > 进程的当前工作目录 >Windows 系统目录 >Windows 目录 > 列在 Path 环境变量中的一系列目录。

如果你在本机编写一个 Windows 应用程序,移植到其他机子上 ( 当然也是 Windows 操作系统 ) ,有可能因为缺少相关 DLL 文件而无法执行。因为 DLL 是动态链接,就是随用随加载,这就是为什么我们玩 3D 游戏时经常弹出缺少 d3dx9***.dll 的错误提示。如果启动的程序调用了一个过期的 DLL 文件或不匹配的 DLL 文件,则会出现“未定义的动态链接调用”消息。

动态链接库除了实现代码的共享外,其模块封装特性使得应用程序在调用一个 DLL 的不同版本时,只要导出的函数名相同就不必进行重新编译链接。这样,软件产品在更新或升级时,客户程序不必进行改动。在开发软件产品时,对于通用功能的函数,一般以 DLL 的形式来实现。 Windows 设备驱动程序就是体现上述特点的动态链接库。

DLL 的调用

动态链接库的调用方式又分为隐式调用(也称静态调用,需要 .lib 文件)和显式调用(也称动态调用, LoadLibrary à GetProcAddress à FreeLibrary )。

那么,在我们编写的程序中,如何调用 DLL 中的 API 呢?既然用户对 C 函数的调用最终是通过调用底层 API 来完成真正的功能,那么,我们在编写第一个 Hello World 程序时就已经不知不觉地调用了 DLL 。实际上 <stdio.h> 中定义的 printf 函数由 msvcrt.dll 函数导出,而 *printf 系列函数在 ntdll.dll 中有具体底层实现。

DLL 文件包括了具体实现的代码编译后的结果(二进制的机器码),而头文件中的代码主要是 DLL 库文件 导出函数的原型声明。 <winuser.h> 即主要对 user32.dll 中导出的函数做声明索引, 所以若要使用 MessageBox(A/W) #include <winuser.h> (已被 <windows.h> 包含)。正如调用 printf 函数需要 #include <stdio.h>

在调用 printf 函数或 MessageBox 函数时,我们仅仅包含了声明头文件,没有显式 LoadLibrary à GetProcAddress ,那我们是如何是隐式调用(定位) DLL 中的 API 的呢?实际上我们在利用 VC 向导生成一个 Win32 Console Application 时,向导已经为项目设置了 link 项( Project Settings à Link à Input à Object/library modules ),其中默认链接 kernel32.lib user32.lib gdi32.lib 等。

静态链接库( .lib

目标代码集静态链接库

在早期库的组织形式相对简单,里面的目标代码只能够进行静态链接,所以我们称为“静态库”,静态库的结构比较简单,其实就是把原来的目标代码( *.obj) 集合在一起,链接程序 LINKER 根据每一份目标代码的符号表查找相应的符号(函数和变量的名字),找到的话就把该函数里面需要定位的进行定位,然后将整块函数代码放进可执行文件里,若是找不到需要的函数就报错退出。标准 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 )如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。

DLL 隐式调用静态链接库

使用 DLL 隐式链接时,可执行程序链接到一个包含 DLL 导出函数信息的输入库文件 (.LIB 文件 ) 。操作系统在加载使用可执行程序时加载 DLL 。可执行程序直接通过函数名像调用其他源文件中的函数一样调用 DLL 中的导出函数。

我们可以用记事本打开 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 中相应的 API

除了在“ Project Settings à Link à Input à object/library modules” 中填写静态链接库( *.lib )外,我们还可以通过 #pragma comment 宏显示输入 *.lib 库文件, 例如在网络编程中需要添加 WS2_32.LIB 库,则可以在文件的开头包含头文件后 #pragma comment ( lib , "WS2_32.LIB" ) 引入静态链接库文件。

由于 我们 经常 要调用一些第三方厂商或其他编译器编写的动态链接库,但是一般都不提供源文件或 .lib 文件。我们若知道相应 API 的函数原型,通过 LoadLibrary à GetProcAddress 以实现正确的调用。如果隐式调用,则需要 lib 文件,可使用 DLL2LIB 工具生成 DLL 对应的 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 的创建和使用

深入探究 VC- 编译 CL.EXE

隐式 链接 无.LIB 动态链

由MessageBox透视Win32 API的调用


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论