第一个程序
和控制台应用程序一样,Windows对应的“Hello,world”程序包括了同样的组成部分,即 include语句、程序入口、函数调用,以及return语句。
//控制台应用程序 HelloMsg.c
#include <windows.h>
int main()
{
MessageBox(NULL,TEXT("Hello,windows 98! "),TEXT("HelloMsg"),0);
return 0;
}
//Windows桌面应用程序 HelloMsg.c
//注意:此代码在控制台应用程序项目无法运行,入口函数不一样。
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdshow)
{
MessageBox(NULL,TEXT("Hello,windows 98! "),TEXT("HelloMsg"),0);
return 0;
}
修改源文件后缀名为cpp也可以运行。
头文件
HelloMsg.c里打头的是在几乎所有用C语言编写的 Windows程序中都有的预处理器指令:
#include <windows.h>
windows.h是一个最重要的包含文件,它囊括了若干其他Windows头文件,其中的某些头文件又包含另外的一些头文件。下列几个是最重要也是最基本的头文件:
-
WinDef.H:基本数据类型定义。
-
Winnt.H:支持Unicode的类型定义。
-
WinBase.H :内核函数。
-
WinUser.H:用户界面函数。
-
WinGDI.H:图形设备接口函数。
这些头文件定义了 Windows的所有数据类型、函数调用、数据结构以及常量标识符它们在Windows文档中占有至关重要的地位。你既可以外部依赖项打开这些头文件,直接翻阅它们的内容。
头文件的详细说明在C++语言中。
程序入口
正像main是C程序的入口一样,Windows程序的入口是WinMain,它总是以下面的面目出现:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdshow)
程序入口在 WinBase.h 声明如下:
int
WINAPI
WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
);
返回值
WinMain 函数的返回值被定义为int。
WINAPI
minwindef.H中用以下语句来定义WINAPI标识符
#define WINAPI __stdcall
这条语句规定了一种函数调用约定,表明如何生成在堆栈中放置函数调用参数的机器代码。绝大多数Windows函数调用都定义成WINAPI。
函数调用约定主要约束了两件事:
1.参数传递顺序
2.调用堆栈由谁(调用函数或被调用函数)清理
常见的函数调用约定:stdcall cdecl fastcall thiscall naked call__stdcall表示
1.参数从右向左压入堆栈
2.函数被调用者修改堆栈
3.函数名(在编译器这个层次)自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见。
参数
改动了WinMain声明中的两个参数名,因为绝大部分Windows程序在变量命名上都采用所谓“匈牙利标记法”,变量名前都有个短前缀,用以表明该变量的数据类型。前缀i表示int,前缀sz表示“以零结尾的字符串”。
-
hInstance:实例句柄
WinMain的第一个参数一般叫做“实例句柄”。在 Windows程序中句柄无非就是一个数值,程序里用它来标识某些东西。比如在我们这个例子里,这个句柄就唯一标识了我们的这个程序。 -
hPrevInstance:同一程序其它实例句柄
实际上在某些Windows程序中,把句柄当作调用参数是必须的。比如在早期的 Windows版本中,当多路并发运行同一个程序时,就需要为那个程序创建多个实例。同一程序的所有的实例都共享代码以及只读存储(即菜单或对话框模板之类的资源)。一个程序可以通过查看 hPrevInstance参数从而知道是否有它的其他实例正在运行。它也就可以因此跳过某些零散杂务步骤,把一些数据从前一个实例搬到自己的数据区来。在32位 Windows中,这一概念已不再采用。因此 WinMain的第二个参数通常总是NULL(定义为0)。 -
szCmdLine:
用来运行程序的命令行(Command Line)。有些Windows程序在启动时用它来把文件装入内存。
在上面示例HelloMsg.c中把LPSTR改成了PSTR。这两种数据类型在 Winnt.H中都声明为指向字符串的指针。前缀LP代表长指针(Long Pointer),它是16位 Windows的产物。
-
iCmdshow:
用来指明程序最初如何显示:或正常显示,或最大化到全屏,或最小化显示在任务栏上。
MessageBox函数
MessageBox(NULL,TEXT("Hello,windows 98! "),TEXT("HelloMsg"),0);
MessageBox函数是用来显示短信息的。尽管形式比较单一,MessageBox 所显示的小小窗口实际上也是一个对话框。
-
第一个参数通常是一个窗口句柄。它的具体意思留到第3章再谈。
-
第二个参数就是将要在信息框里出现的文本字符串。
-
第三个参数是将要在标题栏上显示的文本字符串。在HELLOMSG.C中,这些文本串都被打包在 TEXT宏代码(Macro)里面。一般来说,并不需要把所有的字符串都打包到 TEXT宏代码里面,之所以这样做是因为这样在把程序转换成Unicode时会方便很多。我将在第2章中详细讨论这一点。
-
第四个参数是以前缀MB_打头的一些常量的组合。WINUSER.H中定义了这些相关常量。从以下第一个常量集中,你可以任选一个来表示在对话框里希望用哪种按钮:
#define MB_OK 0x00000000L
#define MB_OKCANCEL 0x00000001L
#define MB_ABORTRETRYIGNORE 0x00000002L
#define MB_YESNOCANCEL 0x00000003L
#define MB_YESNO 0x00000004L
#define MB_RETRYCANCEL 0x00000005L
当你把 HELLOMSG 中的第四个参数选成0时,那就只显示OK按钮。你可以通过使用C里面的 OR(l)运算符把一个上述按钮常量跟下列常量组合起来,来描述哪个按钮为默认按钮:
#define MB_DEFBUTTON1 0x00000000L
#define MB_DEFBUTTON2 0x00000100L
#define MB_DEFBUTTON3 0x00000200L
#define MB_DEFBUTTON4 0x00000300L
在我们这个范例程序中,MessageBox函数返回的数值为1,更精确地说返回IDOK在 WINUSER.H中,IDOK定义为1。根据消息框中给出的其他按钮,MessageBox函数也可能返回ISYES、ISNO、IDCANCEL、IDABORT、IDRETRY 或 IDIGNORE。
编译链接运行
通常情况下在编译阶段,编译器从C源代码文件产生一个.OBJ(目标)文件。在链接阶段,链接器又把.OBJ文件和.LIB(库程序)文件放到一起产生.EXE (可执行)文件。你可以通过选择Project菜单下的Settings,并单击Link标签来查看这些库程序的文件列表。特别要注意的是KERNEL32.LIB、USER32.LIB 和 GDI32.LIB三个重要库程序,它们分别代表个主要的 Windows子系统。它们包含动态链接库的名字,以及与.EXE 相关的访问信息Windows利用这些信息来解析程序中对KERNEL32.DLL、USER32.DLL 和GDI32.DLL动态链接库内的函数的调用。
在Visual Studio里,你可以在不同的配置下编译和链接程序。默认情况下,这些配置通常称为Debug配置和Release配置。可执行文件就存放在以这些配置命名的子目录里。在 Debug配置下,.EXE文件中加入了一些有助于程序调试以及源代码跟踪的信息。
参考:《Windows 程序设计 第5版》