当前位置:C++技术网 > 资讯 > Windows零基础入门:2.4 WinMain函数参数深入浅出详解

Windows零基础入门:2.4 WinMain函数参数深入浅出详解

更新时间:2015-08-08 15:26:45浏览次数:1+次

    这一节课,我们来仔细了解一下入口函数的参数。
    对于入口函数的返回值是作为程序的退出码,这个在前面的小节里讲过了这个知识点,如果你不知道,说明你没有看,只要看了就懂了。虽然我们的C++技术网提供免费零基础入门课程,我也建立了一个学习群,促进学习。我们的目标是让参与学习的都学好。但是却发现,很多都是被动式学习,在群里找他们都没有回话,只有个别几个积极学习。据了解,很多初学者都很急功近利,学一节课就想写出怎么样的程序。往往大量的培训机构就是这么吸引人的,但是也急功近利,课程只讲述那些技术如何用,对于为什么用,背景基础知识,也都一笔带过。然而,很多学生却还以为好厉害的样子。
    我们推出的课程,要杜绝这样的低质量课程。如果你在看这个教程,请不要急功近利。我们课程看似很慢,其实效率是很高的,看似入门,确实很深入的。学的每一节课,都是仔细斟酌的。都为零基础入门定制,既基础又深入,你只需要学一遍,就彻底掌握,不必反复学多个版本的视频课程了。
    不过,每一节课,如果涉及到动手的地方,请务必动手尝试,否则,效果不大。要主动去学习,虽然我会主动带着大家,讲的很仔细,但是要深刻领悟还是需要自己去动手领悟的。
    我觉得这些话不是废话,请读者琢磨。想成为高手,必须静下心扎实学习。求快永远都会得不到真经,甚至会“走火入魔”(会学迷茫了,学得心烦气躁,甚至放弃了)。每一个学习过程都需要循序渐进,这样知识层面,也完整,也会增进你的信心,实实在在提高内功。内功是我们变成真正高手的灵魂。而这些内功就是把这些知识学得无所不知,为什么来,有什么用,如何用全部掌握,你想不是高手都不行。
    我们的课程就是让你变成高手的课程。急功近利,就不用学了。
    好了,我们开始讲解入口函数的各个细节。我们再来看看入口函数的代码:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,PSTR szCmdLine,int iCmdShow)
{
    return 0;
}
     WINAPI是函数的调用约定,前面也说过,不重复说了。对于调用函数约定,我会后面用一节课专门讲解一下。
    那么现在我们要看的就是参数了。
    WinMain函数有四个参数,这个四个参数必不可少,因为Win32程序需要这些参数。
    我们先看看参数类型HINSTANCE、PSTR。这两个类型是大家接触Win32编程最先遇见的数据类型了。肯定对这些很陌生,也感觉别扭。但是等后面大家用多了,就会喜欢了。大家现在只是不习惯罢了。
    WINAPI和HINSTANCE定义在WinDef.h文件中,PSTR定义在WinNT.h文件中。如果我们的cpp文件中不包含这些头文件,就无法使用这些东西的。而我们在cpp文件中没有包含这些头文件,为什么能用呢?我们在前面的课程中,注释掉windows.h的头文件包含,就发现这些东西报错。而恢复包含windows.h就可以正常使用了。
    事实上,我们需要的这些头文件,都写在windows.h头文件中了。这样我们就不必一个个去包含了。windows.h头文件就像一个桶,桶里装了很多经常用的基本的头文件,那么我们只要包含这个桶,是不是就一并包含了所有需要的东西呢。只要桶里包含了,我们就拥有了。微软就是基于这个考虑,把最基本常用的头文件,都放在windows.h头文件了,这样我们就省事了。
    但是,我们有时候用不上很多东西,而都包含进来就会增大程序文件的大小。这时候,我们可以自己主动去选择要包含的头文件,而不是这样笼统的包含的。你可能经常发现一些程序很小,却功能挺强大的。说明这个开发者水平很高,深知自己用的功能都需要什么包含什么,然后自己手动包含的。所以,其实你也不必太惊讶,你也可以精简自己的程序的大小。
    为了证实windows.h头文件中确实包含了WinDef.h和WinNT.h两个头文件,我们来找一下。这里插一句,你要看这些类型定义在哪,如何定义的,把光标点到这些类型上面,然后右击,然后“查看定义”或者“转到定义”,就可以看到了。在VS2015中,直接可以在本页面查看定义,很方便,不必跳转到定义的文件页面了。VS2010等比较低的版本,就需要“转到定义”了。
    我们要查看头文件,把光标放在头文件的几个字上,然后右击,单击“打开文档<windows.h>”,这样我们就可以打开这个头文件了。打开windows.h头文件后,如下图所示:
    windows.h头文件
    有兴趣你可以从头看到尾,不过基本上你也看不懂。我们来搜索一下windows.h头文件是否包含了WinDef.h和WinNT.h两个头文件。CTRL+F,打开查找对话框,输入头文件名字WinDef.h,查找结果如下图所示:
    查找WinDef.h
    然后再输入WinNT.h查找,结果如下图所示:
    没找到WinNT.h
    结果发现没找到。是不是没有包含呢?不是的,WinNT.h被包含在其他头文件中,而这个头文件被包含在windows.h头文件中,也就是嵌套的包含了。我找了半天,发现在WinDef.h头文件中包含了WinNT.h头文件。如下图所示:
    在WinDef.h中找到WinNT.h
    到此,我们看到了这个关系,windows.h包含了WinDef.h头文件,然后WinDef.h包含了WinNT.h头文件。这样就把我们需要的定义都包含进来了,所以,包含一个windows.h就够了。你也可以看到,在windows.h头文件中有大量的包含,包含的头文件还有包含,所以,如果这些包含的你不需要,就让你的程序无谓的变大了。但是功底不够,这些你也不能确定你到底需要包含些什么,所以,努力学习吧。
    到此,对于参数从哪来已经弄清楚了。下面仔细看看类型的定义。这里顺便提一下WINAPI,这个的定义如下:
#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define WINAPIV     __cdecl
#define APIENTRY    WINAPI
#define APIPRIVATE  __stdcall
#define PASCAL      __stdcall
     你可以看到,这些是一些宏定义,WINAPI可以换成CALLBACK、APIENTRY、APIPRIVATE和PASCAL。为什么要这么多本质一样的定义呢?你可以从这些宏的字面意思看它想表达的意思。比如CALLBACK,是回调的意思,这里就代表回调函数的调用约定,APIENTRY的ENTRY是入口的意思,表示Win32程序的入口函数调用约定,其他的也是表达一个意思,事实上都是一个东西,换一个名称而已。定义这么多,就是让你在不同的场合使用不同的名字,让表达的意思更加清楚,你也很好记了。就像有人给你取个好听的外号如高个子,也就是想表达你比较高,另一个人给你取外号竹竿,想表达你很瘦。这个多个定义也是这个意思。另一个相对的调用就是__cdecl,表示的是C语言的调用约定。那么这两种调用约定的区别和含义,我将在后面的课程讲解。
    HINSTANCE和PSTR都是重定义的数据类型,使用的是typedef关键字重定义的。定义分别如下:
typedef HINSTANCE__* HINSTANCE   
typedef __nullterminated CHAR *NPSTR, *LPSTR, *PSTR;
typedef char CHAR;
     我们可以看出,这两种类型都是有其他类型重定义类型过来的,HINSTANCE由HINSTANCE__*重定义过来,PSTR由CHAR*重定义类型过来的。重定义类型,也相当于给这个类型取个别名一样。而CHAR有char重定义过来。这样一来,你是不是有所明白了呢。
    对于这个类型在哪里定义,如何重定义类型的,都介绍完了。
    现在需要知道的是,这些类型代表什么意义,有什么用。下面就是说这个问题的时候了。
    PSTR由英文Pointer To String,就是指向字符串的指针,而这个字符串是以'\0'空字符结尾的。__nullterminated这个修饰符就是额外的交待了这个以空字符结尾的字符串类型。这个就是传统的C语言的字符串数组了。是不是回到了熟悉的类型了。这样,你就可以把PSTR等同于char*看待和使用了。这样你也就不会觉得陌生别扭了吧。
    这个PSTR类型的参数,表示的是命令行,也就是启动程序传入的命令行参数。虽然类型被包装成了PSTR,本质还是char*,这样一来,和《Windows零基础入门:1.8 Win32与控制台程序入口函数对比》里谈到的控制台传入的命令行参数类型char** argv是不是一样的呢?char*数组存储的是多个char*类型参数,而WinMain的PSTR把这个参数提取成单独的一个参数,不用数组了,本质还是char*,所以这里就是一致的。是不是再次感觉控制台程序入口函数和Win32程序入口函数是不是很一致呢,也应证了那节课说的。是不是既惊喜又很感叹呢,这两个程序类型看似不相干,却又这样的相似呢。而区别就在于两种了类型各自需要的特定的参数类型不一致罢了。
    最后一个参数int iCmdShow,就是显示窗口的大小的标志。我们可以修改一个Win32程序启动时的状态,不用敲命令。方法如下:
    右击一个程序的快捷方式,然后可以看到如下图所示的界面:
    QQ,启动QQ
    在运行方式中,有三个选项:常规、最小化、最大化。这个就是我们窗口常见的最大化窗口最小化窗口和常规大小窗口了。这里设置的就是传递给程序的iCmdShow了。至于生不生效,就看你的程序处不处理这个参数了,不处理的话,设置了也没有用。比如这个QQ,固定大小,你设置不设置都这么大。它不吃你这一套。但是很多程序会解析这个参数。得到这个参数我们再根据参数最大化启动或者最小化启动窗口等。使用if语句判断一下就可以了。
    这个运行方式对于控制台程序也是有用的,默认会自动处理。你设置为最大化,启动快捷方式时就会将这个参数传入,控制台自动就显示成最大化了。记得,不是直接启动程序哦。以快捷方式,是快捷方式可以这样传递参数。
    最后介绍的就是Win32特有的类型HINSTANCE吧。HINSTANCE表示为Handle to Instance,中文意思就是程序实例的句柄。对于句柄是什么,请先看《浅析Windows中的句柄Handle的本质》。什么是实例呢?实例就是我们运行的一个程序。如果启动一个exe多次,就出现多个同时运行的程序实例。我们可以简单的这样理解:exe文件就像一个模板,而实例就是这个模板印出来的一个东西,这个模板可以印很多实例出来。当然其他exe文件也可以印出一大堆实例来。那么系统为了区分这些实例,就给每一个实例一个句柄,唯一标识这个实例的,就像每一个人有一个身份证号码一样。句柄其实就是这么一个唯一的编号而已。所以HINSTANCE就是这么一个实例句柄类型了。第一个参数hInstance就表示这个程序的实例句柄了。这个实例句柄在程序被系统启动时候,传递给运行时库,运行时库再传递给我们程序的。任何程序,只要知道你这个句柄了,就可以把你干掉。当然你自己必须有呀,所以这就是第一个参数的来源了。至于第二个参数,则是历史遗留,现在没有任何用。我们也就不必研究一个没有意义的东西了。
    入口函数的参数都是由系统传递进来的,你不必担心它的参数来不来得了。有一个疑问,假如我们程序是由另一个程序启动的呢,那参数还是由系统传递进来的吗?好了,这个可能大家都有所误会,我好好解释一下。
    程序实例是系统资源,试问,另一个程序如何启动得了你的程序呢?而我们听说的一个程序启动另一个程序,实际上,是请求系统来启动的。所以启动你的程序的过程依然没有变,变的只是请求启动的对象不一样而已。我们直接双击exe,实际上是系统的Shell程序请求系统启动的,而参数则是从exe文件获取的。如果你是从DOS窗口启动,则是DOS程序请求系统启动你的程序的,那么参数就是你输入到DOS命令传入的。而由另一个程序启动的,参数则是由那个程序传递的。而启动程序的过程都是系统完成。
    到此,整个入口函数的参数介绍完毕。那么下节课就将这节课学习的知识使用起来,也就是教你如何使用这些参数。