您的位置:首页 > 其它

程序的启动和退出过程详解

2015-03-06 11:09 302 查看
    Windows支持两种类型的应用程序。一种是基于图形用户界面(GUI)的应用程序,另一种是基于控制台用户界面(CUI)的应用程序。 基于G U I 的应用程序有一个图形前端程序。它能创建窗口,拥有菜单,可以通过对话框与用户打交道,并可使用所有的标准“Wi n d o w s ”组 件。Wi n d o w s 配备的所有应用程序附件(如Notepad 、Calculator 和WordPad ),几乎都是基于G U I 的应用程序。基 于控制台的应用程序属于文本操作的应用程序。它们通常不能用于创建窗口或处理消息,并且它们不需要图形用户界面。虽然基于C
U I 的应用程序 包含在屏幕上的窗口中,但是窗口只包含文本。命令外壳程序CMD.EXE(用于Windows 2000 )和COMMAND.COM (用于Windows 98)都是典型的 基于CUI的应用程序。

    这两种类型的应用程序之间的界限是非常模糊的。可以创建用于显示对话框的C U I 应用程序。例如,命令外壳程序可能拥有一个特殊的命令,使它 能够显示一个图形对话框,在这个对话框中,可以选定你要执行的命令,而不必记住该外壳程序支持的各个不同的命令。也可以创建一个基于G U I 的应用程序,它能将文本字符串输出到一个控制台窗口。我常常创建用于建立控制台窗口的G U I 应用程序,在这个窗口中,我可以查看应用程序执 行时的调试信息。当然你也可以在应用程序中使用图形用户界面,而不是老式的字符界面,因为字符界面使用起来不太方便。

    当使用Microsoft Visual C++来创建应用程序时,这种集成式环境安装了许多不同的链接程序开关,这样,链接程序就可以将相应的子系统嵌入产生的可执行程序。用于C U I 应用程序的链接程序开关是/ S U B S Y S T E M : C O N D O L E ,而用于G U I 应用程序的链接程序开关是SUBSYSTEM : WINDOWS 。当用户运行一个应用程序时,操作系统的加载程序就会查看可执行图形程序的标题,并抓取该子系统的值。如果该值指明一个CUI应用程序,那么加载程序就会自动保证为该应用程序创建文本控制台窗口。

如果该值指明这是个G U I 应用程序,那么加载程序不创建控制台窗口,而只是加载应用程序。一旦应用程序启动运行,操作系统就不再考虑应用程 序拥有什么类型的用户界面。Wi n d o w s 应用程序必须拥有一个在应用程序启动运行时调用的进入点函数。可以使用的进入点函数有4 个:int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow);

int WINAPT wWinMain(HINSTANCE hinstExe,HINSTANCE, PWSTR pszCmdLine,int nCmdShow);

int __cdecl main(int argc,char *argv[],char *envp[]);

int _cdecl wmain(int argc, wchar_t *argv[], wchar_t *envp[]);
    操作系统实际上并不调用你编写的进入点函数。它调用的是C / C + +运行期启动函数。该函数负责对C / C + +运行期库进行初始化,这样,就可以 调用m a l l o c 和f r e e 之类的函数。它还能够确保已经声明的任何全局对象和静态C + +对象能够在代码执行以前正确地创建。下面说明源代 码中可以实现哪个进入点以及何时使用该进入点(见表4 - 1 )。

表4-1 应用程序的进入点

应用程序类型
进入点
嵌入可执行文件的启动函数
需要ANSI字符和字符串的GUI应用程序
WinMain
WinMainCRTStartup
需要Unicode字符和字符串的GUI应用程序
wWinMainw
WinMainCRTStartup
需要ANSI字符和字符串的CUI应用程序
main
mainCRTStartup
需要Unicode字符和字符串的CUI应用程序
wmain
wmainCRTStartup
    链接程序负责在它连接可执行文件时选择相应的C / C + +运行期启动函数。如果设定了/ S U B S Y S T E M : W I N D O W S 链接程序开关,那 么该链接程序期望找到一个Wi n M a i n 或w Wi n m a i n函数。如果这两个函数都不存在,链接程序便返回一个“未转换的外部符号”的错误消 息。否则,它可以分别选择Wi n M a i n C RT S t a r t u p 函数或w Wi n M a i n C RT S t
a r t u p 函数。
同样,如果设定了/ S U B S Y S T E M : C O N S O L E 链接程序开关,那么该链接程序便期望找到m a i n 或w m a i n 函数,并且可以分别选 择m a i n C RT S t a r t u p 函数或w m a i n C RT S t a r t u p 函数。同样,如果m a i n 或w m a i n 都不存在,那么链接程序返回一条 “未转换外部符号”的消息。

    但是,人们很少知道这样一个情况,即可以从应用程序中全部删除/ S U B S Y S T E M 链接程序开关。当这样做的时候,链接程序能够自动确定应 用程序应该连接到哪个子系统。当进行链接时,链接程序要查看代码中存在4 个函数(Wi n M a i n 、w Wi n M a i n 、m a i n 或w m a i n ) 中的哪一个。然后确定可执行程序应该是哪一个子系统,并且确定可执行程序中应该嵌入哪个C / C + +启动函数。

    Wi n d o w s / Visual C++编程新手常犯的错误之一是,当创建新的应用程序时,不小心选择了错误的应用程序类型。例如,编程员可能创建一个 新的Wi n 3 2 应用程序项目,但是创建了一个进入点函数m a i n 。当创建应用程序时,编程员会看到一个链接程序错误消息,因为w i n 3 2 应 用程序项目设置了/ S U B S Y S T E M : W I N D O W S 链接程序开关,但是不存在Wi n M a i n 或w Wi n M a
i n 函数。这时,编程员可以有 4 个选择:

将m a i n 函数改为Wi n M a i n 。通常这不是最佳的选择,因为编程员可能想要创建一个控制台应用程序。
用Visual C++创建一个新的Win32 控制台应用程序,并将现有的源代码添加给新应用程第4 章进程计计47 下载序项目。这个选项冗长而乏味,因为它好像是从头开始创建应用程序,而且必须删除原始的应用程序文件。
单击Project Settings 对话框的L i n k 选项卡,将/ S U B S Y S T E M : W I N D O W S 开关改为/ S U B S Y S T E M : C O N S O L E 。这是解决问题的一种比较容易的方法,很少有人知道他们只需要进行这项操作就行了。
单击Project Settings 对话框的L i n k 选项卡,然后全部删除/ S U B S Y S T E M : W I N D O W S 开关。这是我喜欢选择的方法,因为它提供了最大的灵活性。现在,连接程序将根据源代码中实现的函数进行正确的操作。当用Visual C++的Developer Studio 创建新Wi n 3 2 应用程序或Wi n 3 2 控制台应用程序项目时,我不知道为什么这没有成为默认设置。

    所有的C / C + +运行期启动函数的作用基本上都是相同的。它们的差别在于,它们究竟是处理A N S I 字符串还是U n i c o d e 字符串,以及它 们在对C 运行期库进行初始化后它们调用哪个进入点函数。Visual C++配有C 运行期库的源代码。可以在CR t0.c 文件中找到这4 个启动函数的代码 。

现在将启动函数的功能归纳如下:

 检索指向新进程的完整命令行的指针。
检索指向新进程的环境变量的指针。
 对C / C + +运行期的全局变量进行初始化。如果包含了S t d L i b . h 文件,代码就能访问这些变量。表4 - 1 列出了这些变量。
 对C 运行期内存单元分配函数(m a l l o c 和c a l l o c )和其他低层输入/输出例程使用的内存栈进行初始化。•
为所有全局和静态C + +类对象调用构造函数。

    当所有这些初始化操作完成后,C / C + +启动函数就调用应用程序的进入点函数。如果编写了一个w Wi n M a i n 函数,它将以下面的形式被调用 :GetStartupInfo(&StartupInfo);
int nMainRetVal = wWinMain(GetMjduleHandle(NULL),
NULL, pszCommandLineUnicode,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ?
StartupInfo.wShowWindow:SW_SHOWDEFAULT);
    如果编写了一个Wi n M a i n 函数,它将以下面的形式被调用:GetStartupInfo(&StartupInfo);

int nMainReLVal = WinMain(GetModuleHandle(NULL),
NULL, pszCommandLineANSI,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ?
Startupinfo.wShowWindow:SW_SHOWDEFAULT);
    如果编写了一个w m a i n或m a i n 函数,它将以下面的形式被调用:int nMainRetVal = wmain(__argc, __wargv, _wenviron);

int nMainRetVal = main(_argc, __argv, _environ);

当进入点函数返回时,启动函数便调用C 运行期的e x i t 函数,将返回值(n M a i n R e t Va l )传递给它。E x i t 函数负责下面的操作 调用由_onexit函数的调用而注册的任何函数。
为所有全局的和静态的C++类对象调用析构函数。
调用操作系统的ExitProcess函数,将nMainRetVal传递给它。这使得该操作系统能够撤消进程并设置它的e x i t 代码。

表4 - 2 显示了程序能够使用的C / C + +运行期全局变量。
表4-2 程序能够使用的C / C + +运行期全局变量

变量名
类型
说明
_osver
unsigned int
操作系统的测试版本。例如Windows 2000 Beta 3是测试版本2031 。因此_osver的值是2031
_winmajor
unsigned int
采用十六进制表示法的Windows主要版本。对于Windows2000来说,它的值是5
_winminor
unsigned int
采用十六进制表示法的Windows次要版本。对于Windows2000来说,它的值是0
_winver
unsigned int
( _winmajor < < 8 ) + _ winminor在命令行上传递的参数号
__argc
unsigned int
带有指向ANSI/Unicode字符串的指针的__argc大小的数组
__argv
char * *
带有指向ANSI/Unicode字符串的指针的__argc大小的数组
__wargv
wchar_t * *
每个数组项均指向一个命令行参数
_environ
char * *
指向ANSI/Unicode字符串的指针的数组。每个数组项指向一个环境字符串
_wenviron
wchar_t * *
指向ANSI/Unicode字符串的指针的数组。每个数组项指向一个环境字符串
_pgmptr
char *
正在运行的程序的ANSI/Unicode全路径和名字
_wpgmptr
wchar_t *
正在运行的程序的ANSI/Unicode全路径和名字
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息