Windows 应用程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。C/C++开发人员可以使用以下两种入口点函数:
Int WINAPI _tWinMain(
HINSTANCE hInstance,
HINSTANCE,
PTSTR pszCmdLine,
int nCmdShow);
int _tmain(
int argc,
TCHAR *argv[],
TCHAR *envp[]);
具体的符号取决于我们是否要使用Unicode字符串。操作系统实际上不会调用我们写的入口点函数,他会调用由C/C++运行库实现并在链接时使用-entry:命令行选项来设置一个C/C++运行时启动函数。该函数将初始化C/C++运行库,使我们能使用malloc和free之类的函数。它确保了在我们代码开始执行前,我们声明的任何全局和静态c++对象都被正确的构造。
所有的C/C++运行库启动函数所做的事情基本都是一样的,区别在于他们要处理的是ANSI字符串,还是Unicode字符串。以及在初始化C运行库之后,他们调用的是哪个入口点函数。Visual C++自带C运行库的源代码。可以在crtexe.c文件中找到4个启动函数的源代码。这些启动函数的用途简单总结如下:
- 获取指向新进程的完整命令行的一个指针。
- 获取指向新进程的环境变量的一个指针。
- 初始化C/C++运行库的全局变量。如果包括了StdLib.h,我们的代码就可以访问这些变量:_osver, _winmajor, _winminor, _argc,_argv等
- 初始化C运行库内存分配函数(malloc 和 calloc)和其他底层I/O例程使用的堆(heap)。
- 调用所有全局和静态C++类对象的构造函数。
完成所有这些初始化工作后,C/C++启动函数就会调用应用程序的入口点函数。如果我门写了一个_tWinMain函数,而且定义了_UNICODE,其调用过程将如下所示:
GetStartupInfo(&StartupInfo);
int nMainRetVal=wWinMain((HINSTANCE)&__ImageBase,NULL,pszCommandLineUnicode,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWdEFAULT);
如果没有定义_UNICODE,其调用过程将如下所示:
GetStartupInfo(&StartupInfo);
int nMainRetVal=WinMain((HINSTANCE)&__ImageBase,NULL,pszCommandLineAnsi,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWdEFAULT);
注意,_ImageBase是一个链接器定义的伪变量,表明可执行文件被映射到应用程序内存的什么位置。
如果我们写了一个_tmain函数,而且定义了_UNICODE,那么其调用过程如下:
int nMainRetVal=wmain(argc,argv,argp);
如果没有定义_UNICODE,调用过程如下:
int nMainRetVal=main(argc,argv,argp);
使用Visual Studio向导生成的应用程序时,CUI应用程序的入口中没有定义第三个参数(环境变量块),如下所示:
int _tmain(int argc,TCHAR *argv[]);
如果需要访问进程的环境变量,只需将上面调用换成
int _tmain(int argc,TCHAR *argv[],TCHAR *env[]);
这个env参数指向一个数组,数组中包含所有环境变量及其值,两者用等号(=)分隔。
入口点函数返回后,启动函数将调用C运行库函数exit,向其传递返回值(nMainRetVal)。
exit函数执行以下任务:
调用_onexit函数调用所注册的任何一个函数。
调用所有全局和静态C++类对象的析构函数。
在DEBUG生成中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄露报告。
调用操作系统的ExitProcess函数,向其传入nMainRetVal。这会导致操作系统"杀死"我们的进程,并设置它的退出码。
注意,为了安全起见,Microsoft并不赞成使用所有这些变量,因为使用了这些变量的代码可能会在C运行库初始化这些变量之前开始执行。我们应该直接调用对应的Windows API函数。