Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > April 1997
April 1997


Code for this article: C++0497.exe (25KB)
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).

This month I digress from the usual Q&A format to bring you up to date on a program called TRACEWIN that I first described in my October 1995 column. TRACEWIN displays MFC diagnostic output in a window instead of the debugger, just like the Windows® 3.1 program DEBUGWIN used to do. Figure 1 shows TRACEWIN in action. If you didn't read the October issue, don't worry, I'll review the essentials.
Figure 1 TRACEWIN in action
Figure 1 TRACEWIN in action

The basic idea behind TRACEWIN is to change the data member m_pFile in the MFC global variable afxDump. CDumpContext is an MFC class for displaying diagnostics; afxDump is MFC's global instance of this class, sort of like stderr in C or cerr in C++. The TRACE macro and its siblings—TRACE0, TRACE1, and company—all invoke ::AfxTrace, which writes to afxDump, as do all the virtual CObject::Dump functions. Everything eventually goes through a single function, CDumpContext::OutputString, that looks roughly like this.
// Simplified from ..\mfc\src\dumpcont.cpp
void CDumpContext::OutputString(LPCTSTR lpsz)
{
  if (m_pFile == NULL)  {
    ::OutputDebugString(lpsz);
  } else
    m_pFile->Write(lpsz, strlen(lpsz));
}
That is, CDumpContext calls the Win32® function ::OutputDebugString, which writes the string to the debugger unless m_pFile is set, in which case it writes the string to the file by calling m_pFile->Write(). Normally, afxDump.m_ pFile is NULL, but apps using TRACEWIN change it by calling MfxTraceInit.
// In MfxTrace.h
BOOL MfxTraceInit()
{
      afxDump.m_pFile = new CFileTrace;
}
CFileTrace is a special file class whose Write function sends the string to the TRACEWIN applet via a WM_ COPYDATA message. CFileTrace and MfxTraceInit are defined in a header file, MfxTrace.h. All apps using TRACEWIN must #include MfxTrace.h and call MfxTraceInit, usually from the app's InitInstance function. So there are actually two components to TRACEWIN: the TRACEWIN applet that sits around waiting for WM_COPYDATA messages and displays the text when it receives them, and the MfxTrace.h header file that apps using TRACEWIN must #include so they can call MfxTraceInit.
In my October 1995 column, I tried to eliminate the need for MfxTraceInit by instantiating a static _trInit object in MfxTrace.h, whose constructor did the initialization. That way the only thing programmers would have to do is #include MfxTrace.h. My idea failed because C++ called my static _trInit object's constructor before the one for afxDump, so when my constructor set
m_pFile = new CFileTrace;
CDumpContext::CDumpContext would set it back to NULL. C++ makes no guarantees about the order in which it will initialize static objects from different modules. It just happened that afxDump was initialized after my _trInit object; hence, the need to call MfxTraceInit from InitInstance, which MFC doesn't call until the program is running. At that point all static objects, including afxDump, are guaranteed to be initialized.
This brings me to my reason for revisiting TRACEWIN. I never liked having to require that programmers #include a file and call an init function to use TRACEWIN. Ideally, TRACEWIN should operate invisibly to the app. You should be able to just start TRACEWIN, and all TRACE output from any old MFC program should magically appear in its window as long as the program was compiled with _DEBUG. No #include files, no init function.
Well, believe it or not, while I was toiling away recently in my software dungeon, hard at work building a file viewer for my arch-nemesis the Maximum Leader of Acme Corporation, I thought of a way to do it. My solution only works for apps that link MFC as a DLL, not a static library, but fortunately most apps do.
The crux of the idea is beautifully simple: first, write a DLL that sets afxDump.m_pFile = new CFileTrace when it loads. Then, get every app to load this DLL. Like I said, simple.
Unfortunately, as with everything in Windows and MFC, what looks easy from nine miles up turns out to be more like the inside of a space shuttle engine up close. The easiest way I can explain it is to retrace my own journey along the road to enlightenment, starting with MfxTraceInit.
Whenever Windows loads a DLL into a new process, it calls the DLL's DllMain function with a code, DLL_ PROCESS_ATTACH. I had the idea of writing a DLL, TrWinDll.dll, with a DllMain that does the initialization
BOOL DllMain(...DWORD dwReason...)
{
  	if (dwReason == DLL_PROCESS_ATTACH) {
	  MfxTraceInit();
 	.
 	.
 	.
   }
}
In MFC apps, you don't have to write DllMain; instead, you override CWinApp::InitInstance.
// In TrWinDll.cpp
BOOL CTraceWinDLL::InitInstance()
{
	MfxTraceInit();
	return TRUE;
}
Now, instead of the app having to #include MfxTrace.h and call MfxTraceInit, TrWinDll does it. When Windows unloads TrWinDll, it calls DllMain with DLL_PROCESS_ DETACH, which MFC's DllMain translates to a call to your DLL's ExitInstance function.
// In TrWinDll.cpp
int CTraceWinDLL::ExitInstance()
{
	MfxTraceTerm();
	return CWinApp::ExitInstance();
}
In the old, non-DLL version of TRACEWIN, there was no need for a termination function. I simply let afxDump.m_ pFile point to the CFileTrace object forever (that is, for the entire lifetime of the process). But now that I've moved MfxTrace.h to where the CFileTrace code lives in my DLL, I can't leave afxDump.m_pFile pointing to my CFileTrace object after the DLL is unloaded because MFC might still call afxDump.m_pFile->Write(). MFC would be trying to call code that doesn't exist, something sure to awaken the exception demon and generate a nasty little dialog with something about an "access violation" to tell you your program was naughty. So CTraceWinDLL::ExitInstance calls a new function, MfxTraceTerm, to set everything straight.
// In MfxTrace.h
void MfxTraceTerm()
{
	afxDump.m_pFile = NULL; // restore
}
So far, so good. TrWinDll turns tracing on as soon as it's loaded, and then turns it off again when it's unloaded. Now my problem becomes, how do I get TrWinDll loaded into every process? Under Windows NT, it's easy. Just add TrWinDll.dll to the AppInit_DLLs value in your system registry. This value holds a semicolon-delimited list of DLLs that you want Windows NT® to load with every app.
Unfortunately, there's no AppInit_DLLs for Windows 95, so you have to be more clever. For that, I turned to my pal Jeff Richter for help. In a May 1994 MSJ article, he described ways to "inject" a DLL into another process's address space. Jeff actually describes several ways; for TRACEWIN I chose to use a system-wide hook. This technique is also described in article Q134655 (AppInit_DLLs Registry Value and Windows 95) of the Win32 SDK Knowledge Base (albeit less clearly). For details, consult one of these articles. I only present the two-bit summary here.
A windows hook is a callback function you can ask Windows to call when something interesting happens. For example, a WH_GETMESSAGE hook traps every call to ::GetMessage, whereas a WH_CBT (Computer Based Training) hook traps only certain events, like window creation, destruction, and activation, among others. A hook can be thread-specific or system-wide. For injecting DLLs, a system-wide hook is just the ticket. The basic idea is to write a hook procedure, put it in a DLL, and then write an applet that installs your DLL proc as a hook. Since all code, including hooks, must execute within the current running process's address space, Windows has to load the hook before it can call it. Say you installed a WH_CBT hook. Consider now what happens when XYZ app creates a window. Windows loads your DLL into XYZ's process space (if it isn't loaded already), then calls your hook procedure. The upshot is, setting a system-wide hook is a kludgy way to get Windows to inject a DLL into every running app.
If you understand all that, you can understand how my new and improved TRACEWIN works. TRACEWIN installs a system-wide WH_CBT hook using a hook procedure in TrWinDll. The hook procedure doesn't do anything; it's just there to make Windows load my DLL. Since window activation is one of the events trapped by a CBT hook, as soon as I activate any window on my desktop, Windows loads TrWinDll into that app's process space before calling the hook. As TrWinDll loads, it initializes tracing by setting afxDump.m_pFile. Mission accomplished. If the app isn't an MFC app, no harm done.
Being a big C++ fan and realizing that injecting DLLs is useful for other things besides TRACEWIN, I naturally encapsulated all the messy hook voodoo in a little class for all of you to steal. CInjectDll is an easy-to-use class that injects any DLL system-wide. It doesn't require MFC, so you can use it in non-MFC apps (Borland fans take note!). Figure 2 shows the full code for TrWinDll, including CInjectDll. Figure 3 shows only the startup code from the TRACEWIN applet that calls TrWinDll.
To use CInjectDll, you first must instantiate one—and only one—instance of it in your DLL. The easiest way to do it in an MFC app is to derive your app object from CWinApp and CInjectDll via multiple inheritance.
// multiple inheritance
class CTraceWinDLL : public CWinApp, 
  public CInjectDll {
 .
 .
 .
                     } theDLL; // one-and-only
This reinforces the idea that "injectability" is a feature of the DLL, and also makes it easy to override CInjectDll virtual functions. Since CInjectDll is not derived from CObject, you don't have any of the ugly problems usually associated with multiple inheritance in MFC. In a non-MFC app, you'd write something like
CInjectDLL theDLL; // one-and-only
Once you have a CInjectDll object, you can call handy functions to inject and remove your DLL. Since the TRACEWIN applet is the program that actually does the injecting and removing, I wrote wrapper functions MfxTraceDllInit and MfxTraceDllTerm that in turn call the CInjectDll member functions.
// In TrWindDll.cpp
int MfxTraceDllInit()
{
	return theDLL.Inject(theDLL.m_hInstance,WH_CBT);
}

BOOL MfxTraceDllTerm()
{
	return theDLL.Remove();
}
CInjectDLL::Inject requires the instance handle of the DLL and the WH_XXX code for the kind of hook you want to use. In MFC, the instance handle is stored in the DLL's CWinApp::m_hInstance (actually in CWinThread); in a non-MFC DLL, you get the DLL's HINSTANCE from Windows as an argument to your DllMain function. It's important to use the HINSTANCE for the DLL and not the app calling it; GetModuleHandle(NULL) won't work.
To see how it all works, consider what happens when TRACEWIN starts up. It calls MfxTraceDllInit, which in turn calls theDLL.Inject, which sets a system-wide CBT hook using a static member function, CInjectDll:: HookProc, as the callback procedure. This function doesn't actually do anything, it's just there to force Windows to load the DLL. When TRACEWIN exits, it calls MfxTraceDllTerm, which calls theDLL.Remove to destroy the system-wide hook. End of story. The great thing about it is that apps don't have to do anything to make tracing work!
Well, almost. There is one requirement. TRACEWIN only works for apps that use the dynamically linked version of MFC (MFCxx.DLL), not the static library. Why? Because when TrWinDll sets afxDump.m_pFile, it sets the afxDump variable in MFCxx.DLL. There's no way it could access a statically linked variable without itself being statically linked. If you want to use TRACEWIN to see output from statically linked MFC apps, you have to do it the old way, by #including MfxTrace.h and calling MfxTraceInit. Sorry.
Before leaving TRACEWIN, there are a couple of points I'd like to highlight. First is this issue of afxDump. afxDump is a static variable that's part of MFC—that is, it lives in MFCxx.DLL. Under Win32, DLL data is by default non-shared. While multiple processes all share the same DLL code (and shared code is what makes DLLs so great), each process gets its own copy of DLL data. If application 1 and application 2 both use MFC42.DLL, they each get their own copy of afxDump (see Figure 4). This protects processes from one another, and it's why TrWinDll must set afxDump.m_pFile every time it loads—because each process has its own afxDump.
Figure 4  Each App Gets Its Own Copy of afxDump
Figure 4 Each App Gets Its Own Copy of afxDump

It is possible to let processes share DLL data. When data is shared, all processes access the same physical bytes in memory. You can imagine how this might be useful for constant data. If the data can't change, why create a copy for every process? But there are other times shared data is useful as well, and in fact, CInjectDll needs it. When CInjectDll::Inject calls SetWindowsHookEx to set its hook, Windows returns a handle to the hook (HHOOK), which the hook proc must pass back to Windows to call the next hook:
// "virtualized" hook proc
LRESULT CInjectDll::OnHookProc(int nCode, WPARAM wp, LPARAM lp)
{
  return nCode < 0 ? 
    CallNextHookEx(g_hHook, nCode, wp, lp) : 0;
}
CInjectDll::g_hHook is a static global that must be shared! Why? Because while you want every process to have its own copy of afxDump, theDLL, and most other variables, there's only one hook for the entire system. TRACEWIN is the only process that ever calls MfxTraceDllInit to set the hook. Once the hook is set, every process uses the same HHOOK value to call CallNextHookEx. So every process must share the same g_hHook.
If you look in InjectDll.cpp, you'll see that g_hHook is enclosed in #pragma statements:
// you must define as SHARED in .def
#pragma data_seg (".InjectDll")		
// one instance for all processes
HHOOK CInjectDll::g_hHook = NULL;	
#pragma data_seg ()
This is Microsoft® mumbo-jumbo to tell the compiler to put the variable g_hHook in the data segment called "InjectDll". (Other compilers have different syntax—check your manual.) The module definition file TrWinDll.def contains the following line
SECTIONS .InjectDll READ WRITE SHARED
Note the SHARED keyword. I point this out because if you decide to use CInjectDll to inject your own DLL, you must add the above line to your DLL's module definition file, or you'll get a nasty message from the linker complaining about undefined segments.
I mentioned that CInjectDll's hook procedure doesn't actually do anything. This isn't quite true; it does map the callback to a CInjectDLL virtual function, CInjectDll::OnHook. There's also an OnInject virtual function that CInjectDLL calls the first time OnHook is called. TRACEWIN doesn't use either of these, but you might use OnHook if you wanted to actually do stuff when the hook events happen. For example, you could watch for WM_CREATE messages and subclass new windows as they're created.
The improved TRACEWIN is really cool. Just run it to see TRACE output from any debug-build MFC app that uses MFCxx.DLL. You can even start TRACEWIN after the app is already running!

Have a question about programming in C or C++? Send it to askpd@pobox.com

From the April 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

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