The following applies to MFC 4.1 only - and even there not all versions. In order to avoid problems with
MFC, always be sure to use the latest releases. At the time of this writing this means
VC++ 5.0 at least (there is an SP 3 out for this product!)
Background
While verifying some IIS behavior, we ran into some really strange things with VC++
4.10 MFC. This article describes the bugs we (think we) have found and how they affect
your isapi apps.
In order to make this information available soon, we include a more or less raw copy of
the original warning message sent to the ISAPI-L mailing list. Thus, please pardon this
document for not being formatted like an real article.. We hope this nevertheless will be
of help to you.
We will post updates to this topic as soon as new information is available.
The Article
I have tried to use MFC's CHttpServer classes' constructors and destructors (in
contrast to DllMain()) to set up and clear some DLL global variables (VC++ 4.1, NTS 3.51
sp4, IIS 1.0c sp3). During my test I have discovered something that I would call a bug in
MFC:
My constructor gets - as expected - called when my DLL is loaded into memory. Fine. My
desctructor on the other hand *never* gets called. I then suspected this is due to
stopping IIS by aborting the debug session. To explain: I am running IIS as an application
in VC++'s debugger and do interactive debugging of my isapi app on top of this. Thus I
start my debug session (and IIS) from within Developer Studio and shut it down (as there
is no other way) by the debug/stop menu option (or shift-f5, being an old console
hack...).
I doubt that this would (again magically) prevent my destructor from being called. To
check against this, I modified the registry so that IIS will do no DLL caching. Now the
DLL will be loaded *and unloaded* for each and every (non-concurrent) request. So my
constructors and destructors should be called for sure. But... again, no problems with the
constructor. Again, too, the destructor doesn't get called. However, there is something
happening at time of DLL unload: the debug window displays a nice message:
"First-Class exception in inetinfo.exe (mydll.dll): 0xc0000005: Access
Violation". Oops... Now I know why my destructor doesn't get called: there is an
access violation (probably before the constructor ever gets control) which leads to
termination of my request (IIS obviously catches these exceptions). But why? Have I
overwritten some memory locations? Stack/heap destruction? Well, let's try and reproduce
that thing with a minimal program.
So I started and re-created an application with VC++'s app wizard (isapi extension as
target). Did some minor modification to the constructor and destructor coding and retried.
.... boom... that exception again. Now I really was puzzled. I then told the debugger to
catch that exception by himself so that I can get into control as soon as things go the
wrong way. Here is what the stack trace told me:
------- Begin Stack Trace ---------
AfxUnlockTempMaps() line 57 + 46 bytes
DllMain(HINSTANCE__ * 0x10000000, unsigned long 0, void * 0x00000000) line 110
_DllMainCRTStartup(void * 0x00000000, unsigned long 0, void * 0x10000000) line 281 + 17
bytes
NTDLL! 77f8c585()
KERNEL32! 77f34157()
W3SVC! 76d0afca()
W3SVC! 76d06770()
W3SVC! 76d0bd89()
W3SVC! 76d01804()
W3SVC! 76d01f83()
INFOCOMM! 767063ce()
------- End Stack Trace ---------
Now I was really puzzled and I began to suspect that MFC might do something wrong (btw:
it's in MFC's coding to clean up temporary window (!) objects). And than, I went into
something I call the ultimate test:
I again started a new project from scratch. Did absolutely no modification to the
source code (just pressed the f7 key) and than run the resulting DLL in my debugger ....
and boom, here we are, here is our access violation again.
Now I know for sure why my destructors (and your's probably, too) don't get called: I
think all applications generated by VC++ 4.1 isapi extension wizard will cause an access
violation at the time of their unloading. However, as this happens really seldom in real
life and IIS itself catches the exception, nobody really cares or knows. You might notice
a memory leak or some unpredictable behavior of IIS some time (maybe days) later on, but I
guess you won't necessarily think of your DLL being (better: MFC) being the real cause.
With this in mind I started a final test (just got the idea while writing this note): I
added an CWinApp object to my isapi app (ever wondered why MS did so in the WWWQuote
sample?) And: here we are, no exception is caused! However, MFC sends some output to my
debugger window that an memory leak is detected! Not nice, but lets do one further test: I
added some OutputDebugString() calls to both the constructor and desctructor of the wizard
generated program (three modifications in total now). Here is what I got:
------- Begin Debug Output ---------
Loaded symbols for 'C:\WINNT35\system32\RPCRT4.DLL'
Loaded symbols for 'C:\WINNT35\system32\USER32.DLL'
Loaded symbols for 'C:\WINNT35\system32\gdi32.dll'
Loaded symbols for 'C:\WINNT35\system32\OLE32.DLL'
Loaded symbols for 'C:\WINNT35\system32\COMDLG32.DLL'
Loaded symbols for 'C:\WINNT35\system32\SHELL32.DLL'
Loaded symbols for 'C:\WINNT35\system32\COMCTL32.DLL'
Constructor called
Detected memory leaks!
Dumping objects ->
{30} normal block at 0x00E31F48, 24 bytes long.
Data: <@ > 40 06 E1 00 FF FF FF FF 00 00 00 00 00 00 00 00
Object dump complete.
Destructor called
------- End Debug Output ---------
So here we are: VC++ class wizard generated isapi extensions in raw format (without a
CWinApp object) will cause exceptions when being unloaded. Not very nice. If you add an
CWinApp object, things go smoother, BUT some small memory allocations will be lost
forever. So be advised to be very carefully when using this features. |