对于程序员来说,如果要提高编程效率,一个好用的,功能强大的并且可以得心应手使用的编程工具往往会给我们程序员带来莫大的方便。其实对于现在的编程工具来说,使用哪
一种工具都不是问题的关键,重要的是你能够使用到什么程度,毕竟现在的工具都是非常的强大,对于一般的编程任务来说还没有不能够胜任的工具,否则的话恐怕他就不可能在这个世界上存在哪怕是只有一个月的生命。但是根据个人所好以及周围的人的影响,我们都会去使用某一种或者几种工具。比较Visual Basic 、C++ Builder和Delphi等编程工具,用VC++编写Windows应用程序可以说是最富于挑战性和艰巨性。在本文中我无意去比较各种工具的好坏,仅就我自己学习Visual C++的MFC的体会和心得拿出来与大家一起分享和交流,也希望可以结识更多的志同道合的朋友。
就我个人的偏见,学习VC++就应该要学习他的类库MFC(Microsoft Foundation Classes)。也许有的人一听说MFC就有点望而生畏,这是可以理解的,毕竟Microsoft虽然给了我们一个强大而且非常复杂的类库,但是没有给我们带来学习他的好的方便之处。回想自己学习MFC时的无助和迷茫,以及所走过的弯路,现在想起来还心有余悸,虽然我现在也还是处于非常初级的初级入门阶段,但是还是很乐意把自己的心得和体会拿出来一起与大家分享。也希望得到大家的指点。
一、SDK应用程序结构
我学习MFC之路可是从windows编程开始的(可能这一开始就是弯路了^_^)。首先也请大家跟着我一起看一个SDK应用程序结构的Windows应用程序。当然也是经典的“Hello world!”了,编写的过程就不必罗嗦了,下面给出他的主要源代码(我使用向导生成的,但是为了阅读的方便经过了一点点的编辑):
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) // TODO: Place code here. MSG msg; ……………………………… MyRegisterClass(hInstance); if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } // Main message loop: while (GetMessage(msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } }
return msg.wParam; } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { …………………… return TRUE; } //窗口函数WndProc(),回调函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; …………………… switch (message) { case WM_COMMAND: ……………… break;
hdc = BeginPaint(hWnd, ps); // TODO: Add any drawing code here... RECT rt; GetClientRect(hWnd, rt); DrawText(hdc, szHello, strlen(szHello), rt, DT_CENTER); EndPaint(hWnd, ps); break; case WM_DESTROY: …… default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } |
SDK之"Hello World"源程序
首先让我们看一下这个程序的结构,WinMain()是函数的入口点,该函数的主要任务是完成一些初始化的工作和维护了一个消息循环。他们的工作流程如下:入口(WinMain())---à MyRegisterClass()---->InitInstance ()-àwhile消息循环。函数由入口开始执行,之后调用 MyRegisterClass()注册窗口类,之后InitInstance ()生成并显示窗口,这样之后,就完成了一个窗口的初始化工作了(当然,在 MyRegisterClass(),InitInstance ()中都需要调用相应的API函数来具体的实现,不过我这里重点分析的是他的结构,所以不考虑他的具体实现细节),然后就是维护消息循环,至此,这个程序的基本结构就差不多建立了。以后程序的运作就靠个消息循环来推动了。
现在,再让我们看看那个消息循环的结构,在例子程序中,我们是要程序在窗口中输出一句"Hello World"。在主程序中我们似乎已经把应用程序的框架全部分析的滴水不漏了,但是没有看到要求程序输出"Hello World" 呀?这就是Windows消息的作用了,我们当然还记得刚刚我们说过主程序还维持了一个消息循环,不错,就是在这个循环里面大有文章。Window应用程序的特点就是消息驱动,当系统或者用户要求应用程序完成某一个任务的时候,所依靠的就是消息,系统会把用户的要求或者系统的要求放到一个消息结构中,然后发送给应用程序,再去处理。我们现在来看看应用程序是怎么来完成我们的任务的。在应用程序初始化完成之后,调用了一个显示窗口的API函数,所以系统知道了程序要显示窗口了,此时(注意,这里就是产生消息的时机),此时就会在消息队列中产生一个WM_PAINT消息,这样,应用程序的消息循环就可以捕捉到这个消息并且将它发送给窗口函数(注意,这个函数是由系统调用的),然后窗口函数就处理这个消息,我们就是在他处理这个消息的时候让他完成我们的任务的。从这个过程中我们可以看到,如果我们要与程序交互的话,需要做得仅仅就是选择适当的时机让系统产生消息了。现在,我们终于可以完全明白SDK的程序的运作过程了。
OK,一个SDK的应用程序的框架就这样被建立了,下面我们再来看看如何建立一个MFC的应用程序的框架,以及这两者之间的对应关系。
二.MFC应用程序结构
在《明明白白看MFC之程序框架(一)》中我分析了一个经典的应用程序的结构,现在可是要进入主题"MFC应用程序结构"了.应用程序有好多种,为了能够更清楚地与前面的文章形成对比,我们在这里看一个SDI的应用程序,当然例子还是经典的Hello World了。在使用向导生成应用程序后,会发现有好几个文件,首先我们不管有哪些文件,按照程序执行得主线抽取主要的源程序分析一下再说(因为MFC生成的应用程序不是很方便阅读,所以在这里我将他们重新编辑了)。
CHelloWorldApp theApp; int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp();
// AFX internal initialization if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure;
// App global initializations (rare) if (pApp != NULL !pApp->InitApplication()) goto InitFailure;
// Perform specific initializations if (!pThread->InitInstance()) { if (pThread->m_pMainWnd != NULL) { TRACE0("Warning: Destroying non-NULL m_pMainWnd/n"); pThread->m_pMainWnd->DestroyWindow(); } nReturnCode = pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run(); InitFailure: ……………………
AfxWinTerm(); return nReturnCode; }
BOOL CWinApp::InitApplication() { if (CDocManager::pStaticDocManager != NULL) { if (m_pDocManager == NULL) m_pDocManager = CDocManager::pStaticDocManager; CDocManager::pStaticDocManager = NULL; }
if (m_pDocManager != NULL) m_pDocManager->AddDocTemplate(NULL); else CDocManager::bStaticInit = FALSE;
return TRUE; } BOOL CHelloWorldApp::InitInstance() { AfxEnableControlContainer();
………………………………
// Change the registry key under which our settings are stored. // TODO: You should modify this string to be something appropriate // such as the name of your company or organization. SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(); // Load standard INI file options (including MRU)
// Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views.
CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CHelloWorldDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CHelloWorldView)); AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE;
// The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow();
return TRUE; } BOOL CWinApp::InitInstance() { return TRUE; }
|
MFC应用程序之Hello World
咋一眼看上去,好像这个程序无从下手分析,甚至连程序的入口点都找不到。其实,上面的程序还是经过整理后才有如此模样。好了,一样的来看看这个程序是怎么运行的吧(要注意的事上面的程序来自于不同的文件,这里排版在一起只是为了更清楚地表示程序的结构,至于MFC的文件组织我会在下面一个话题中具体的分析,这里可以暂时不考虑)。
首先,在程序的开始处,首先定义了一个全局变量theApp,我们现在只需要知道他代表了整个程序的存在,然后程序开始介入入口点。有没有搞错,入口点在哪里?不及,其实int AFXAPI AfxWinMain()就是这个程序的入口点,奇怪吧!不过没有关系,就好像我们第一次看到C语言中的main()函数一样,只要了解就可以了。在AfxWinMain()中分别调用了一些类的成员函数,仿照前面的分析方法,也可以画出一个程序执行路径图。入口点----〉AfxGetThread()------〉AfxGetApp()-------àAfxWinInit()-------àpApp->InitApplication()-----àpThread->InitInstance()------àpThread->Run()。可以看到,程序一样有一个执行的线索可循,但是,相对于SDK来说,如今已经面目全非了,过去的那种清晰的程序结构在这些程序中也有吗?答案是肯定的,只不过他们的具体实现在MFC中都进行了包装而已,那么,还是来看看这个应用程序是如何启动并且运行的吧。
程序由AfxWinMain()开始运行后,首先调用了AfxGetApp()来获取应用程序的对象指针pApp,然后通过这个指针调用有关的成员函数来完成初始化和启动工作,最后就调用了Run()函数,在这里,Run()函数就是代表了SDK中的消息循环。
事情的发展在预料中进行着,但是似乎还遗漏了一点什么似的?不错,在上面我们的确是还有一样工作没有完成,这就是我们需要的”Hello World”好像还没有输出来!这不是我的疏忽,而是故意的安排,因为MFC中采用了一种全新(当然是相对于SDK来说的了)的消息处理机制,至少在表面上来说是这样的。然而,在这里我不打算一下子就把问题解决掉,毕竟这有点复杂,等我们明白了MFC的文件之间的关系后我会在回答这个问题。我没有想到这篇文章会这么长,刚刚开始的时候我以为我可以一下子就把这个问题说清楚地,但是事实上我写作的思路也像我分析程序时一样,竟然是一个漫长的过程!所以我也有必要提醒一下自己以及读这篇文章的朋友,应该休息一下了。我现在都开始庆幸我把这个问题分成好几个小问题来解决了,我会在接下来的话题中继续讨论的事MFC程序所生成的文件以及它们之间的调用关系.