Electronic Ink
TECH NOTES   XTRAS PROGRAMMING   MFC XTRAS  
Home
Products
Tech Notes
Contact Us

Building Xtras Using The Microsoft Foundation Classes

Say what you will about the Microsoft Foundation Classes (MFC) and their design, it's undeniable that they can save you from spending a lot of time re-inventing the wheel in your development projects. From core classes that handle mundane elements such as points, rects, lists and strings, to window and dialog box management, there's a lot in MFC for you to take advantage of.

Typically, using MFC in your Windows Xtra will add about 100K to 150K to its size, a little more in 32-bit Windows than 16-bit Windows. Here are three easy steps to make sure your Xtra can use MFC gracefully:

1. Reconfigure Your Project File

In order to use MFC within your Xtra, the changes you need to make to your project file are relatively minor. From your Project Options dialog in Visual C++, select the checkbox "Use Microsoft Foundation Classes". Then, in the Preprocessor Options section of the Compiler Settings, you need to add _WINDLL and _USRDLL to your list of pre-defined symbols.

2. Hack the MOA Headers

The main conflict between MOA and MFC is that both of them define a Windows DLL entrypoint to set themselves up. Only one of the two entrypoints (MOA's) wins out. This leaves the MFC globals in an un-initialized state, and will make your DLL crash horribly when you try to use practically any MFC call.

The standard entrypoint for a DLL is called WinMain() in 16-bit Windows, and DllMain() in 32-bit Windows. MOA's version of this function simply stuffs the Windows "instance handle" (HINSTANCE) for the DLL into the global variable gXtraFileRef. This variable is important, because it allows access to the resources stored in your Xtra file, among other things. So it needs to be intialized somewhere or MOA won't work properly. MFC's version of the WinMain/DllMain function initializes a whole suite of global variables, and sets everything up to work right for MFC.

The goal of modifying moaxtra.h is to disable MOA's entrypoint when _USRDLL is defined. This will allow MFC to do its setup work first when WinMain() or DllMain() is called. Then all you need to do later on, probably in the MoaCreate() method of your IMoaRegister implementation, is set the gXtraFileRef = AfxGetInstanceHandle(). The following code snippet replaces the code starting at the #ifdef CODEMODEL_DLL line in moaxtra.h:

#ifdef CODEMODEL_DLL

/* MODIFICATION FOR COMPATIBILITY WITH MFC */
#ifdef _USRDLL

   /* this code prevents an Xtra from defining its own LibMain */
   /* It's very important to let MFC's LibMain be the main one */
   /* and set gXtraFileRef from somewhere ELSE. */
   
   #define XCODE_PREFACE \
       \
      MoaFileRef gXtraFileRef = kMoaFileRef_NoRef;
      
#else
/* END MODIFICATION FOR MFC */

   #ifndef WIN32

      #define XCODE_PREFACE \
          \
         MoaFileRef gXtraFileRef = kMoaFileRef_NoRef; \
          \
         _EXTERN_C_ int FAR PASCAL LibMain(HANDLE hInst, WORD wDataSeg, WORD wHeapSize, LPSTR cmd) \
         { \
            gXtraFileRef = hInst; \
             return(1); \
         }

   #else

      #define XCODE_PREFACE \
          \
         MoaFileRef gXtraFileRef = kMoaFileRef_NoRef; \
          \
         _EXTERN_C_ BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, PMoaVoid reserved) \
         { \
            gXtraFileRef = hInst; \
             return(1); \
         }
      
   #endif
   
#endif

#endif /* EXTRA ENDIF FOR MFC MODIFICATION */

A new modification you need to make with Visual C++ 5.0 and the MOA headers that ship with Director 6/Authorware 4 is to remove MOA's typedef of REFCLSID, since MFC takes care of this already. Find the following line in MOAXTRA.H:

    typedef ConstPMoaClassID REFCLSID;
And bracket it with preprocessor directives to look like this:
#if _MSC_VER < 1100 /* Don't do this typedef in VC++ 5.0 */
    typedef ConstPMoaClassID REFCLSID;
#endif // _MSC_VER < 1100

3. Bracket your MOA EntryPoints

Any time you use MFC routines within your Xtra, you need to bracket your entrypoints with MFC routines that lock and unlock the temporary object maps. This can lead to big problems and memory leaks if you don't do it. The easiest way to make sure the temp maps are properly unlocked is to create a set of replacement macros for the MOA macros X_ENTER, X_EXIT and so on:

#ifdef _USRDLL
   #define MFCXTRA_ENTER                    { X_ENTER    AfxLockTempMaps(); }
   #define MFCXTRA_ABORT                    { AfxUnlockTempMaps(); X_ABORT }               
   #define MFCXTRA_EXIT                     { AfxUnlockTempMaps(); X_EXIT }               
   #define MFCXTRA_RETURN(retType, retVal)  { AfxUnlockTempMaps(); X_RETURN(retType, retVal) }   
   #define MFCXTRA_RETURN_VOID              { AfxUnlockTempMaps(); X_RETURN_VOID }
#else
   #define MFCXTRA_ENTER                    X_ENTER    
   #define MFCXTRA_ABORT                    X_ABORT                  
   #define MFCXTRA_EXIT                     X_EXIT                  
   #define MFCXTRA_RETURN(retType, retVal)  X_RETURN(retType, retVal)   
   #define MFCXTRA_RETURN_VOID              X_RETURN_VOID            
#endif
 
Then, simply do a case-sensitive search-and-replace in your source files, searching for "X_" and replacing it with "MFCXTRA_".

You also need to bracket your entrypoints with TRY and CATCH blocks. Otherwise, Director will crash if MFC throws an exception within your Xtra. Below is an example of a MOA entrypoint properly implemented with TRY and CATCH blocks, and the MFCXTRA_XXXX macros.

STDMETHODIMP MyXtra_IMoaRegister_Register(MyXtra_IMoaRegister FAR * This, 
   PIMoaCache pCache, PIMoaDict pXtraDict)
{
   MFCXTRA_ENTER
   
   MoaError    err = kMoaErr_NoErr;

   TRY
   {
      /* implement the functionality for the entrypoint here */
   }
   CATCH(CFileException, obj)
   {
      /* notify the user that a file error occurred */
   }
   AND_CATCH(CMemoryException, obj)
   {
      /* notify the user that a memory error occurred */
   }
   AND_CATCH_ALL(obj)
   {
      /* some other type of error occurred */
   }
   END_CATCH_ALL

   MFCXTRA_STD_RETURN(err);
   MFCXTRA_EXIT
}

4. Create an instance of CWinApp

MFC will not work properly unless there's exactly one instance of the CWinApp class around. The easiest thing to do is initialize it in a static variable.

  CWinApp gWinApp("My Xtra");

The simple declaration above is all you really need. If you want to get fancy, you can create your own subclass of CWinApp that overrides sone of its virtual functions, but to get your Xtra to work, it's not really necessary.

Feedback

The information presented above is as accurate and up to date to the best of my knowledge. (It's the basis for a number of shipping Xtras at this point, so it had better be accurate!) If you have any corrections, additions or comments about this article, please e-mail me any feedback and I will integrate the information into future versions.

©1994-2024 Electronic Ink. All rights reserved.