我的第一个Windows程序, Hello,world!
在Charles Petzold的书中, 作者首先回顾了下C语言在控制台下通过标准输入输出函数输出"Hello,world!"的程序, 代码如下:
#include <stdio.h> int main() { printf( "Hello,world!\n" ) ; return 0 ; }
不过令我疑惑的是, 在我这本《Windows程序设计》(第五版) 2010年9月第5版的书中, 却将这段经典代码的"return 0 ;"写成了"Return 0 ;", 笔者尝试了下, "Return 0 ;"在VC6的编译器和GCC下都是报错的, 难道是我买到盗版书了?
同样, Charles Petzold也给出了Windows版的"Hello,world!"(其实他给出的是Hello,windows 98!), 代码如下:
#include<windows.h> int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 ); return 0; }
通过Visual C++ 6.0的"文件"-->"新建"-->"工程", 选择"Win32 Application"创建一个空的项目, 再在这个项目中新建一个"文件", 文件类型为"C++ Source File", 文件以.c为扩展名, 将上面的代码敲入或者复制粘贴到这个文件内容中, 经过编译运行就可以得到一个对话框了, 赶紧截图留念吧!
在这个对话框中, 有标题栏, 标题栏的内容是"MessageBox", 对话框的内容为"Hello,world!", 还有一个"确定"按钮, 而且, 没有那个黑框框窗口, 一切看起来都是那么美好, 来一起看看这段Windows版的Hello,world!吧!
-----------------------------------------------------------
*Windows版的Hello,world!代码注释*
*第一行
#include<windows.h>
稍微有点C语音基础的都能明白, 这是要包含"windows.h"这个头文件, 也就说明, 在下面的代码中, 要用到这个头文件, 如果我们将#include<windows.h>这句去掉再进行编译看看会有什么情况:
Compiling... HelloWorld.c d:\project\lwinc\helloworld\helloworld.c(3) : error C2061: syntax error : identifier 'WinMain' d:\project\lwinc\helloworld\helloworld.c(3) : error C2059: syntax error : ';' d:\project\lwinc\helloworld\helloworld.c(3) : error C2146: syntax error : missing ')' before identifier 'hInstance' d:\project\lwinc\helloworld\helloworld.c(3) : error C2061: syntax error : identifier 'hInstance' d:\project\lwinc\helloworld\helloworld.c(3) : error C2059: syntax error : ',' d:\project\lwinc\helloworld\helloworld.c(3) : error C2059: syntax error : ')' 执行 cl.exe 时出错.
意料之内的, 报错了, 第一条就是标识符"WinMain"错误, 具体的细节暂时就不深究了, 继续向下看。
*关于windows.h头文件:
在windows.h这个头文件中, 实际上已经包含了若干的其他相关的头文件, 用书上的话说, windows.h是个非常重要的包含文件, 其中包含的其他比较重要的头文件有:
■ WINDEF.H 基本数据类型定义
■ WINNT.H 支持Unicode的类型定义
■ WINBASE.H 内核函数
■ WINUSER.H 用户界面函数
■ WINGDI.H 图像设备接口函数
不过我还是好奇windows.h到底包含了那些头文件, 找到VC6的安装目录, 打开Include文件夹, 找到WINDOWS.H并打开, 虽说看不太懂, 但找#include关键词还是无压力的.
除去上面的5个还有:
■ WINRESRC.H ■ EXCPT.H ■ STDARG.H ■ WINNLS.H ■ WINCON.H
■ WINVER.H ■ WINREG.H ■ WINNETWK.H ■ CDERR.H ■ DDE.H
■ DDEML.H ■ DLGS.H ■ LZEXPAND.H ■ MMSYSTEM.H ■ NB30.H
■ RPC.H ■ SHELLAPI.H ■ WINPERF.H ■ WINSOCK2.H ■ MSWSOCK.H
■ WINSOCK.H ■ WINCRYPT.H ■ COMMDLG.H ■ WINSPOOL.H ■ OLE.H
■ OLE2.H ■ WINWLM.H ■ WINSVC.H ■ MCX.H ■ IMM.H
*程序的入口
在Win32控制台程序(Win32 Console Application)中, 应用程序的入口为main()函数, windows程序的程序入口和win32控制台程序的入口类似, 为WinMain()函数.
程序的入口函数在WINBASE.H作出了声明, 声明如下:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd );
其中由声明可以看出, WinMain函数的返回值被定义为int型;
WINAPI为WinMain函数的调用规则, 在WINDEF.H对"WINAPI"作出了如下宏定义:
#define WINAPI __stdcall
说明, WinMain函数的调用规则为"__stdcall"方式, 对于"__stdcall"调用规则, 现在暂时先不去深究, 知道有这么回事就行, 以后会详细了解到的, 现在如果深究"__stdcall"就偏离了这篇博文的主题。
*WinMain函数的参数:
1>. WinMain的第一个参数 HINSTANCE hInstance, 用书上的解释为"实例句柄", 由于第一次接触C语言Windows程序设计, 对这个句柄的概念也不是很了解, 去百科了下, 句柄的解释为"一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标志应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。"——引用自百度百科->句柄。
笔者是这样对句柄进行理解的, 在一个应用程序中, 通常创建了很多的窗口、按钮、标签, 或者使用了一个文件等, 在程序的任何地方, 只要能够获得这个被称为句柄的东西, 就能够找到该控件或者窗口在内存中的位置, 从而对其进行操作。感觉有点像带参数的main函数, 只是这里的主函数参数为一个句柄。
2>. WinMain函数的第二个参数, 同样是个实例句柄, 但书上又进一步解释说在32位的Windows程序设计中, WinMain函数的实例句柄概念已不再采用, 因此WinMain的第二个参数通常总是NULL。
笔者的见解: 感觉马上就要晕了, 疑问一: "因此WinMain的第二个参数通常总是NULL", 那么第一个呢?WinMain的第一个参数会不会也可以是NULL呢? 疑问二: WinMain函数的参数从何而来?是操作系统么?带着疑问继续向下看。
3>. WinMain的第三个参数是用来运行程序的命令行, PSTR: 用来指向一个字符串的指针类型, szCmdLine, sz:表示以0结尾的字符串; 目的是通过命令行方式运行程序并向主函数中传入参数, 应该就像给main函数传入参数一样;
4>. WinMain的第四个参数是一个int型参数, 用来指明程序(窗口)最初如何被显示, 例如最小化?最大化?全屏?
笔者的见解: 应该很有用, 经常见一些游戏一启动就是全屏的, 但是这个参数也是操心系统传给程序的么?因为从平时运行Windows程序时都是直接双击, 并没有通过命令行给它传入参数, 在编程时应该对程序启动时的显示方式有交代才对, 这样系统再运行时再把这个交代的参数传入给程序告诉程序启动时应该如何显示.
(在"笔者的见解"部分的观点均为笔者个人的见解, 如果有误肯定指正, 我会及时更正, 避免误导其他读者。)
*WinMain函数函数体的MessageBox函数:
MessageBox(), 名如其"人", 不用猜也知道这个就是显示一个对话框的函数, 打开API文档,MSDN Library通过索引找到MessageBox函数, 发现其声明如下:
int MessageBox( HWND hWnd, // handle of owner window, 窗口的一个句柄 LPCTSTR lpText, // address of text in message box, 一个文本(字符串)的指针 LPCTSTR lpCaption, // address of title of message box, 标题字符串的指针 UINT uType // style of message box, 对话框的风格 );
在上面示例中对MessageBox函数的调用如下:
MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );
第一个参数窗口的句柄的实参为NULL, 意思为不属于任何窗口.
第二个参数为对话框的内容, 第三个参数为对话框的标题, 但是这两个参数都使用了一个TEXT()的函数, 书上讲使用TEXT()的目的是将这些字符串打包到TEXT宏代码里面, 笔者尝试了不用这个TEXT()函数而直接像这样:
MessageBox( NULL, "Hello,world!", "MessageBox", 0 );
调用并没有出现警告或者报错信息, 具体使用TEXT()函数的详细原因还不太清楚, 暂时先在这里画个圈。
第四个参数为对话框的风格, 一些以MB_开头的一些常量的组合, 可以使用OR(|)运算进行组合, 这些常量定义在WINUSER.H中, 例如常用的有:
1>.对话框按钮类型:
#define MB_OK 0x00000000L //仅有一个"确定"按钮 #define MB_OKCANCEL 0x00000001L //"确定" + "取消" #define MB_ABORTRETRYIGNORE 0x00000002L //"终止" + "重试" + "忽略" #define MB_YESNOCANCEL 0x00000003L //"是" + "否" + "取消" #define MB_YESNO 0x00000004L //"是" + "否" #define MB_RETRYCANCEL 0x00000005L //"重试" + "取消"
2>.对话框中的图标类型:
#define MB_ICONHAND 0x00000010L //一个红X的错误/停止图标 #define MB_ICONQUESTION 0x00000020L //一个问号的询问图标 #define MB_ICONEXCLAMATION 0x00000030L //一个黄色感叹号的警告图标 #define MB_ICONASTERISK 0x00000040L //一个带有i的信息提示图标
同时, 在这些图标中有的还可以用其他名称代替, 这些别名在WINUSER.H的定义如下:
#define MB_ICONWARNING MB_ICONEXCLAMATION //警告 #define MB_ICONERROR MB_ICONHAND //错误 #define MB_ICONINFORMATION MB_ICONASTERISK //信息 #define MB_ICONSTOP MB_ICONHAND //停止
------------------------------------------------------------------
下午的学习暂时就到这里, 在学习的过程中出现了几个疑问, 在这里对疑问进行下总结:
疑问一: 在书中介绍WinMain函数的参数时讲到 "因此WinMain的第二个参数通常总是NULL", 那么第一个呢?WinMain的第一个参数也可以是NULL吗?
疑问二: WinMain函数的参数从何而来?是操作系统么?
疑问三: 使用TEXT()函数的作用是什么呢?
在这篇博文中笔者表达可几个个人的见解(也可以说是猜测), 如果有错误或者不妥之处恳请指出, 笔者将立即更正, 避免误导其他读者。
Wid, 2012.10.06