MORE INFORMATION
Size is the major advantage for converting a statically-linked Regular DLL
to a Regular DLL that uses the MFC Shared Library. Converting the Trace.dll
from the DLLTRACE sample to use the Shared Library will drop the size of a
release-build DLL from around 95K down to about 12K. The difference between
debug builds is even more staggering: a debug build Trace.dll drops from
nearly a megabyte in size down to 25K when using MFC in the Shared Library.
Steps to convert DLLTRACE to use MFC in a shared library
- In the Build Settings property sheet, select both the release and debug builds to change settings for.
Visual C++ .NET:
On the Properties dialog for the project, from the Configuration combo box, select All Configurations. - In the General tab, in the Microsoft Foundation Classes combo box, change the option to Use MFC in a Shared DLL (mfc(d).dll).
Visual C++ .NET:
On the General tab, change Use of MFC option to Use MFC in a Shared DLL. - Add the following line of code to the beginning of every function exported from the DLL:
AFX_MANAGE_STATE(AfxGetStaticModuleState())
The AfxGetStaticModuleState() MFC function
The code in step 3 uses an MFC-provided macro (AFX_MANAGE_STATE) to swap
the current module state with the state returned from
AfxGetStaticModuleState() until the end of the current scope. The
AfxGetStaticModuleState() function is a special MFC function in Regular
DLLs that returns the module state of the Regular DLL in the context of the
DLL. For example, Trace.dll exports only three functions:
PromptTraceFlags()
ProcessDLLIdle()
FilterDLLMsg()
Here is what FilterDLLMsg() should look like in the converted form:
extern "C" BOOL FAR PASCAL EXPORT FilterDllMsg(LPMSG lpMsg)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
TRY
{
return AfxGetApp()->PreTranslateMessage(lpMsg);
}
END_TRY
return FALSE;
}
After making these changes, rebuild the DLLTRACE project. The application
will use the new DLL and work just as it did before.
Problems occur if the AFX_MANAGE_STATE macro is not used
Certain problems occur if the AFX_MANAGE_STATE macro is not used. In
discussing these problems, this article may shed some light on problems
that crop up when attempting to convert the old-style _USRDLLs to use the
DLL version of MFC.
If the PromptTraceFlags function does not use the AFX_MANAGE_STATE macro at
the very beginning of execution, an assertion will fail at line 43 in
Dlgdata.cpp and the following message will appear in the output window:
Error: no data exchange control with ID xx.
The primary cause of this problem is that MFC is using the resource handle
of the main application to load the dialog box template for the Prompt Dialog.
This template is actually stored in the Trace DLL. The root cause, of
course, is that MFC's module state information has not been switched by the
AFX_MANAGE_STATE macro. The resource handle is recovered from MFC's module
state; not switching the module state causes the wrong resource handle to
be used.
If the AFX_MANAGE_STATE macro is omitted from the beginning of either the
FilterDLLMsg or ProcessDLLIdle function, a different problem
occurs. In this case, immediately after executing the DLLTRACE application
it takes a momentary siesta and then quits with an error message similar to
this one:
Unhandled exception in dlltrace.exe (MFC40D.DLL):
0xC00000FD: Stack Overflow
The primary culprit here is the AfxGetApp() function, which is used to call
either PreTranslateMessage() in the case of FilterDLLMsg() or OnIdle() in
the case of ProcessDLLIdle(). Once again, the root evil is the failure to
switch the module state, which maintains the pointer to the current CWinApp
object that is returned from AfxGetApp().
In each of these situations, when the module state is not switched,
AfxGetApp() returns a pointer to the CTheApp object of the application, not
the CTracerDLL object of the DLL, as it rightfully should. When
FilterDLLMsg() goes to call AfxGetApp()->PreTranslateMessage(), it calls
CTheApp::PreTranslateMessage(). However, FilterDllMsg() was called by
CTheApp::PreTranslateMessage(); so, once again, FilterDLLMsg() is called,
which in turn calls CTheApp::PreTranslateMessage(), creating an infinite recursion. ProcessDLLIdle() is invoked in a similar way.
To avoid this recursive state, AFX_MANAGE_STATE is used and AfxGetApp()
returns a pointer to the CTracerDLL object in the DLL. Because CTracerDLL
has no override of PreTranslateMessage() or OnIdle(), the default MFC
versions are called--not the CTheApp overrides. Consequently, there is no
infinite recursion.
Final question
Why doesn't AFX_MANAGE_STATE need to be put on every function in the DLL?
CTracerDLL::InitInstance() is called by the MFC code in the application,
why doesn't it need to have AFX_MANAGE_STATE?
The answer is that MFC does it for you. MFC manually shifts the module
state before InitInstance() and then switches it back after InitInstance()
returns. The same is true for all message map handlers. Regular DLLs
actually have a special master window procedure that automatically switches
the module state before routing any message.