1.典型C/C++程序
/*HelloC.c*/ #include<stdio.h> int main(int argc, char *argv[]) { printf(“Hello C!/n”); return 0; } // HelloCPP1.cpp #include<iostream> int main(int argc, char *argv[]) { std::cout<<“Hello CPP!/n”; return 0; } // HelloCPP2.cpp #include <iostream> using namespace std; class X { public: X() { cout<<“X constructor”<<endl; } ~X() { cout<<“X deconstructor”<<endl; } }; X x; int main() { cout<<“end main”<<endl; return 0; }
2.Windows 编程的第一种方式是传统的SDK方式,Windows操作系统是由C语言编写的,故可采用C语言直接调用windows的API函数进行编程。
以下为Win SDK应用程序窗口程序框架示例及框架解析。
(1)建立Win32应用程序项目Win32SDK:
Visual Studio 2005à文件à新建à项目àVisual C++àwin32àwin32项目àwin32应用程序à空项目(默认使用Unicode字符集)。如果运行时提示找不到mfc80d.dll或msvcr80d.dll文件,在“项目属性à配置属性à清单工具à常规”中的“使用FAT32解决办法”处选择“是”,再重新生成解决方案。
(2)添加源文件Win32SDK.cpp
/*以下为典型Windows窗口应用程序框架*/ // Win32 大多数API,包含在windows.h头文件中 #include <windows.h> // 窗口函数声明 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // 应用程序入口函数,参数由系统传入 int WINAPI WinMain( HINSTANCE hInstance, // 程序实例句柄(线索) HINSTANCE hPrevInstance, // 为保持与Win16兼容的句柄 LPSTR lpCmdLine, // 命令行参数 int nCmdShow // 初始化窗口显示方式 ) { LPCTSTR lpszProviderClass = __TEXT(“MyWndClass”); WNDCLASS WndClass; // 窗口类,可为WNDCLASS MSG Msg; // Windows 消息 HWND hWnd; // 窗口句柄 // 窗口类的定义 WndClass.style = CS_HREDRAW|CS_VREDRAW; // 窗口类型 WndClass.lpfnWndProc = (WNDPROC)WndProc; // 指定窗口处理函数 WndClass.cbClsExtra = NULL; // 窗口类无扩展 WndClass.cbWndExtra = NULL; // 窗口实例无扩展 WndClass.hInstance = hInstance; // 窗口类所属实例 WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);// 窗口最小化图标,为默认图标 WndClass.hCursor = LoadCursor(NULL,IDC_ARROW); // 用箭头作为鼠标图标 WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 以白色作为窗口颜色 WndClass.lpszMenuName = NULL; // 窗口无菜单 WndClass.lpszClassName = lpszProviderClass; // 窗口类名 // 注册窗口类 if(!RegisterClass(&WndClass)) { MessageBeep(0); // 如注册失败则发出警告 return FALSE; } // 创建窗口 hWnd = CreateWindow( lpszProviderClass, // 注册的窗口类名 __TEXT(“Hello Windows!”), // 窗口标题名 WS_OVERLAPPEDWINDOW, // 窗口的风格 CW_USEDEFAULT, // 窗口左上角的X坐标,取默认值 CW_USEDEFAULT, // 窗口左上角的Y坐标,取默认值 CW_USEDEFAULT, // 窗口右下角的X坐标,取默认值 CW_USEDEFAULT, // 窗口右下角的Y坐标,取默认值 NULL, // 此窗口无父窗口 NULL, // 菜单句柄(此处无) hInstance, // 程序实例句柄 NULL // 指向一个传递给窗口的指针型参数,此处设置为空 ); if(!hWnd) // 创建窗口失败,返回FALSE { return FALSE; } ShowWindow(hWnd, nCmdShow); // 显示窗口 UpdateWindow(hWnd); // 刷新窗口 // 消息循环 while(GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); // 翻译消息 DispatchMessage(&Msg); // 将消息传递给处理函数 } return Msg.wParam; // 返回消息的附加参数 } // 消息处理函数,每当有与本窗口相关的用户交互时,系统调用此回调函数进行处理 LRESULT CALLBACK WndProc( HWND hWnd, // 窗口句柄 UINT nMessage, // 接受到的待处理的消息ID WPARAM wParam, // 消息附参数 LPARAM lParam // 消息附参数 ) { switch(nMessage) { // 处理用户感兴趣的消息 // 响应WM_CREATE消息 case WM_CREATE: MessageBox(hWnd, __TEXT(“收到WM_CREATE消息!”), __TEXT(“通知”), MB_OK); break; // 响应WM_LBUTTONDOWN消息 case WM_LBUTTONDOWN: MessageBox(hWnd, __TEXT(“收到WM_LBUTTONDOWN消息!”), __TEXT(“通知”), MB_OK); break; // 响应WM_CHAR消息 case WM_CHAR: TCHAR buffer[30]; wsprintf(buffer, __TEXT(“你刚才按下了%c键!”), wParam); MessageBox(hWnd, buffer, __TEXT(“通知”), MB_OK); break; // 响应WM_DESTROY消息 case WM_DESTROY: PostQuitMessage(0); // 发出WM_QUIT消息退出 break; // 调用系统默认处理函数DefWindowProc() default: return DefWindowProc(hWnd,nMessage,wParam,lParam); } return FALSE; // 用户处理后返回,将控制权交由系统。 }
3.Windows下的C/C++应用程序运行机制
(1)subsystem选项
VC6:
Project SettingsàC/C++àPreprocessoràPreprocessor Definitions:_CONSOLE(_WINDOWS)
Project SettingsàC/C++àProject Options:/D “_CONSOLE”(/D “_WINDOWS “)
Project SettingsàLinkà Project Options:/subsystem:console(/subsystem:windows)
VC2005:
项目属性àC/C++à预处理器à预处理器定义:_CONSOLE(_WINDOWS)
项目属性àC/C++à命令行:/D “_CONSOLE”(/D “_WINDOWS “)
项目属性à链接器à命令行:/SUBSYSTEM:CONSOLE(/SUBSYSTEM:WINDOWS)
项目属性à链接器à 系统à 子系统:控制台(/SUBSYSTEM:CONSOLE)(Windows (/SUBSYSTEM:WINDOWS))
(2)可执行文件的Entry Point
可执行文件都有一个Entry Point(起始地址),LINK时可以用/entry指定。
缺省情况下,如果subsystem是“console”,Entry Point是 mainCRTStartup(ANSI)或wmainCRTStartuup(UNICODE),即:
/subsystem:”console” /entry:”mainCRTStartup” (ANSI)
/subsystem:”console” /entry:”wmainCRTStartuup” (UNICODE)
mainCRTStartup 或 wmainCRTStartuup 会调用用户编写的main或wmain。在进入可执行文件的代码前,系统将会创建一个控制台窗口。
值得一提的是,在进入应用程序的Entry Point前,Windows的装载器已经做过C变量的初始化,有初值的全局变量拥有了它们的初值,没有初值的变量被设为0。
如果subsystem是“windows”,Entry Point是WinMain(ANSI)或wWinMain(UINCODE),即:
/subsystem:”windows” /entry:”WinMainCRTStartup” (ANSI)
/sbusystem:”windows” /entry:”wWinMainCRTStartup” (UINCODE)
WinMainCRTStartup 或 wWinMainCRTStartup 会调用 用户编写的WinMain 或 wWinMain。窗口由用户调用CreateWindow(Ex)创建。
在VC2005中,项目属性à链接器à高级处有入口点选项,在编写Windows Mobile ANSI控制台程序时需指定入口点为mainWCRTStartup(WindowsCE (/SUBSYSTEM:WINDOWSCE))。
在VC2005中,建一个简单的Console程序(main为入口函数),编译后F10将进入crtexe.c,查看调用堆栈如下:
àint main(int argc, char* argv[])
àint __tmainCRTStartup(void)
àint mainCRTStartup(void)
<1>mainCRTStartup函数只是简单的调用__tmainCRTStartup函数,其代码如下:
#ifdef _WINMAIN_
int WinMainCRTStartup(void) // UNICODE版本:int wWinMainCRTStartup(void)
#else
int mainCRTStartup(void) // UNICODE版本:int wmainCRTStartup(void)
{
__security_init_cookie();
return __tmainCRTStartup();
}
<2>__tmainCRTStartup函数调用main/WinMain函数,其代码如下:
int __tmainCRTStartup(void)
{
//……
#ifdef _WINMAIN_
GetStartupInfo( &StartupInfo );
……
mainret = WinMain(…) // UNICODE版本:int wWinMain(…)
#else /* _WINMAIN_ */
mainret = main(…); // UNICODE版本:int wmain(…)
//……
/*
* do C++ constructors (initializers) specific to this EXE
*/
if (__native_startup_state == __initializing)
{
_initterm( __xc_a, __xc_z );
__native_startup_state = __initialized;
}
//……
if ( !managedapp )
exit(mainret);
//……
return mainret;
}
可以推测上述_WINMAIN_宏是与subsystem相关的一个VC编译器内部宏。
(3)应用程序的启动
程序是一连串静态的指令,而进程是一个容器,它包含了一系列运行在这个程序实例上下文中的线程使用的资源。
当我们用鼠标点击磁盘上的可执行文件App.exe后,App.exe被装载至内存后就形成了进程,因此进程是一个正在运行的程序的实例。一般我们可以同时启动多个App.exe的实例,即创建同名的多个不同ID的进程。
进程内核对象不是进程本身,仅仅是一个系统用来管理这个进程的一个小的数据结构(PCB,Process Control Block)。进程是不活泼的,程序代码的执行是线程的工作,线程是进程内执行代码的独立实体。一个进程要完成任何的事情,它必须有一个运行在其地址空间的线程。Windows操作系统调用CreateProcess函数创建一个新的进程和该进程的主线程,返回 LPPROCESS_INFORMATION信息。主线程通过执行C/C++运行期启动代码初始化C/C++运行期库,C/C++运行期启动代码又会调用main函数。主线程在运行期间,可以调用CreateThread创建辅助线程,即所谓的多线程。
当App.exe进程的主线程入口函数main/WinMain返回后,启动函数mainCRTStartup(__tmainCRTStartup)调用C/C++运行期退出函数exit(参数为main/WinMain返回值)。
全局变量(包括内置类型和类类型)存储在全局区。exit函数会销毁所有全局的或静态的C++对象,全局C++对象的构造函数在进入应用程序的Entry Point之后,调用用户编写的main/WinMain之前调用。编译器调用atexit记录全局对象的析构函数(dynamic atexit destructor for ‘x’,多个函数形成多播链),在exit退出时(main/WinMain返回之后)调用。
在exit处F11进入crt0dat.c中的doexit,其中析构函数的调用代码片段如下:
/*
* do _onexit/atexit() terminators(if there are any)
* These terminators MUST be executed in reverse order (LIFO)!
*/
_PVFV * onexitbegin = (_PVFV *)_decode_pointer(__onexitbegin);
_PVFV * onexitend = (_PVFV *)_decode_pointer(__onexitend);
// 依次调用atexit注册的函数(oneixtbeginàonexitend)
HelloCPP2.cpp程序在VC6中,没输出“X deconstructor”,在VC2005中输出“X deconstructor”。
最后(/* return to OS or to caller */)调用ExitProcess(doexità__crtExitProcess)促使操作系统终止应用程序。
参考:
《深入浅出MFC》第一章àWin32程序基本概念à进程与线程à一个进程的诞生与死亡
《Windows核心编程 第五版》4.1编写第一个Windows应用程序。