23.8 The MFC program flow


In this long section we're going to try and explain the execution of a Windows program built using the MFC framework (which is also called the AFX/MFC framework). The story is not all that clearly told in the Microsoft documentation, but one can check out the details by tracing function calls in the debugger.

The author has a lingering anxiety that he's made some mistakes in this account. On the other hand, he has a feeling of boredom and impatience about further examining the innards of MFC. One of the differences between science and computer science is that in science you are investigating things that are going to be around for a long time. In computer science, your objects of investigation are eternally moving targets, fleeting blossoms that bloom, shrivel, and blow away. Given that whatever knowledge one gets about Windows MFC programs is going to be worthless in ten years , it's hard to get super-motivated about getting every last detail right.

Even so, you do need a mental image of what any system is when you write programs for it. The 'system story' helps you organize your knowledge about what things work and what things don't work. Whether the story is correct in every detail doesn't really matter, just so long as it enables you to write good code.

Think of this The MFC Program Flow section, then, as a kind of 'creation myth' about the origins of your active MFC window. When you face other kinds of systems in the future, you'll need the ability to formulate creation myths of your own.

We'll give two versions of the creation myth, first a simple and practical one, and then a more complicated one.

All you really need to know

In simplest terms, here's the sequence of function calls that happen during the start of an MFC program.

  • The CPopApp constructor.

  • The CPopApp method InitInstance .

  • The CPopDoc constructor.

  • The CPopView constructor.

  • The CPopView method OnCreate .

Now the program starts running. The following CPopView methods affect the view.

  • OnDraw is automatically called when the view needs to be redrawn, because (a) it's new, or (b) it's been uncovered, or (c) it's been resized, or (d) it's had a call to Invalidate .

  • OnSize is automatically called when the view is resized.

The various mouse, keyboard, toolbar, menu, and dialog control processing messages get called automatically. According to how the methods are coded, they can affect either the CPopView or the CPopDoc . When something is changed, we usually force an update of the view with a call to the CPopView method Invalidate . Or, more indirectly, we call the CPopView method OnUpdate and let it make the call to Invalidate . If you want the application to run on its own, you can put some code into the CPopApp method OnIdle .

When the program closes down the destructors are called in this order.

  • The ~CPopView destructor.

  • The ~CPopDoc destructor.

  • The ~CPopApp destructor.

That's all you really need to know about the MFC program flow. But now, in case you're interested, we'll tell you a little more.

The invisible WinMain function

Any application, such as the Pop program, defines its own type of application as a child of the CWinApp . Pop uses a class called CPopApp , which is a child of the CWinApp class which is in turn a child of the CWinThread class. The CPopApp class has its prototype in Pop.h .

If we look in Pop.cpp we find the declaration of a static variable object of the type CPopApp . In general, your code is going to have exactly one object of the relevant kind of 'App' type. The AppWizard puts the code into Pop.cpp as follows . (By the way, your application object doesn't have to be called ' theApp ,' you can call it anything you like; the crucial thing is that there be exactly one CPopApp object declared in your Pop.cpp .)

 // The one and only CPopApp object  CPopApp theApp; 

There is a CPopApp() constructor defined in Pop.cpp that carries out some of the initialization of theApp . And the rest of the initialization happens inside the program's WinMain , which is what we'll talk about next .

As you know, a C program always has a primary function called main . When the program starts, it starts running at the beginning of the main code, and when it gets to the end of the main code it exits. In a similar way, a Windows program always has a primary function called WinMain which starts, runs, and ends your program. In the older style of Windows coding, the programmer had to explicitly write out the WinMain code. But this code is almost the same from program to program, so the designers of the MFC have hidden the WinMain code from you. It's built-in as part of the AFX within which the MFC classes are patterned . The 'invisible' AFX WinMain function has a skeleton that looks like the following. To keep things simple, we've left out all the function arguments.

 WinMain()  {      pApp->InitApplication()      pApp->InitInstance()      pApp->Run() /* Calls the global ::DispatchMessage() or pApp->OnIdle      pApp->ExitInstance() */  } 

We describe our WinMain function in terms of a CWinApp *pApp pointer which is really just &theApp , that is, a pointer to the single CPopApp object, theApp .

This version of the WinMain code (including the comment) mentions six functions, and five of these are member functions of CWinApp . They are virtual members of CWinApp . They have their own default code, but since they are virtual functions, we are free to override them. We can change the code for the member functions of CWinApp by overriding their implementations for our CPopApp as defined in our Pop.cpp file.

InitInstance , Part 1: Initializing the CMainFrame

Here are the first two functions mentioned in our little outline of the hidden WinMain function.

  • CWinApp::InitApplication

  • CWinApp::InitInstance

Ordinarily you don't worry about overriding InitApplication , and you never even see the code for it.

By default, AppWizard expects you to override InitInstance , and AppWizard will put some code for it in your Project.cpp , file, whatever your 'project' name happens to be. A lot happens in here. We're going to be talking about this function for all of this and the next two subsections.

It turns out not to be a good idea to try and do any kind of heavy-duty initialization inside the CPopApp() constructor, as the program is in some sense not really ready yet at that stage. So this is one thing that we use InitInstance for, as a place to put initialization code. If you want to allocate something like a global memory bitmap resource at program startup, you would ordinarily put code for this at the start of our CPopApp::InitInstance ; and when you then want to deallocate that memory, you would do it in the destructor CPopApp()::~CPopApp .

The default CPopApp::InitInstance code takes care of opening the main frame window. This is the place where the particular selections you made for the kind of program you want are implemented. The choices about your Document-View architecture go in here, for instance. The code looks (in part) like this; you can see the whole thing in Pop.cpp .

 BOOL CPopApp::InitInstance()  {      /* Register the application's document templates. Document          templates         serve as the connection between documents, frame windows and          views. */      CMultiDocTemplate* pDocTemplate;      pDocTemplate = new CMultiDocTemplate(IDR_POPTYPE,          RUNTIME_CLASS(CPopDoc), //your CDocument          RUNTIME_CLASS(CChildFrame), // your CMDIChildWnd          RUNTIME_CLASS(CPopView)); //your CView      AddDocTemplate(pDocTemplate);      // create main MDI Frame window      CMainFrame* pMainFrame = new CMainFrame; //your CMDIFrameWnd      if (!pMainFrame->LoadFrame(IDR_MAINFRAME))          return FALSE;      m_pMainWnd = pMainFrame;      // Parse command line for standard shell commands, DDE, file open      CCommandLineInfo cmdInfo; //Create a default cmdInfo object      ParseCommandLine(cmdInfo); //Copy any command line params      // Now Dispatch commands specified on the command line.      // The default startup cmdInfo sends FileNew to your App,      // which creates a document, a child frame, and a view.      if (!ProcessShellCommand(cmdInfo))          return FALSE;      // The main window has been initialized, so show and update it.      pMainFrame->ShowWindow(m_nCmdShow);      pMainFrame->UpdateWindow();      return TRUE;  } 

The pDocTemplate is defined in terms of the CPopDoc , CChildFrame , and CPopView classes which are defined in, your project *.h and *.cpp files with the related names . Note that these specialized classes inherit from, respectively, the standard MFC classes CDocument , CMDIChildWnd , and CView . The template tells the application what kind of a document and view structure you have, answering questions like: are you using the SDI or the MDI; and is there more than one type of document you might open?

Once the app knows about your template structure, a main frame window is created by using new to call the CMainFrame constructor. Your CMainFrame class inherits from the MFC CMDIFrameWnd class and was defined by AppWizard in your project files MainFrm.* . If you have some global variables that will be used by all of your files, you might want to keep these variables in your CMainFrame class and initialize them in the constructor. And if you need to allocate and deallocate some kind of global pointer, you might want to do it in the CMainFrame constructor and destructor.

The call to LoadFrame(IDR_MAINFRAME) attaches whatever resources you've defined in your *.rc file for your main frame: things like menu bars and toolbars . Once we have a good pointer to a CMainFrame object, we store this in the CWnd* m_pMainWnd field of the CWinApp class, so the app can use it whenever it needs a pointer to its main window.

The next important group of calls use a CCommandLineInfo class that's basically a data structure. We initialize a cmdInfo instance of the class with the default values set by the CCommandLineInfo constructor, and then we call the CWinApp::ParseCommandLine function to put any command-line arguments into the cmdInfo variable.

You might wonder how your app could receive command-line arguments anyway? After all, you're running Windows, not DOS or LINUX, and you never even see a command-line anymore! Well, you may have noticed in Windows Explorer that if you click on a file name, the file will often get opened by the application associated with it. Thus, clicking on a *.dsw file will typically start up a Visual Studio session, while clicking on a *.doc file may open up a session of Microsoft Word (if it's on your computer). When these apps are started up in this way, the Windows operating system passes them as command-line parameters saying, in effect, 'Do the File Open command on the following file name . . .'

If there are no command-line parameters of this type, the default settings for a CCommandLineInfo object like cmdInfo will tell your app to start by processing a File New command. And this is what happens when we hit the CWinApp method called ProcessShellCommand in the bit of code

 if (!ProcessShellCommand(cmdInfo))      return FALSE; 

[If you haven't looked at much C code, this line may seem odd. But really it's just a cautious way of saying ProcessShellCommand(cmdInfo) . A typical programming trick is that when you call a function that conceivably might not work correctly, you have it return the Boolean value TRUE if all goes well, and return FALSE if there is a problem. That way the calling function can use this return value to pass on the bad news to the rest of the program. FALSE is really another name for the integer 0, with TRUE normally being 1, although any value other than 0 is normally also acceptable as TRUE .]

InitInstance , Part 2: Initializing the CDocument and CView

When your app hits the ProcessShellCommand call inside InitInstance (or when a user selects File New ) it needs to create a new CDocument and an attached CView . This sets off a whole cascade of MFC calls. In terms of initialization, the three key calls in the cascade are: (a) the CDocument constructor, (b) the CView constructor, and (c) the CView::OnCreate method.

Let's say a bit more about these three function calls.

Something you probably remember about C++ programming is that you're not allowed to initialize a variable as part of its class member declaration. If a variable isn't a member of a class if it's a global variable, or a temporary variable inside a function, for instance then you can declare and initialize it with a line like int crittercount = 21; or int crittercount(21); . But C++ does not allow you to initialize a variable inside a class definition. A variable which is a member of a class needs to be initialized in the class's constructor or possibly somewhere else.

The CDocument constructor is the place to initialize and/or allocate any variables that belong to the CDocument . And if you have variables in your CView , you need to initialize these as well. Normally we initialize as many of these as possible in the CView constructor, but there will sometimes be CView variables that should be initialized a bit later: inside the CView::OnCreate method.

The reason why it's not always possible to initialize all the CView variables inside the CView constructor is that when this constructor is called, the CView window isn't really 'fully in existence.' When you're inside the CPopView constructor, for instance, if you try and use one of the CView methods it's likely not to work, because the special HWND window index called m_hWnd that's hidden inside the CView is still 0. And if you try and use the CView::GetDocument() function in here it'll crash because the document is still 0 as well. The only things that are safe to initialize inside the CView::CView constructor are things that don't use the CDoc and which don't in any way depend on the CView already existing as a graphical window object.

You need to wait until the follow-up CView::OnCreate function to do any initialization of CView that involves looking, say, at the size of the view, or looking at its owner document. You won't find the CPopView::OnCreate function listed in your CPopView.cpp file, but you can include it by adding a 'message handler for WM_CREATE.' More about message handlers in a bit.

InitInstance , Part 3: Putting the Windows on the screen

The last two function calls in InitInstance are the CWnd methods, ShowWindow and UpdateWindow . Note that they are called by the app's newly initialized m_pMainWnd field.

The ShowWindow function figures out the appropriate size for the main frame, and then draws the frame around that window on the screen. The UpdateWindow function fills in the internal 'client area' of the window. This will involve a cascade of function calls, as filling in the client area of the main frame involves first of all, creating and drawing a child frame window, and then creating and drawing a CView window inside the child. Another way of putting it is that when a CView::UpdateWindow is called, an UpdateWindow function gets called for all of that window's child windows.

Now of course before your view can appear on the screen, it has to have been initialized. But we're cool with that, it already happened when the ProcessShellCommand told our WinApp to behave as if it had received a File New menu selection. We processed the CDocument constructor, the CView constructor, and the CView::OnCreate method.

So now the main window's call to UpdateWindow has generated a CPopView::Update window call. And what happens then? A call to CView::UpdateWindow generates a call to CView::OnPaint . If you look for CPopView::OnPaint in the Pop project files, you won't find it though. It turns out that CView::OnPaint in turn calls CView::OnDraw . And you will find the CPopView::OnDraw method in the CPopView.cpp file.

For most of our MFC programs, the OnDraw method of our view is where the rubber hits the road or, more accurately, where the pixels hit the screen. Let's look a little more closely now at the MFC run cycle.

The MFC run cycle

If you look back at our outline of the AFX 'hidden WinMain ' function, you'll see that the following three functions are mentioned in it.

  • CWinApp::Run

  • ::DispatchMessage

  • CWinApp::OnIdle

Your app spends most of its time inside the CWinApp::Run function. What this function does is to repeatedly see if there are any 'messages' to process. If there is a message, then the global ::DispatchMessage function is used to send the message to the piece of your code that is designed to handle it. When there is no message to process, then the CWinApp::OnIdle function is called until eventually there is another message to process. (Recall from Chapter 22: Topics in C++ that when we're doing MFC programming, we often put a :: in front of the names of global functions that are not the member of any class, just to remind ourselves that they're global.)

Talking about messages is going to take a long time, so let's put that off for the next section. Right here and now let's just say something else about OnIdle . As we discuss in Chapter 6: Animation, if we want to set in action a process which runs all the time, a good way to do this is to write a function CPopApp::animateAllDocs that executes whenever OnIdle is called. In the Pop Framework, we do this by replacing the base class CWinApp::OnIdle function with a CPopApp::OnIdle which calls the base class CWinApp::OnIdle and then calls a function we write as animateAllDocs . (As mentioned in Chapter 6: Animation, the CPopApp::OnIdle is actually slightly more complicated than what we show here.)

 BOOL CPopApp::OnIdle(LONG lCount)  {      CWinApp::OnIdle(lCount); //Do the base class WinApp processing.      animateAllDocs(); /* Step through all the docs and update each          doc and all its views. */      return TRUE; //Keep doing it over and over.  } 

The code for animateAllDocs is sort of ugly, so we printed it in Section 23.6. The net effect of the animateAllDocs function in our Pop program will be as follows.

  • Step through the list of open CPopDoc objects and for each of these documents call a CPopDoc::stepDoc function which will

  • call a cGame::step method to update the positions of a list of polygonal critters that are stored in the document.

  • Make a call to CDocument::UpdateAllViews which will

  • send down a call to a CPopView::OnUpdate for each of the document's views, and for each view this function call will

  • use a CView::Invalidate call that generates a call to CPopView::OnDraw , which will

  • use the current cGraphics to show an image on the screen.

But before we can start having that much fun, we will need to explain about Windows messages and the ::DispatchMessage function.

Messages and message handlers

The Windows operating system maintains an internal message queue. Whenever you use the mouse or the keyboard a message is placed on the queue. Windows also puts a number of messages on the queue by itself. Windows works by continually taking messages off the queue and passing them to the window that they're intended for. The AppWin::Run function inside your hidden WinMain apportions the messages among the windows that belong to your app. The function that it uses to send off the messages is a global function called ::DispatchMessage .

At the low level, Windows represents messages by a kind of structure called MSG . The Windows MSG structure can be written as follows.

 struct MSG  {      HWND   hwnd;      UINT   message;      WPARAM wParam;      LPARAM lParam;      DWORD  time;      POINT  pt;  }; 

The first four of the special Windows variable types on the left are really all the same as 32-bit integers, while the last two are 64-bit integers, though a POINT can also be thought of as a pair of 32-bit integers. Even though it is customary to speak of the MSG structures themselves as messages , the MSG structure has a field whose name is ' message .'

The hwnd field of a MSG structure is an ID number which tells the Windows operating system which window (or which CView , in MFC terms) the message is intended for. If you press a key or take a mouse action, you will generate a MSG whose numerical hwnd ID value is equal to the numerical HWND index value for a currently active window.

The message field of a MSG structure is an integer code number describing what type of message this is. All of these message codes have names that start with WM_. You can look at a long list of them in Visual Studio by selecting the Help Index dialog and typing in WM_. There are several hundred different WM_? messages, but most of them are rarely used.

It's common to blur the distinction between the MSG structure as a whole and the value of its message field. Thus it's common to speak of the operating system sending a window, say, a WM_CREATE message, even though strictly speaking, the window is really getting a MSG structure whose message field is WM_CREATE.

The wParam and lParam fields have extra information about the MSG . In the old-style Win32 programming, you need to learn what kind of information each message puts into these parameters. But in MFC programming, this is automatically done for you; the wParam and lParam information gets repackaged into an easier-to-use form. The time and pt fields of the MSG are rarely used.

In MFC, the code for processing a message takes the form of a 'message handler' function that lives in one of your CWnd classes, usually in your CPopView , but occasionally in your CMainFrame or CChildFrame .

Table 23.5. Windows messages and their MFC handler methods.

Message

Handler function

Comments

WM_CREATE

OnCreate(LPCREATESTRUCT lpCreateStruct)

Is called after the constructor. The best place to initialize your CView data

WM_SIZE

OnSize(UINT nType, int cx, int cy)

nType tells you if you've just Minimized or Maximized the window. ( cx , cy ) is the size of client area

WM_PAINT

OnPaint()

Is followed by a call to OnDraw , which is where we normally write our graphics

WM_LBUTTONDOWN

OnLButtonDown(UINT nFlags, CPoint point)

nFlags tells you which buttons are down. point is the cursor position in client-window coordinates

WM_MOUSEMOVE

OnMouseMove(UINT nFlags, CPoint point)

All mouse message handlers pass the same arguments as in OnLButtonDown

WM_VSCROLL

OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

nSBCode tells what kind of scroll request, nPos holds the scroll bar position

WM_CHAR

OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

nChar is the ASCII code for the key if it's a letter key

WM_KEYDOWN

OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

nChar is the Windows VK_ ' Virtual-key code' for the key

WM_DESTROY

OnDestroy()

Gets called right before the destructor at exit

Table 23.5 lists a few of the common Windows messages, along with the names of their MFC message handler functions and some information about the arguments that get passed to the message handler.

At this point, let's make a few remarks about the difference between OnChar and OnKeyDown . Generally speaking, you use the first function for doing wordprocessing and you use the second for using the keyboard as a control panel.

Although we can't seem to find them in the Microsoft online documentation, the virtual-key codes returned by OnKeyDown are all defined in the WINUSER.H file that lives in the INCLUDE subdirectory of your compiler. These codes have all have names starting with VK_ followed by a simple name for the key. The virtual-key code for the F1 key, for instance, is VK_F1, the virtual-key code for the Left Arrow key is VK_LEFT. The virtual-key code VK_A for a letter-key like A is in fact the same as the ASCII code 'A' for A.

If you press the A key alone, as opposed to pressing the A key with the Shift key, you'd really like to get the ASCII code for 'a' , as opposed to the ASCII code 'A' . How do we do this?

Well, when you press a letter key, your CView gets an OnChar message handler call as well as an OnKeyDown message handler call. (The WinApp::Run function makes this happen by using a global function called ::TranslateMessage .)

So, if you care about the case of a letter key, then you look at the OnChar function input, as the nChar argument to this function will be the correct upper-or lower-case ASCII code for the letter that the user typed in by pressing a letter key and by possibly pressing the Shift key at the same time.

Usually we don't do anything with the other OnChar and OnKeyDown parameters, by the way. You might, for instance, be tempted to try and use the 'repeat count' variable nRepCnt in a game program to detect when the user is holding an Arrow key down, but this turns out not to work as reliably as does directly polling the key state with a repeated call from WinApp::OnIdle to the global ::GetAsyncKeyState(int vKey) function.

Program termination

When you close down your file, either by ending the application or by using the File Close menu selection, that means you are closing the view and the document, so the destructors will be called: first ~CView and then ~CDocument . If your program is closing, you'll then call the ~CMainFrame destructor, and last of all the ~CWinApp destructor gets called. In general, destructors always get called in the reverse order of the corresponding constructors.

Another spot where you might do some processing at program exit is in the OnClose or OnDestroy functions of CView . If you want code that asks the user whether or not he or she wants to save some file information, this call usually issues from inside the OnClose function, which is called after the File Close menu selection is made, or when the view is killed by clicking on the 'X' or 'kill' box in the upper right corner.

Summing up, the sequence of function calls when you exit the program is as follows.

  • CView::OnClose

  • CView::OnDestroy

  • CView::~CView destructor

  • CDocument::~CDocument destructor

  • ~CMainFrame destructor

By the way, when you click the kill box at the upper right corner of your CView , it calls the CWnd::OnSysCommand(UINT nID, LPARAM lParam) message handler for your CView , with an SC_CLOSE in the nID parameter. OnSysCommand reacts by invoking your CView::OnClose message handler, if there is one. If your CView doesn't have an OnClose message handler (some hard-to-get-rid-of programs have an OnClose which puts up a message box asking 'Are you sure you want to exit?'), then the default CWnd::OnClose calls your CView::OnDestroy function and then passes control to the window's destructor.



Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net