乱弹:
在学习的时候,人们对结构陌生而且复杂的东西总是心存恐惧。比如学习《植物学》,如果一上来就让你研究被子植物雌花的构造,那你肯定当场晕菜;又或如学习 《有机化学》,不由分说先让你分析一下“高级脂肪酸钠”分子中的共价键/离子键结构,我保证你下半辈子都不再打算学化学。然而,雌花结构之美,大分子结构 之玄妙是我们无法想象的--在电子显微镜下,一个一个细胞如冰雕般晶莹,如玉石般温润,如晚霞般五彩缤纷;在原子级别上,大分子中的电子云会在不同的能量 级下显现出各种奇幻的形状来……为什么这些美轮美奂的知识却少人有问津呢?原因就在于他们的复杂,复杂就代表着难度。其实,难度并不是问题,问题在学学习 的方法,如果方法不正确那么就会“难上加难”。真不知道有多少科学天才正是因为学习方法不正确,而在一次一次失败的打击下丧失了热情与爱好,与攀登科学的 最高峰、摘取科学皇冠上的明珠失之交臂。
那么,学习复杂知识最好的办法是什么呢?其实答案很简单:从它的发展/演变历史入手开始学习。想了解被子植物雌花的结构,那就要从单细胞植物(比如蓝藻) 学起,然后是多细胞植物,然后是细胞的分化,细胞分化为器官,器官中有“叶”,叶上长了“孢子”就有了裸子植物,叶子卷曲起来保护这些孢子就成了“花”的 雏形,花(为了吸引虫虫们来Happy)形成了之后孢子演变成种子--被子植物诞生了。研究有机大分子也一样,必须从最最简单的有机分子--甲烷分子-- 的一个C和四个H开始学习,然后你会看到乙烷……然后去掉几个H,再加上O,你就得到酒精(乙醇)了,有意思吧!不过,酒,并不是这么造滴~~~~酒精是 可以燃烧滴,燃烧是剧烈的氧化反应(比燃烧更剧烈的是爆炸),我们身体里的有机燃料(比如血糖和脂肪)也会氧化,不过这种氧化非常缓慢,既保证了我们可以 从中获得足够的热量维持生命、运动和新陈代谢,又保证了我们不至于成为烤全羊或者“肉弹”……血糖氧化不完全就可能产生乳酸,存留在你的肌肉里就会让你“ 酸疼酸疼”的,直到它们被你的血液从肌肉中洗干净……在这些有机小分子上加上一个一个的C原子,就会形成有机大分子,比如脂肪酸分子。脂肪酸分子大了,就 称之为“高级脂肪酸分子”,高级脂肪酸分子与钠盐反应就生成了高级脂肪酸钠,有了高级脂肪酸钠,我们就造出了--肥皂,没错,是肥皂。所以,如果你看到肥 皂盒上印有“使用高级脂肪酸钠制造”一定要心知肚明“此高级非彼高级”,这个“高级”是指C链的长度,跟肥皂的质量没关系,只要是肥皂就肯定是用“高级脂 肪酸钠”制造的,否则的话那是洗衣粉。
推而广之,要想学习微积分,你必须从1+1开始;要想研究社会现状,你必须从“夏商与西周,东周分两段……”开始;要想论证“奇点”的存在或者“时间旅行 ”的可能性,你必须从v=s/t开始……摸清一个事物的来龙去脉才能领略它的复杂之美。从简单到复杂,每个环节都不能缺失,大概考古学、天文学、地质学的 魅力就在于此吧。
正文:
一.为什么要学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;
}
解 说:其实这是[一级]的完整形式,就算你不写,计算机也会隐式为你添加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;
}
解 说:在[二级]的基础上添加了main函数的参数。一个非常重要的而且你必须要知道的一点就是:main入口点函数的参数不像程序内成员函数的参数,成员 函数的参数是由设计程序的程序员“手动”传递进去的,也就是程序员调用函数则程序员负责向函数传递参数。而main函数不是由程序员调用的,而是程序编译 完成并交付用户后,用户通过操作系统来调用的(比如双击程序的图标或者在命令行里输入程序的名字),因此,main函数的参数不是程序员在设计期能传递 的,只能在main函数被系统调用时,由系统传递给它。简言之就是:谁调用,谁传参。
[四级]
#include <stdio.h>
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;
//交换a,b的值
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);
return 0;
}
解说:用同样的算法分别交换了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;
}
解说:有操作复用的地方,就会有函数的出现。
[七级]
#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);
//输出结果
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;
}
解说:为了避免过多的子函数出现在main前而将main“埋没”,采取了函数的“前置声明”和“后置实现”。特别注意:前置声明函数的时候,甚至可以只给出参数的类型而不必给出参数的名称。
[总结]
至此,一个结构美观,功能实用的小程序就进化完成了--从仅仅8个字符进化到十几行。之所以给大家展示这样一个程序,就是因为我们下面要看的Win32程序虽然复杂,但也是这样一点一点进化来的。
三.正式开始
热身运动结束之后,我们就要正式剖析一个Win32的程序了。Win32的程序远比命令行程序复杂,而且变量名和函数名也要长得多,入口点函数的参数也比较多也比较复杂……呃……入门的门槛比较高,做好心理准备哦!
[一级]:一个什么都不干的Win32程序
#include <windows.h>
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 ;
}
解 说:在[一级]的基础上,除了像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 ;
}
解 说:这次是添加了核心代码(上下两句其实是完全一样的,只是上面一句使用了预先定义的宏方便了记忆,而下面一句是“原始面貌”)。MessageBox函 数会让程序弹出一个消息框,第一个参数是指出哪个窗体拥有这个消息框,我们的程序还没有窗体,所以只能用一个NULL值,接下来的两个不说你也应该看出 来,一个是内容,一个是标题。不过要注意,由于是32位程序设计,所以要用L(即TEXT()宏的原形)来把16位字符串转换成32位字符串。最后一个参 数是消息框的按钮数量--MB_OK就是只有一个OK按钮,对应的值是0;MB_YESNO就是有Yes和No两个按钮,对应的值是4……总之,用记宏比 你记没有形象的整数值要方便多了