一.为什么要学Win32
要回答这个问题,我们就要先搞清楚我们是站在Windows程序开发历史的哪个阶段。当红的C#及.NET平台技术是建立在“程序集”(Assembly)模块上的,这是一种比COM更加高级的封装形式,据说一开始叫“COM3”来着,可能是Bill不太乐意他的他的Windows老在COM上打转转,于是就叫“.NET Framework”了吧。在Assembly之前的封装形式是“COM+”(COM2乎?),在COM+之前自然就是COM封装了,COM前是OLE封装,OLE之前……呃……就没有封装了,只有赤裸裸的Win16/Win32函数可以调用。前面提到的所谓“封装”就是人们发现有些Win32函数总是一起使用或总是按一定的结构使用(称之为“复用”),于是就把它们“攒”起来,形成一个模块。拜C++语言的OO能力所赐,C语言形式的Win32函数被封装在称为“类”的模块里,形成了MFC(Microsoft)及OWL(Borland)等类库,并以COM组件的形式安装在用户的计算机里供用户和开发人员使用。在COM的基础上又发展出了COM+,其本质仍然是对Win32函数的封装。COM+之后就是.NET Framework的Assembly了,它是迄今为止对Win32函数最高级别的封装了……你基本已经看不到Win32函数的影子了,这就是为什么我们说.NET/C#开发不是底层开发的原因。
OK,我们暂且称基于.NET Framework的程序开发为“第三代Windows程序开发”,基于COM的程序开发(如VC/VB)为“第二代Windows程序开发”,基于Win32函数的程序开发为“第一代Windows程序开发”。
由此可见,无论是想掌握COM程序设计还是.NET Framework程序设计的真谛,你迟早是要回来学《Windows程序考古学》的。因为,有些问题由于封装的太厚了,你可能找不到答案——你只可能在Win32级别上去找答案。只有透彻地学习了Win32程序设计之后,你方能体验到脚踏实地、豁然开朗的感觉,放能体验那种恍然间的开悟。
Now, let’s go.去剖析一下一个最简单的Win32程序。
二.热身运动
一上来就直接看Win32程序,我怕会吓到你,所以我们先从一个命令行程序开始。以这个程序来演示一下一个Program是如何进化的。
[一级]
main()
{
}
{
}
解说:这恐怕是最简单的C语言程序了——只有一个main入口点函数,当然,它什么也不做。
[二级A]
void main()
{
}
{
}
解说:在[一级]的基础上,明确地指出了主函数没有返回值。没有返回值对程序的运行结果不好把握,所以这一支进化到此为止。
[二级B]
int main()
{
return 0;
}
{
return 0;
}
解说:其实这是[一级]的完整形式,就算你不写,计算机也会隐式为你添加int返回类型和在执行完之后return一个零。注意哦!不写返回值类型的C语言函数默认是返回int型值,而不是无返回值的void型函数。详细信息你可以去ISO-C90/C99里去查。不过值得注意的是:C++语言不支持默认的int型返回值和return 0,这就意味着,如果你的源文件是以.c作为扩展名,加不加int和return 0都没有关系,若是以.cpp为扩展名,你将有可能收到一个warning,不过,程序应该能继续运行。
[X级]
int main(int argc, char* argv[])
{
return 0;
}
{
return 0;
}
解说:在[二级]的基础上添加了main函数的参数。一个非常重要的而且你必须要知道的一点就是:main入口点函数的参数不像程序内成员函数的参数,成员函数的参数是由设计程序的程序员“手动”传递进去的,也就是程序员调用函数则程序员负责向函数传递参数。而main函数不是由程序员调用的,而是程序编译完成并交付用户后,用户通过操作系统来调用的(比如双击程序的图标或者在命令行里输入程序的名字),因此,main函数的参数不是程序员在设计期能传递的,只能在main函数被系统调用时,由系统传递给它。简言之就是:谁调用,谁传参。
[四级]
#include <stdio.h>
int main(int argc, char* argv[])
{
return 0;
}
int main(int argc, char* argv[])
{
return 0;
}
解说:添加了#include<stdio.h>这句预编译指令,注意:这是一句指令,而不是语句,所以没有分号结尾。
[五级]
#include <stdio.h>
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400,temp=0;
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400,temp=0;
//交换a,b的值
temp=a;
a=b;
b=temp;
//交换x,y的值
temp=x;
x=y;
y=temp;
temp=a;
a=b;
b=temp;
//交换x,y的值
temp=x;
x=y;
y=temp;
//输出结果
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
[七级]
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
return 0;
}
解说:用同样的算法分别交换了a与b、x与y的值。
}
解说:用同样的算法分别交换了a与b、x与y的值。
[六级]
#include <stdio.h>
void Exchange(int* arg1, int* arg2)
{
int temp=0;
temp = *arg1;
*arg1 = *arg2;
*arg2=temp;
}
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400;
//用函数交换值
Exchange(&a,&b);
Exchange(&x,&y);
//输出结果
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
return 0;
}
解说:有操作复用的地方,就会有函数的出现。
void Exchange(int* arg1, int* arg2)
{
int temp=0;
temp = *arg1;
*arg1 = *arg2;
*arg2=temp;
}
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400;
//用函数交换值
Exchange(&a,&b);
Exchange(&x,&y);
//输出结果
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
return 0;
}
解说:有操作复用的地方,就会有函数的出现。
[七级]
#include <stdio.h>
//前置函数声明
void Exchange(int*, int*);
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400;
//用函数交换值
Exchange(&a,&b);
Exchange(&x,&y);
//前置函数声明
void Exchange(int*, int*);
int main(int argc, char* argv[])
{
//声明了一些变量
int a=100,b=200,x=300,y=400;
//用函数交换值
Exchange(&a,&b);
Exchange(&x,&y);
//输出结果
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
return 0;
}
[二级]
[X级]
printf("a=%d,b=%d,x=%d,y=%d\n", a,b,x,y);
return 0;
}
//函数实现
void Exchange(int* arg1, int* arg2)
{
int temp=0;
temp = *arg1;
*arg1 = *arg2;
*arg2=temp;
}
void Exchange(int* arg1, int* arg2)
{
int temp=0;
temp = *arg1;
*arg1 = *arg2;
*arg2=temp;
}
解说:为了避免过多的子函数出现在main前而将main“埋没”,采取了函数的“前置声明”和“后置实现”。特别注意:前置声明函数的时候,甚至可以只给出参数的类型而不必给出参数的名称。
[总结]
至此,一个结构美观,功能实用的小程序就进化完成了——从仅仅8个字符进化到十几行。之所以给大家展示这样一个程序,就是因为我们下面要看的Win32程序虽然复杂,但也是这样一点一点进化来的。
三.正式开始
热身运动结束之后,我们就要正式剖析一个Win32的程序了。Win32的程序远比命令行程序复杂,而且变量名和函数名也要长得多,入口点函数的参数也比较多也比较复杂……呃……入门的门槛比较高,做好心理准备哦!
[一级]:一个什么都不干的Win32程序
#include <windows.h>
WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
}
WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
}
解说:比起命令行下那个只有8个字符的最简单程序来,Win32最简单的程序也足够复杂了。首先,#include<windows.h>指令是绝不能缺少的(就算以后你在程序中没有直接include这个windows.h文件,那么也一定是通过别的.h文件间接地包含了它),不要指望编译器会自动为你添加这一句。其次,入口点函数的名称也不再是main而是WinMain,而且WinMain也不像main那样能够支持有参数和无参数两种形式,WinMain函数只有一种形式,那就是接收4个参数(参数的数据类型怪怪的,如果想知道具体是什么类型,可以参见本人的另一篇掘作《Windows数据类型探幽——千回百转你是谁?》)。目前,最重要的是你要盯紧那第一个参数,也就是HINSTANCE类型的hInstance变量。
[二级]
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
return 0 ;
}
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
return 0 ;
}
解说:在[一级]的基础上,除了像main一样添加了int返回值类型和return 0之外,还添加了一个WINAPI修饰符。这个宏(如果还不了解什么是“宏”,请学习C/C++语言基础知识)的实际值是__stdcall,__stdcall是Microsoft公司对C/C++语言扩充时添加的Keywork,这个Keywork是专门用于呼叫Win32 API时使用的(所以宏的名字叫“WINAPI”),而且在出现这个Keywork的时候,被修饰函数的参数传递顺序是从右向左,被修饰函数被调用完后,还要负责清理自己所占用过的栈内存——这些不理解不要紧,并不影响我们的入门学习。
[X级]
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
MessageBox(NULL, TEXT("Hello, Win32!"), TEXT("问候"), MB_OK) ;
MessageBox(NULL, L"Hello, Win32!",L"问候",0);
return 0 ;
}
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
MessageBox(NULL, TEXT("Hello, Win32!"), TEXT("问候"), MB_OK) ;
MessageBox(NULL, L"Hello, Win32!",L"问候",0);
return 0 ;
}
解说:这次是添加了核心代码(上下两句其实是完全一样的,只是上面一句使用了预先定义的宏方便了记忆,而下面一句是“原始面貌”)。MessageBox函数会让程序弹出一个消息框,第一个参数是指出哪个窗体拥有这个消息框,我们的程序还没有窗体,所以只能用一个NULL值,接下来的两个不说你也应该看出来,一个是内容,一个是标题。不过要注意,由于是32位程序设计,所以要用L(即TEXT()宏的原形)来把16位字符串转换成32位字符串。最后一个参数是消息框的按钮数量——MB_OK就是只有一个OK按钮,对应的值是0;MB_YESNO就是有Yes和No两个按钮,对应的值是4……总之,用记宏比你记没有形象的整数值要方便多了。
本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/18969,如需转载请自行联系原作者