Objective Grid : PART II Programmer’s Guide : Chapter 15 Advanced Concepts
Chapter 15 Advanced Concepts
Objective Grid DLL Architecture
In this chapter, we will take a look at the distribution architecture of the Objective Grid class library. We will also go into details of how Dynamic Link Libraries (DLLs) in general, and MFC extension DLLs in particular, integrate with MFC applications. Objective Grid is primarily built and linked to as an MFC extension DLL. It is also possible to link to the Objective Grid library statically.
Please refer to the samples located at:
<stingray-installdir>\Samples\Grid\ Grid\Dll\DllHusk (Extension DLL)
<stingray-installdir>\Samples\Grid\ Grid\Dll\ OGRegDll (Regular DLL)
Dynamic Link Libraries
DLLs are a very important part of Windows. DLLs are pieces of program code that are separate from the main program. These segments of code can be loaded by the main program on demand and used in a transparent manner. All of the support required for doing this is built into the Windows operating system.
MFC DLLs
MFC in its present form supports three types of DLLs. These are:
1. Regular DLLs
2. Regular DLLs dynamically linked to MFC
3. Extension DLLs
Understanding how these DLL types function is essential to understanding the DLL architecture that is used by the Objective Grid class library.
Regular DLLs
These are the MFC DLL variants that are closest to regular Win32 DLLs. They use MFC internally and statically link to it. These DLLs have a CWinApp-derived object and can be called by any Win32 application. The calling application does not have to be an MFC application. Regular DLLs cannot (normally) export MFC objects. They are normally used to export C-style functions. These functions can be called by external programs. For example, consider the following function:
 
extern “C” void _declspec(dllexport) _stdcall Foo()
{
CString str;
str = _T(“This is a regular dll”);
// perform any additional operations
}
 
This function can be called from any application that links to this DLL.
As can be seen from above, these DLLs can use MFC just like any other MFC application, but cannot export MFC objects.
Regular DLLs Linked Dynamically to MFC
These are not very different from the regular DLLs explained above. The only difference is that they link dynamically to MFC. This is a considerable advantage, since it can make the DLL considerably smaller. Yet another unique possibility is available with this DLL. MFC extension DLLs (explained below) cannot be used with non-MFC applications. As a workaround, it is possible to create a regular DLL that links dynamically with MFC. This regular DLL would in turn link to the extension DLL. This lets us use the extension DLL from non-MFC applications. This approach is particularly useful for building COM severs with MFC (Objective Grid CCE uses this approach). With extension DLLs, management of run time classes and class factories is very easy. The functionality is then exported for use by other applications through a regular DLL COM server.
Regular DLLs dynamically linked to MFC also have CWinApp-derived application objects.
Extension DLLs
These DLLs are the most interesting DLLs from an MFC programmer’s perspective. They can be used to export cool MFC derived functionality in a seamless manner that is otherwise not possible. Let us say that we have a cool CView-derived class that we would like to share among programs (and perhaps among programmers). We can simply put this CView-derived class in an MFC extension DLL, and it can be shared in seamless manner from any MFC application! To understand how this works, let us take a look at the initialization code in an MFC extension DLL.
MFC extension DLLs do not have a CWinApp-derived object. Their DllMain() function is very similar to what you would normally expect from a WIN32 DLL.
 
static AFX_EXTENSION_MODULE TestDll1DLL = { NULL, NULL };
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
 
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("TESTDLL1.DLL Initializing!\n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(TestDll1DLL, hInstance))
return 0;
// comments deleted
 
new CDynLinkLibrary(TestDll1DLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("TESTDLL1.DLL Terminating!\n");
// Terminate the library before destructors are called
AfxTermExtensionModule(TestDll1DLL);
}
return 1; // ok
}
We can see that the DLL makes a call to AfxInitExtensionModule().
 
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(TestDll1DLL, hInstance))
return 0;
This function just saves data that is used by MFC, the DLL module handle, the run-time class list head, and also the class factory list head. This is the first part of the initialization.
 
// save the current HMODULE information for resource loading
ASSERT(hModule != NULL);
state.hModule = hModule;
state.hResource = hModule;
 
// save the start of the run-time class list
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
state.pFirstSharedClass = pModuleState->m_classList.GetHead();
pModuleState->m_classList.m_pHead = pModuleState->m_pClassInit;
The extension DLL acts as part of the application that hosts it. To make this possible, an object of class CDynLinkLibrary is created on the heap. This is the second part of the initialization.
 
new CDynLinkLibrary(TestDll1DLL);
Creating an instance of CDynLinkLibrary causes the data that is inherent to the DLL to become part of a global linked list that MFC maintains.
The first part of the CDynLinkLibrary saves state information:
 
// copy info from AFX_EXTENSION_MODULE struct
ASSERT(state.hModule != NULL);
m_hModule = state.hModule;
m_hResource = state.hResource;
m_classList.m_pHead = state.pFirstSharedClass;
#ifndef _AFX_NO_OLE_SUPPORT
m_factoryList.m_pHead = state.pFirstSharedFactory;
#endif
m_bSystem = bSystem;
The second part of the constructor adds this CDynLinkLibrary object to the linked list that is maintained by the MFC framework.
 
// insert at the head of the list
//(extensions will go in front of core DLL)
AfxLockGlobals(CRIT_DYNLINKLIST);
m_pModuleState->m_libraryList.AddHead(this);
AfxUnlockGlobals(CRIT_DYNLINKLIST);
Adding this CDynLinkLibrary to the linked list that is maintained by the framework ensures that the newly loaded MFC extension DLL functions as a seamless part of the application. Whenever MFC locates a resource, run-time class, or a class factory, it looks up this list and basically iterates through it. This ensures that there is no delineation between the calling application and this DLL. To illustrate this, let us look at the code for CRuntimeClass::Load().
 
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
// loads a run-time class description
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
 
WORD wTemp;
ar >> wTemp; *pwSchemaNum = wTemp;
ar >> nLen;
 
if (nLen >= _countof(szClassName) ||
ar.Read(szClassName, nLen*sizeof(char)) != nLen*sizeof(char))
{
return NULL;
}
szClassName[nLen] = '\0';
 
// search app specific classes
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
for (pClass = pModuleState->m_classList; pClass != NULL;
pClass = pClass->m_pNextClass)
{
if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
{
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
return pClass;
}
}
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
#ifdef _AFXDLL
// search classes in shared DLLs
AfxLockGlobals(CRIT_DYNLINKLIST);
for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList;
pDLL != NULL;
pDLL = pDLL->m_pNextDLL)
{
for (pClass = pDLL->m_classList; pClass != NULL;
pClass = pClass->m_pNextClass)
{
if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
{
AfxUnlockGlobals(CRIT_DYNLINKLIST);
return pClass;
}
}
}
AfxUnlockGlobals(CRIT_DYNLINKLIST);
#endif
 
TRACE1("Warning:Cannot load %hs from archive. Class not defined.\n",
szClassName);
return NULL; // not found
}
First, the above code loads the run-time class name. It then attempts to look up the run-time class in the application. It then looks at the classes in extension DLLs simply iterating through them as highlighted above. This explains how MFC extension DLLs work like a part of the calling application.
Exporting Functions from Extension DLLs
To understand the mechanism for importing and exporting symbols (functions and variables) that are used in Stingray Studio libraries, refer to “Extending Stingray Libraries” in the Getting Started part
Module State
Now that we have covered the various important aspects about MFC DLLs that concern an Objective Grid programmer, let us look at yet another related important topic, the concept of module state.
In its simplest sense, the module state is state information that is used by the MFC library for everything from loading resources to creating ActiveX objects. Each application that links to the MFC DLL has a unique module state that separates itself from every other user of that DLL. You can think of discrete module state cubes as separate MFC processes. To understand a little better, let us look at the definition of AFX_MODULE_STATE (shown with some data omitted for brevity).
 
// AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
{
public:
#ifdef _AFXDLL
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion, BOOL bSystem);
 
#else
AFX_MODULE_STATE(BOOL bDLL);
#endif
 
~AFX_MODULE_STATE();
 
CWinApp* m_pCurrentWinApp;
HINSTANCE m_hCurrentInstanceHandle;
HINSTANCE m_hCurrentResourceHandle;
LPCTSTR m_lpszCurrentAppName;
BYTE m_bDLL; // TRUE if module is a DLL, FALSE if it is an EXE
BYTE m_bSystem; //TRUE if module is a "system" module, FALSE if not
BYTE m_bReserved[2]; // padding
 
short m_fRegisteredClasses; // flags for registered window classes
 
// run-time class data
#ifdef _AFXDLL
CRuntimeClass* m_pClassInit;
#endif
CTypedSimpleList<CRuntimeClass*> m_classList;
 
// removed for brevity
 
#ifdef _AFXDLL
// CDynLinkLibrary objects (for resource chain)
CTypedSimpleList<CDynLinkLibrary*> m_libraryList;
 
// special case for MFCxxLOC.DLL (localized MFC resources)
HINSTANCE m_appLangDLL;
#endif
 
// define thread local portions of module state
THREAD_LOCAL(AFX_MODULE_THREAD_STATE, m_thread)
};
It is clear that this data is what defines much of how an extension DLL behaves as part of a chain of DLLs. We can readily see that if a DLL is registered in one module state, it will not be found in another module state list. Even within the same process, we can see that lists of run-time classes, ActiveX class factories etc., are visible only from within the correct module state.
Now that we understand the basics of AFX_MODULE_STATE, let us proceed to look at situations where distinct module states come into play and where they don’t. In general, the most important points to remember are:
1. Regular DLLs have their own module state.
2. Extension DLLs do not have their own module state. They take on the state of the calling application or DLL.
3. Applications have their own module state.
We can see from the above that for a MFC extension DLL to function normally, it will have to be in the module state context that it was initialized in. In any other context, calls into the DLL will fail in a cryptic manner, with ASSERT_VALID triggering or resources failing to load. To gain a better understanding, let us look at a few real life scenarios and attempt to analyze them in terms of what we have seen so far.
Application Calls Grid Extension DLL
There is no problem with this approach, since the module state context in which the DLL is initialized is the same as that which is used when calls are made into the DLL. This is the situation that most users will find themselves in. You don’t have to worry much about the module state in this case.
Your Regular DLL Links to the Grid Extension DLL. The Application Uses this DLL
In this case we can readily discern, based on what we know, that there are two distinct module states—one that pertains to the application, and one that pertains to the regular DLL. Since this regular DLL is what makes calls into the grid, this is the module that should initialize the grid. So we just initialize the grid in terms of the regular DLLs module state. To do this, we have to make a special call to an initialization function that is exported by the grid extension DLL:
 
GXInitDll()
All that this function does is to create a CDynLinkLibrary on the heap. Why is this not done implicitly, as with all extension DLL usage? The reason is that when the DLL is loaded, the module state that is active will be the state of the calling application. If we initialize in this context, then we will be in for some surprises, as seen in the comments that AppWizard generates when we generate an extension DLL project.
 
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("TESTDLL1.DLL Initializing!\n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(TestDll1DLL, hInstance))
return 0;
 
// Insert this DLL into the resource chain
// NOTE: If this extension DLL is being implicitly linked to by
// an MFC Regular DLL (such as an ActiveX control)
// instead of an MFC application, then you will want to
// remove this line from DllMain and put it in a separate
// function exported from this extension DLL. The regular DLL
// that uses this extension DLL should then explicitly call
// that function to initialize this extension DLL. Otherwise,
// the CDynLinkLibrary object will not be attached to the
// regular DLL's resource chain, and serious problems will
// result.
 
new CDynLinkLibrary(TestDll1DLL);
When we move this one line of code into a separate function (in this case GXInitDll), everything falls in place. When the extension DLL calls this function, the module state that is active is that of the regular DLL. Thus, the extension goes into the correct module state, and everything works as expected.
Regular DLL Links to Grid Extension DLL. Application Tries to Use the Grid Directly
This is an unpleasant situation that should be avoided. You can readily see the reason. When the application calls the grid DLL, the module state is incorrect. This also leads to another situation that is not as easily seen. Suppose one of the exported methods that the regular DLL has calls into the grid. Will that cause problems? The answer is yes. The reason is that when calls are made into the regular DLL, the module state is not automatically set to that of the DLL. MFC has no way of knowing which calls you export. So the burden is on you to make sure that all exported calls are protected with the AFX_MANAGE_STATE macro. This macro simply ensures that the correct module state is set for the duration of that call. This switch is automatically made if this is an MFC exported COM interface method or a message handler. MFC knows and takes care of these entry points. For others, it is our responsibility to do so.
Almost all other cases can be explained in terms of the simple rules and situations that we have seen above. This stuff is not so confusing after all!
NOTE >> While we are talking at length about module states, we might as well explain why we use module states in relation to global data in the grid. Previously, all grid state information was maintained in the process’s address space. With version 6.1, we made the switch to module state local data. The required implementation is in gxproc.h for the interested, but in essence this approach lets us integrate our global data with MFC’s least common denominator, the module state.
Module State and Cleanup
In versions of Objective Grid prior to 6.1, it was necessary to call the GXTerminate() function during grid cleanup in order to clean up some grid state information that was maintained in the process's address space. This was true whenever the grid was used in a regular application or any kind of DLL, whether it was a regular DLL, an MFC extension DLL, or an ActiveX control (also known as an OCX).
For Objective Grid versions 6.1 and later, cleanup is handled differently because grid state information is now maintained in the module state local data. If the grid is being used inside of an ActiveX control, you will need to call the GXForceTerminate() function to correctly reset the module state as part of cleaning up your ActiveX control. If you are not using the grid in an ActiveX control, you do not need to do anything at all. The grid will do all necessary cleanup automatically in these cases.
If you are using Objective Grid 6.1 or later, you may safely remove all calls to GXTerminate() that were added when using a previous version of the grid. (Remember, if the grid is inside of an ActiveX control, you must call GXForceTerminate() upon cleanup.) In some cases, leaving the old GXTerminate() calls in will not cause any harm, but in other cases, this will cause problems. To be safe, we recommend that you remove any old GXTerminate() calls from your code.
NOTE >> The method GXTerminate() is unsupported. As a convenience, GXTerminate() has been rewritten in terms of GXForceTerminate(). However, we recommend replacing calls to GXTerminate() with calls to GXForceTerminate().
Manage State
You will need to protect calls across module states with the AFX_MANAGE_STATE macro. The AFX_MANAGE_STATE macro should be the first call you make in a function, before any memory is assessed or allocated. This a requirement for MFC in general; it has nothing to do with Objective Grid. If you neglect to call AFX_MANAGE_STATE first or if you fail to call it altogether, you might experience unpredictable behavior. For example, the control will assert when the mouse cursor is moved across any of the headers because it won't get the correct resource handle.
If you call mfc42.dll from a regular MFC DLL (instead of from an MFC program or extension DLL), because the global variables in which it stores data aren’t synchronized, you’ll need to insert the following as the first line of all exported functions in your regular DLLs:
 
AFX_MANAGE_STATE(AfxGetStaticModuleState());
NOTE >> The AFX_MANAGE_STATE macro won’t have any effect if the code is statically linked to MFC.