*
MSDN*
Results by Bing
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ

November 1996

Microsoft Systems Journal Homepage

C++ Q&A

Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Click to open or copy the SCRB_BBS project files.

Click to open or copy the SCRB_ABR project files.

QI'm writing a Single Document Interface (SDI) application using MFC. I realize Microsoft is pushing a document-centric world, and that's fine, but there are still people who run the application and then pick which document they want to open (silly people!). The problem I have is that, whenever the application starts up, MFC automatically does a File New operation to create a blank document. In my case, File New is fairly expensive; it takes a lot of time, may have to connect to a Web site, and requires the user to pick a document template. I want to avoid going though the File New process unless the user actually wants a new document. What would be a reasonable way to do this? Keep in mind that I still want to let Windows¨ 95 users do New XYZ Document from places such as the desktop (right button popup menu) and Windows Explorer (File New command).

Ed Silky

Calico Technology

AThe problem is that MFC really likes to have a document object in an SDI app. With MDI apps, you have an empty main frame window that creates new child frame windows with documents and views in them every time the user opens a new document or does File New. In SDI apps, MFC has just one frame with a single document/view that's reused for all docs, including new ones.

The standard AppWizard-generated program contains the following lines in the startup code in your application object's InitInstance function:

 CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
   return FALSE;

This code processes the command line passed when your app is invoked. Remember the old days when you used to run a program by typing a line at the DOS prompt? For example

 MYPROG /s /p /xyz foo.doc

There's still a command line, even in Windows 95, you just don't normally see it. When a user double-clicks on a document file in the Explorer window, Windows 95 finds the program associated with that type of file, then invokes that program with the filename on the command line. Likewise, if you do a right-click Print, Windows 95 will invoke

 MYPROG /p foo.doc

CCommandLineInfo holds information about the command line in abbreviated form; CWinApp::ParseCommandLine parses the (barely) human-readable command line into CCommandLineInfo; and CWin-App::ProcessShellCommand processes the command. The default implementation of CWinApp::ParseCommandLine recognizes /p to print, /Embedding to run as an embedded OLE server, and /Automation to run as an OLE automation server. If Windows 95 passes the name of a document, MFC will call CWinApp::OpenDocumentFile to open it when the app starts up. All of this happens automagically, without you having to write a single line of code.

If there are no command-line arguments (which is what happens when you simply invoke you app from the Windows 95 Start menu), CWinApp::ParseCommandLine has nothing to parse. CCommandLineInfo::m_nShellCommand defaults to FileNew since the constructor initializes it like this:

 CCommandLineInfo::CCommandLineInfo()
{
   m_bShowSplash   = TRUE;
   m_bRunEmbedded  = FALSE;
   m_bRunAutomated = FALSE;
   m_nShellCommand = FileNew; // default cmd = File New
}

When ProcessShellCommand sees m_nShellCommand = FileNew, it calls CWinApp::OnFileNew, the same function MFC calls when the user does a File New command. This is why your SDI app does a File New operation when you run it.

You can probably guess where all this is leading. All you should have to do is avoid the File New by writing something like

 cmdInfo.m_nShellCommand = FileNothing; // do nothing

before calling CWinApp::ParseShellCommand. In fact, that's what I did-only to discover it fails miserably. The problem is, File New is where MFC creates the main frame window, deep in the code for CWinApp::OnFileNew. So if you don't do File New as the first thing, your app won't have a main window-yikes! If you try to run it (I did), it'll bomb with a TRACE message from MFC: "m_pMainWnd is NULL in CWinApp::Run - quitting application."

So much for CComandLineInfo. Well, the next thing I tried was creating the main frame myself in InitInstance, the way it works for MDI apps. I'll spare you the details, but when I did File New, MFC created another main frame-I had two main SCRIBBLE windows! Then I tried overriding OnFileNew so it created the main window, but not the document/view. That too failed. There are just too many places where MFC expects to have a valid document/view.

I briefly considered implementing a dummy document type called CEmptyDoc that had no data and did nothing. Just a blank document to make MFC happy when the app started up. But that meant adding another document template and rewriting a bunch of code to make MFC use it when the app starts up, instead of asking the user which document template to use like it normally does. Yuk.

Finally, I realized the simplest solution of all, the thing I should and would have thought of first if I didn't write a Q&A column and wasn't always looking for clever ways to impress readers by overriding obscure virtual functions. It's trivial, really: who says your implementation of OnFileNew really has to create a new document?

The way you normally create a new document in MFC is by overriding CDocument::OnNewDocument to initialize your doc structures and do whatever you need to create an empty document. But why not have a CDocument with two empty states: a "so empty it's invalid" unintialized state that can't be used, and an "initialized but empty" state that results from doing a File New command? When the app starts up, the doc is in the first, uninitialized state and most commands are disabled. It looks to the user like an empty frame. Then, when the user does File New, the doc gets initialized.

Figure 1 shows how I modified the Scribble program from the MFC tutorial to accomplish this. I began by adding a new flag, m_bInitialized, to CScribbleDoc. As you would expect, this flag records whether my scribble doc is initialized or not. When Scribble starts up, MFC goes through its normal command-line processing. CCommandLine-Info::m_nShellCommand defaults to CCommandLine-Info::FileNew, so MFC calls CWinApp::OnFileNew, which eventually calls CScribbleDoc::OnNewDocument. My implementation of CScribbleDoc::OnNewDocument does the same raw initialization it always did, calling CScribbleDoc::Init-Document to set m_bThickPen and the other CScribbleDoc data members. But here's the interesting thing: CScribbleDoc doesn't set m_bInitialized = TRUE yet. Only when the user explicitly invokes File New does the doc get initialized. It happens in CScribbleApp::OnMyFileNew, which I set as the new (ha ha) handler for ID_FILE_NEW in CScribbleApp's message map.

 BEGIN_MESSAGE_MAP(CScribbleApp, CWinApp)
.
.
   ON_COMMAND(ID_FILE_NEW, OnMyFileNew)
END_MESSAGE_MAP()

/////////////////
// Handle ID_FILE_NEW (File New)
//
void CScribbleApp::OnMyFileNew()
{
   OnFileNew(); // do normal thing to create new doc
                // (in case this is an open doc, 
                // reuse it)
   CFrameWnd* pFrame = (CFrameWnd*)m_pMainWnd;
   ASSERT_KINDOF(CFrameWnd, pFrame);
   CScribbleView* pView = 
              (CScribbleView*)pFrame->GetActiveView();
   ASSERT_KINDOF(CScribbleView, pView);
   pView->GetDocument()->Initialize(); // initialize
                                       // new doc
}

CScribbleApp::OnMyFileNew calls CWinApp::OnFileNew to do the normal MFC thing (goes through OnNewDocument, and so on), but then it calls CScribbleDoc::Initialize to initialize the new document, which makes the document usable. The important thing here is that I used a different name for the function-not OnFileNew! A minor detail, but it makes all the difference in the world because MFC calls OnFileNew, which is virtual, from the ProcessShellCommand, and you don't want to initialize the document then. In effect, I distinguished between a File New from the menu and a File New from program startup just by renaming the command handler.

CScribbleDoc::Initialize doesn't actually do much since this is a demo program: it displays an hourglass while it sleeps for three seconds to simulate a lengthy operation such as connecting somewhere over the net. In your app, this is where you'd do your expensive Web connections.

The only piece missing from this puzzle is the UI modification. Don't forget that we're trying to simulate a "no document" condition. When the document is uninitialized, we don't want to let the user do anything like draw or use document-related commands such as Print and Save. To disable the mouse, I added a couple of lines in CScribbleView::OnLButtonDown.

 void CScribbleView::OnLButtonDown(UINT, CPoint point) 
{
   CScribbleDoc* pDoc = GetDocument();
   if (pDoc->IsInitialized()) {

 
 // as before
 ·

   }
}

(CScribbleDoc::IsInitialized is a new function I added that just returns m_bInitialized.) I did a similar thing in CScribbleView::OnDraw to paint the shadow background of an empty frame window when the doc is not initialized. This is important to tell the user there's no document open (see Figure 2).

Figure 2 Scribble without a document

To disable document commands, I was a little more clever. (Here's where I pull my impress-the-readers stunt.) The standard way to do this would be to write ON_UPDATE_COMMAND_UI handlers for each command that operates on the document. There's Print, Print Preview (but not Print Setup, which should always be allowed since it doesn't operate on a document), Save, and Save As-plus Scribble-specific commands like Pen Thick Line and Pen Set Widths. That's a lot of commands! A typical commercial app would probably have even more. What a drag to implement all those ON_UPDATE_COMMAND_UI handlers! And what if you add a new command? You might forget to write a UI handler for it. There has to be a better way.

In fact, there's an easy way to disable all document commands in one fell swoop. To understand how it works, you have to know a little about how MFC routes commands to different command targets in the system. (See my article, "Meandering Through the Maze of MFC Message and Command Routing," in the July 1995 issue of MSJ.) All command and command UI messages go through a virtual function, CCmdTarget::OnCmdMsg. CCmdTarget is the base class for any object that can handle commands, which includes windows, frames, documents, and views. When the user invokes a command from the menu or a toolbar button, Windows sends a WM_COMMAND message to the frame window. MFC translates this into its own special command event and calls OnCmdMsg with nCode = CN_COMMAND. For UI updates, MFC calls OnCmdMsg with nCode = CN_UPDATE_COMMAND_UI.

The details are not important here; what's important is the way MFC routes command messages. CFrameWnd::OnCmdMsg passes the command to the active view, then to the frame itself, and finally to the application (CWinApp-derived) object. CView::OnCmdMsg in turn first tries to handle the command itself, then passes it off to the document. Figure 3 illustrates the order of routing.

Once you know about OnCmdMsg, it's easy to disable commands for a document. I overrode CView::OnCmdMsg like this:

 BOOL CScribbleView::OnCmdMsg(UINT nID, int nCode, 
   void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
   CScribbleDoc *pDoc = GetDocument();
   return pDoc->IsInitialized() ?
      CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)       
      : FALSE;
}

Never mind all the gobbledygook arguments, the point is this: if the document is initialized, CScribbleView::OnCmdMsg just passes the buck to CView to do the normal MFC thing; if not, it returns FALSE, indicating that the command was not handled. This has the effect of disabling the command, since MFC automatically disables any command for which there's no handler. Since I bypassed CView::OnCmdMsg-which calls CDocument::OnCmdMsg-I've cut off any command that's handled by the view or document (any command whose handler is in the CScribbleView or CScribbleDoc message map). It's like removing the view and document from Figure 3. Commands handled by CMainFrame or CScribbleApp still work as normal, even when the document is uninitialized. Pretty cool, huh? Of course, this only works if you have designed your app so the view and document handle only commands pertaining to themselves and not global commands like Print Setup. If not, you may have to move some commands to your app object or CMainFrame so they don't get disabled.

Figure 3 MFC command routing

The upshot of all this is that, when you run the modified Scribble program, it comes up with a new, empty CScribbleDoc object. This satisfies MFC's need to have a CDocument happily hooked up to its view. But because the doc object is uninitialized (m_bInitialized = FALSE), all the document-related commands are disabled, you can't draw with the mouse, and the frame looks blank. The user can open another doc or do a File New (from either the menu or toolbar), in which case CScribbleApp::OnMyNewFile gets control, initializes the doc (does all those hairy Web connections), and-viola!-the window paints white and Print, Save, and all the other commands are back again.

Now, you may think you're done, but not quite. There's still that little matter of creating new documents from the Windows 95 desktop (right-click New) and Windows Explorer (File New). Since I'm running out of space, and since that delves into a whole other subject, I'll save that one for next month. Be there!

QWhat is the best way to implement a scheme to catch any uncaught exception in an MFC app so the user can get a last opportunity to save data before the program does an emergency exit?

Allan Bommer

AGood question! There are a couple of different ways to do this, and C++ itself even has a mechanism. A little-known feature of C++ is the "terminate" function. C++ calls this special function whenever a program throws an exception for which there is no catch handler. The terminate function always has a void signature-no arguments and no return value. For example, you could write your own terminate function like this:

 void my_terminate()
{
   printf("Help me, I'm dying.\n");
   // Save docs now
}

Then, you can make your function the global terminate function.

 set_terminate(my_terminate);

set_terminate is part of the standard C++ runtime library that's required in every implementation of C++. It returns a pointer to the old terminate function, which you should probably call from your new one.

 typedef void (*TERMINATE_FN)();
TERMINATE_FN old_terminate = NULL;

void main()
{
   old_terminate = set_terminate(my_terminate);
.
.
.
}
void my_terminate()
{
   printf("Oh no, I'm dying.\n");
   // Save docs now
   if (old_terminate)
      old_terminate();  // call original terminate fn
}

While it's not relevant for your question, I should mention another special C++ function related to terminate. This function is called "unexpected," and you use set_unexpected to set it. C++ calls unexpected when a function in your program throws an exception it's not supposed to throw. Though few programmers use this feature, in C++ you can declare which exceptions a function throws.

 extern void DoSomething() throw (CFooError, CBarError);

In this example, if DoSomething throws an exception other than CFooError or CBarError, C++ calls unexpected. If a function has no exceptions specified, then it's allowed to throw any exception.

Now, back to terminate. The Official C++ Rule Book (aka The Annotated C++ Reference Manual), section 15.6.1, stipulates that the default terminate function should call abort. That's not very friendly, even if you save the user's docs before dying. It would be nice if there were some way to keep running. The problem is that C++ exceptions are really just a long jump with some stack cleanup thrown in. There's no way to resume from the point the exception was thrown; you have to reinitialize your app and start all over again.

That is, unless your app is a Windows app. If you recall from your Basic Windows Training (which many of you MFC whippersnappers probably skipped), at the heart of every Windows program lies a main loop that looks something like this:

 while (GetMessage(msg, ...)) {  // get a message
   DispatchMessage(msg, ...);   // process it (call
                                // window proc)
}

When stuff happens, Windows puts message in your app's message queue-things like WM_COMMAND, WM_LBUTTONDOWN, or WM_SAYHEYJOEWHATSUP. Windows doesn't actually call your window procedure; you must explicitly retrieve the messages from the queue and call DispatchMessage, which calls your window procedure. This little dance is how Windows 3.1 and earlier versions provide pseudo multitasking.

When your app starts up, it does a little initialization, then immediately enters this get/dispatch loop (or message pump as it's frequently called). If you've only programmed in MFC, you might not even realize there's a message pump because you never have to write it-MFC does it for you. In any case, the message pump is the ideal place to put an exception handler of last resort.

 while (GetMessage(msg,...)) {
   TRY {
      DispatchMessage(msg,...);
   } CATCH_ALL(e) {
      // handler of last resort
   } END_CATCH_ALL
}

By catching exceptions inside the main message loop, you let your app keep on loopin' when an exception occurs. Since every Windows program is in its main loop virtually all the time (though not in the case of startup, termination, modal dialogs, and some weird windows "hook" functions), this handler will effectively catch any exception your code might throw.

I've shown the code for a normal Windows app written in C. In MFC, all you have to do is override the right virtual function to insert your TRY/CATCH handler. Of course, knowing which function to override is rarely trivial-that's the whole trick to programming with MFC! As it turns out, the message pump is implemented in CWinThread::Run and CWinThread::PumpMessage. In Win32¨, message queues exist on a per-thread basis, not per-app. Run calls PeekMessage to see if there's a message waiting, and it only calls PumpMessage to dispatch it if there is. Knowing this, you can implement a global exception handler like this:

 //////////////////
// Override message pump to catch exceptions.
//
BOOL CMyApp::PumpMessage()
{
   BOOL ret;
   TRY {
        // Let MFC pump the message as normal
        ret = CWinApp::PumpMessage(); 
   } CATCH_ALL(e) {
        // my handler of last resort...
        ret = TRUE;   // keep looping
   } END_CATCH_ALL
   return ret;
}

There's a slight flaw with this, but let's ignore it for now. With PumpMessage written as shown, if your code throws an exception while processing some WM_xxx windows message, the exception will jump back to PumpMessage if no one else catches it. You can save docs or whatever, and return TRUE to keep running. Pretty cool, right?

But if putting an exception handler in the message loop is such a great idea, why doesn't MFC do it? In fact, MFC does have an exception handler of last resort-but not in the message pump! It's in the window proc. Under MFC, every window has the same window proc, which looks like this:

 // simplified
LRESULT AfxWndProc(HWND hWnd,...)
{
    pWnd = // get CWnd for this hWnd
   return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, 
                         lParam);
}

AfxWndProc just passes the buck to AfxCallWndProc, which does the dirty work.

 // also simplified
LRESULT AfxCallWndProc(CWnd* pWnd, HWND hWnd, ...)
{
   LONG lResult;
   TRY {
      lResult = pWnd->WindowProc(nMsg, wParam, lParam);
   } CATCH_ALL(e) {
      lResult = AfxGetThread()->ProcessWndProcException(e);
   } END_CATCH_ALL
   return lResult;
}

I've stripped a lot of details to highlight the important point: AfxWndProc-the window proc used for every MFC window-contains an exception handler that calls the virtual function CWinThread::ProcessWndProcException. This means, if your app throws an exception while processing a message, the exception goes to CWinThread::ProcessWndProcException if nobody else handles it. You can call set_terminate all day and your terminate function will never get called, even if your app throws an "unhandled" exception, because all exceptions are handled in AfxCallWndProc. (Again, with the caveat that your app is processing some message, which it just about always is.)

Now, why do you suppose the Redmondtonians put the TRY/CATCH handler in AfxCallWndProc, instead of putting it in CWinThread::MessagePump the way I showed you earlier? This is where that little flaw I mentioned comes in. I'll quote from the online documentation (Programming with MFC: Overview/Using the General Purpose/Handling Exceptions Classes):

When programming for Windows, it is important to remember that exceptions cannot cross the boundary of a "callback." In other words, if an exception occurs within the scope of a message or command handler, it must be caught there, before the next message is processed.

Why can't you throw an exception past a callback? Because exceptions aren't part of the deal. When Windows calls your app (usually through your window proc) or you call Windows, neither party expects the other to throw an exception. If you do, there's no telling what might happen. It might work, or it might not. When windows calls your window proc, it expects control to return to the immediately following instruction, not go gallivanting somewhere into outer space.

This is relevant because in Windows one message typically sends others. Your handler for WM_FOO might respond by sending WM_BAR. Even if you don't call SendMessage directly, many function calls are really a SendMessage in disguise. For example, CWnd::GetFont sends WM_GETFONT and CWnd::SetIcon sends WM_SETICON. Practically all the MFC functions for edit controls, list boxes, combo boxes, and so on actually call SendMessage to send EM_THIS, LB_THAT, or CB_THEOTHERTHING. Every time you call SendMessage, control passes into Windows, then out again to some window proc. Even if that window proc is yours, it's unsafe to throw an exception all the way up to the top-level DispatchMessage that started the chain of messages, bypassing all the secondary messages-you might leave Windows in an unhappy state.

In effect, there's a wall between Windows and your application. Neither side is allowed to throw an exception over the wall. The same wall exists in OLE, where you can't throw an exception from one interface to another, but must instead convert your internal exception to an HRESULT error code and return it. The wall also applies whenever you're using callbacks to or from third-party systems; unless the interface explicitly states that exceptions are allowed, you shouldn't throw past the boundary of a callback (see Figure 4).

Figure 4 Handling exceptions in AfxWndProc avoids throwing across callback boundary

By putting the TRY/CATCH block inside the window procedure, MFC guarantees that exceptions never jump across the wall. When your program throws an unhandled exception, control jumps to the closest AfxCallWndProc frame on the stack, the one for the most recently issued WM_xxx message. The window proc is Windows' portal to your app, and the TRY/CATCH block in AfxCallWndProc is the gatekeeper guarding the door against exceptions that would escape.

One unfortunate consequence is that ProcessWndProcException doesn't catch exceptions thrown while processing messages in a non-MFC subclassed window. Normally, every window in an MFC app uses AfxWndProc as its window procedure, but there are times when you need to subclass a window by installing your own window proc over AfxWndProc. If so, you lose the exception handling; if you need it, you'll have to implement it yourself, which is easy-just mimic the code in AfxCallWndProc.

 LRESULT MySpecialWndProc(HWND hwnd, ...)
{
   LONG lResult;
   TRY {
      lResult = // process the message
   } CATCH_ALL(e) {
      lResult = AfxGetThread()->
                 ProcessWndProcException(e);
   } END_CATCH_ALL
   return lResult;
}

In case you find all this stuff exceptionally mind-boggling, the sort of thing that makes you wish you were a cab driver instead of a programmer, don't worry. The short answer to your question is: override CWinThread/CWinApp::ProcessWndProcException.

I modified the SCRIBBLE program from the MFC tutorial to show how it works. CScribbleApp::ProcessWndProcException handles uncaught exceptions by displaying the dialog box in Figure 5. After that, it calls CWinApp::SaveAllModified, which is an MFC CWinApp function that prompts the user to save all modified documents. Then Scribble dies or goes on, depending on the user's response to the dialog.

Figure 5 An MFC exception dialog

If the user chooses Abort, CScribbleApp::ProcessWnd-ProcException rethrows the exception. Since it's already in the handler-of-last-resort, this has the affect of really throwing an uncaught exception, and control passes to the terminate handler. To prove it, my InitInstance function installs a new terminate function that displays the message, "Help me, I'm dying.", then calls the old terminate handler, if any. (Empirical evidence suggests the old terminate handler is NULL under Windows-go figure.)

If the user chooses Retry, CScribbleApp calls the base class CWinApp::ProcessWndProcException. The default MFC handler does its thing: if the exception occurred during a WM_PAINT message, MFC validates the paint region; if the exception happened during a WM_COMMAND, MFC acts as if the command succeeded. (For details, read the source code.) MFC then displays a message depending on what kind of exception it is-memory, resource, or whatever-and continues the message loop. I added a Throw command to Scribble (see Figure 6) that lets you throw different kinds of exceptions so you can see what MFC does in each case.

Figure 6 Choose your exception

Finally, if the user chooses Ignore, my ProcessWndProcException handler returns zero. Go directly to AfxCallWndProc, do not passCWinApp::ProcessWndProcException. From there, control returns to Windows (DispatchMessage) as if whatever message was being processed had been handled, and from there back to Scribble's main message loop.

My modified Scribble is fine for an educational tool designed to show you how exceptions work in MFC, but my implementation of ProcessWndProcException has a few shortcomings you should correct for a commercial app. For one thing, the user interface is bad; you should replace my Abort/Retry/Ignore dialog with one that asks the user right away whether to save documents. Then, of course, you have to be very careful what you do. If your save operation requires allocating memory, you might crash again if the original exception was CMemoryException. There's not much you can do here except preallocate a chunk of memory during initialization so you can free it in the exception handler and hope it's enough to save the docs.

You also have to be careful how you load the dialog. Ideally you should build it from static data (the way I did with hard-coded strings), not loaded from the resource file, because the resource file may be having problems if the exception is CResourceException. Yet another thing to consider is whether a document was corrupted, which could easily happen if the exception was thrown in the middle of some document operation. You don't want to save a bad doc! The best thing to do here is save a hidden file with a different name like ##docname.doc. Then, the next time the user opens that doc, you can display a message like "there is a crash version of this document newer than the one on disk-do you want to open it?" If the user wants to open it but the crash doc won't open (because it was corrupted), then at least you still have the most recently saved version. Another possibility is to make sure the doc is valid before you save it by doing the equivalent of AssertValid.

Yet another thing to worry about in your exception handler is what message you are currently processing. MFC's CWinThread::ProcessWndProcException calls ValidateRect if the exception happened inside a WM_PAINT message because it can't just return zero. If it did, Windows would send WM_PAINT messages ad infinitum, trying to repaint your window. Also, while the return value is unimportant for most messages, some-like WM_COMPAREITEM-actually look at the return value. The problem is further complicated by the fact that you could be several levels deep in processing a message. Your WM_PAINT handler might send some other WM_xxx message, and that might send another, which is the one that raises the exception. Looking at the current message doesn't tell you what the top-level message is.

It's impossible to write a perfect last-ditch exception handler. My best advice is to be very careful what you do inside ProcessWndProcException, assume the worst, and do your best to protect the user from the slings and arrows of outrageous software misfortune. Automatic backups and crash files go a long way in this department.

One final word about terminate before I, er, terminate: some of you more experienced MFC users may be familiar with AfxTerminate and AfxSetTerminate, which are like terminate and set_terminate in C++. Those functions still exist, but only for backward compatibility. They're officially considered passŽ; you're supposed to use either set_terminate (if you're writing a straight C++ app) or ProcessWndProcException (if you're writing a Windows application.

Have a question about programming in MFC or C++? Send it to Paul DiLascia at 72400.2702@compuserve.com

From the November 1996 issue of Microsoft Systems Journal.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft