View Issue Details

IDProjectCategoryView StatusLast Update
0004591The Dark ModCodingpublic09.03.2020 04:16
Reporterstgatilov Assigned Tostgatilov  
Status resolvedResolutionfixed 
Product VersionTDM 2.06 
Target VersionTDM 2.06Fixed in VersionTDM 2.06 
Summary0004591: Debug build crashes on exit
DescriptionAs said in the title.
Steps To ReproduceHaving e.g. svn revision 7046, build any debug configuration, start TDM from MSVC, then quit game immediately.
You'll get a crash.
TagsNo tags attached.




29.07.2017 05:04

administrator   ~0009036

Last edited: 29.07.2017 05:06

View 3 revisions

Fixed in revisions 7047-7050.

Among all the problems encountered, the hardest one was MFC-related (see rev 7050). It took me several days to understand what the hell was going on, and what may help. Even reading the weird description of MFC internals did not help immediately:

I must admit that MFC is a very hacky thing: it strongly relies on global variables and static initialization/destruction. As you guess, this does not play nicely with DLL-s. During execution, MFC requires process state, thread state, module state, and thread-module state to be set properly. And as you guessed, these states must be properly switched when you change thread/module; and module switching must be done manually sometimes. All of this can be called "state hell" !

The crash on exit in debug configurations was caused by double destruction of thread-module state. Debug heap fills freed memory with 0xFEEE, so thread-module state was incorrect during second destruction, which caused access violation.
This problem did not happen before Duzenko's recent refactoring, because MFC was statically linked into TDM. Now it is linked dynamically. Reverting to static linkage is a bad option, because it forces to link CRT statically too =(

In MFC, each application must have exactly one CWinApp object, defined as a global variable. And this is how things work with static MFC linkage: there is only "CRadiantApp theApp". However, when MFC is linked dynamically, the second hidden CWinApp appears: it is defined as global variable mfc120d.dll!_afxOleWinApp (defined in MFC sources).
When ExitProcess is called, all global variables are destroyed, including both CWinApp-s. The _afxOleWinApp is destroyed first, and it destroys its own module state (also a global variable mfc120d.dll!_afxBaseModuleState). Then CRadiantApp is destroyed, but due to some reason it tries to destroy the same module state, so double destruction happens. In order to work properly, CRadiantApp must point to its own module state, since TheDarkMod.exe and mfc120d.dll are different modules.
I don't know exactly why this happens. Normally, MFC application starts with a built-in WinMain function, which initializes all the states for the CWinApp. In case of Doom 3, custom WinMain function is defined, so built-in initialization does not happen. Trying to copy the steps from appmodul.cpp caused some weird linking errors...

So I tried to simply create module state myself (radiantState) and point "CRadiantApp theApp" to it directly. Strangely, one change did not suffice, so I had to do two:
1. Assign theApp.m_pModuleState = &radiantState manually. Very hacky.
2. Call AfxSetModuleState after _afxOleWinApp is destroyed but before theApp is destroyed (this happens during globals destruction, so it has to be done in CRadiantApp's destructor).

Now it works.
But frankly speaking, I wish MFC would be eradicated from TDM one day.

Issue History

Date Modified Username Field Change
29.07.2017 04:45 stgatilov New Issue
29.07.2017 04:45 stgatilov Status new => assigned
29.07.2017 04:45 stgatilov Assigned To => stgatilov
29.07.2017 05:04 stgatilov Note Added: 0009036
29.07.2017 05:06 stgatilov Note Edited: 0009036 View Revisions
29.07.2017 05:06 stgatilov Note Edited: 0009036 View Revisions
29.07.2017 05:06 stgatilov Status assigned => resolved
29.07.2017 05:06 stgatilov Resolution open => fixed
09.03.2020 04:16 stgatilov Fixed in Version => TDM 2.06