To Brian and Martin, who were involved in helping me out with this, please don't shout at me...
This thread does follow on from that post - and I'm now investigating refactoring a static library which contains a few MFC classes into a shared DLL - using __declspec(dllexport) to export functions and classes that will be needed across all facets of the solution that need the shared functionality.
I've looked all over the place for an answer to this question - and found loads of other people using MFC 6, 7 and 8 with similar problems. I'm not going to be naive enough to suggest that MFC has a bug - I'm well prepared for this being a schoolboy error on my part.
I've got a resource-based exported CDialog implementation in a DLL that I want to show from an application. I've got the window showing up by calling DoModal with no problems at all, but when I dismiss the dialog, I get an assert in wincore.cpp in CWnd::DestroyWindow as follows (the line in blue is not the error, but I refer to it a bit later as the possible source of the problem, or symptom of a mistake I've made; the line in red is where the assertion occurs):
BOOL CWnd::DestroyWindow()
{
CWnd* pWnd;
CHandleMap* pMap;
HWND hWndOrig;
BOOL bResult;
if ((m_hWnd == NULL) && (m_pCtrlSite == NULL))
return FALSE;
bResult = FALSE;
pMap = NULL;
pWnd = NULL;
hWndOrig = NULL;
if (m_hWnd != NULL)
{
pMap = afxMapHWND();
ENSURE(pMap != NULL);
pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd);
#ifdef _DEBUG
hWndOrig = m_hWnd;
#endif
}
#ifdef _AFX_NO_OCC_SUPPORT
if (m_hWnd != NULL)
bResult = ::DestroyWindow(m_hWnd);
#else //_AFX_NO_OCC_SUPPORT
if ((m_hWnd != NULL) || (m_pCtrlSite != NULL))
{
if (m_pCtrlSite == NULL)
bResult = ::DestroyWindow(m_hWnd);
else
bResult = m_pCtrlSite->DestroyControl();
}
#endif //_AFX_NO_OCC_SUPPORT
if (hWndOrig != NULL)
{
// Note that 'this' may have been deleted at this point,
// (but only if pWnd != NULL)
if (pWnd != NULL)
{
// Should have been detached by OnNcDestroy
#ifdef _DEBUG
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL);
#endif
}
else
{
#ifdef _DEBUG
ASSERT(m_hWnd == hWndOrig);
#endif
// Detach after DestroyWindow called just in case
Detach();
}
}
return bResult;
}
That red line is where it asserts. If I ignore the assertion, the dialog happily dismisses and no error tracing occurs - therefore in Release mode the code will work with no complaints. But clearly, during development, having to ignore this assertion when it could potentially happen many many times is not really an option.
At this point, m_hWnd is now NULL, and hWndOrig is, of course, set to the window handle that previously represented the dialog whilst it was on screen. Looking through the code that is executed, it seems that the line in blue should be setting the pWnd pointer to a safe CWnd pointer that the framework can use - but it's actually getting set to NULL. What should be happening is that the first part of the IF statement within which this assertion is happening should be executed - that is the framework is validating that the window has definitely been destroyed, but it's not - because this pWnd pointer is NULL.
Both the app and the dll are using the default for VS2005 MFC projects: shared MFC dll.
I've overriden CDialog::DoModal and DestroyWindow with calls to AFX_MANAGE_STATE(AfxGetStaticModuleState()) before the framework calls to ensure that resources are properly resolved (before doing this, the Dialog was not being shown at all).
Any thoughts would be greatly appreciated (Martin, Brian - go easy on me if you reply!
)

CDialog::DoModal asserting in CWnd::DestroyWindow
Skov Trolden
I think you've got it. I breakpointed CWnd::Detach and it's called before CWnd::Destroy calls it after being called from DoModal. Here's the call stack:
> mfc80ud.dll!CWnd::Detach() Line 351 C++
mfc80ud.dll!CWnd::OnNcDestroy() Line 846 C++
mfc80ud.dll!CWnd::OnWndMsg(unsigned int message=130, unsigned int wParam=0, long lParam=0, long * pResult=0x0012ef4c) Line 2028 C++
mfc80ud.dll!CWnd::WindowProc(unsigned int message=130, unsigned int wParam=0, long lParam=0) Line 1741 + 0x20 bytes C++
mfc80ud.dll!AfxCallWndProc(CWnd * pWnd=0x0035d880, HWND__ * hWnd=0x00190628, unsigned int nMsg=130, unsigned int wParam=0, long lParam=0) Line 240 + 0x1c bytes C++
mfc80ud.dll!AfxWndProc(HWND__ * hWnd=0x00190628, unsigned int nMsg=130, unsigned int wParam=0, long lParam=0) Line 389 C++
mfc80ud.dll!AfxWndProcBase(HWND__ * hWnd=0x00190628, unsigned int nMsg=130, unsigned int wParam=0, long lParam=0) Line 407 + 0x15 bytes C++
user32.dll!EnableMenuItem() + 0x4cd4 bytes
I've noted here that my own Dialog does not appear anywhere in the call stack, therefore when CWnd::Detach resets the CWnd's m_hWnd member to NULL, it's not actually setting the m_hWnd member of my dialog, but a temporary CWnd that the framework has obtained, presumably in AfxCallWndProc.
Consequently, CWnd::Destroy called from within my dialog's DoModal loop doesn't find a valid CWnd because the window handle has already been released from the global resource pool, but the dialog's m_hWnd member still appears to be valid as far as the framework is concerned.
Then, just to rub salt in the wounds (but not necessarily adding to the problem - it's just quite amusing to see it), CDialog::DoModal then calls PostModal, which calls CWnd::Detach again!
I'm starting to think that this is a framework bug Not serious I know, because you can simply ignore the assertion - but that's not really the point is it! It's clear from the number of times CWnd::Detach is called that MFC employs a 'better safe than sorry' approach, where it ends up calling the same method a few times.
It'll be interesting to hear your thoughts.
Thanks.
Pontus Bergquist
Why are you doing this in a regular DLL Wouldn't an extension DLL be more suitable And you can avoid all this hassle.
Ted Herrman
Hi Brian,
Firstly, thanks for your guidance on this. One of the things I'm going to do is check out an MFC extension DLL to see if the same problems occur. We know that opening a dialog normally with DoModal does not cause this problem, so therefore the cause is more likely my implementation, rather than a bug in MFC. Unless it's the case that there should not be anything else special to be done in order to get it working - then it'll be bug-report time!
I'll investigate workarounds also (overriding the DoModal loop has been a thought - but that's extreme I know). It might even be the case that it's only DoModal dialogs that cause a problem and modeless windows won't give me the same trouble - in which case I'll avoid using dialogs modally from the DLL, not ideal - but then needs must when you're on a deadline eh!
However, I'll definitely investigate further starting with the method you suggest, and if I get any further then I'll certainly post it on this thread. I think I mentioned at the beginning that I searched around for an answer before posting, and there seems to have been lots of people with similar problems through at least the last 3 versions of MFC - and in all my searching nobody seems to have provided an answer (nobody saying 'Yep, that solved it, thanks!'). All the threads just tail off as these kinds of issues often do.
Let's hope this one can change that!
Thanks again for all your help
Chouproute
Just a quickie - got no problems at all when exporting the dialog from an MFC extension DLL instead of a standard MFC DLL - so that would appear to be the easiest solution in these situations.
Another thing I'd been trying was to have an app that dynamically loads its startup window from another DLL - I was testing it by embedding the dialog in the DLL and exporting it, then basically creating an instance of the Dialog in the InitInstance method instead of the default Dialog you get with the MFC dialog project. With a standard MFC Dll the call to DoModal always failed miserably - runtime error complaining about ESP register not being saved properly across function calls.
With the extension DLL, however, no problem at all. So that's another thing it's fixed.
However, i'll compare the behaviour between the two methods (stepping through and Spy++ on hwnds etc as previously suggested) at some point to find out if the assertion is by design when creating an MFC Dll or not. See if we can shed some light on the subject.
Cheers.
Mafalda con Petalos
I think your issue is too complicated for me to guess at what the problem is.
It might appear that MFC has a bug, and MFC bugs are always a possibility, but for your sake it's better to not assume this and seek a workaround. Instead, simplify your scenario and find out exactly what kind of complexity in your setup is causing this "double detach."
In your careful analysis, make a diagram of the HWND values from Spy++ and make sure that you're seeing a case of a double detach. If you really are, then look at the callstack to decide which one is the erroneous one. If you end up solving this, I'd be interesting in hearing about it.
Also, if you know the HWND prior to the Attach, it might be useful to set a conditional breakpoint in Attach set to that particular value. Are Attach/Detach balanced One Attach vs two Detaches etc
(Re-edit). Sorry, that won't work. HWNDs are not consistent between runs (but pointers usually are.)
Brian
Steven Hemingray
I know - I just feel a bit embarrassed because I feel I should have moved on a bit further than I have from my previous problem (getting nowhere fast!).
I'll check this out - it'll be interesting to see if that's the case.
Thanks.
SupermanAlchemist
For the record, I never shouted at you or did anything that might be considered "not going easy on you."
Is there any point prior to DestroyWindow where
pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd);
is not NULL I would first investigate this, and then also see if somehow this m_hWnd is being Detached (CWnd::Detach) earlier than you expected (i.e. set a breakpoint in Detach and record hWnds and callstacks).
Brian
Manoj Kumar31390
Thanks for your reply.
Unfortunately, I can't avoid the hassle because the reason I need a shared DLL (or similar) of base functionality is because the project I'm working on needs an extendable type system that would encompass not just objects but also window objects as well.
.Net was originally considered for this project (obvious advantages such as reflection of constructors, etc) but certain aspects of the project are pushing around potentially huge amounts of data and must respond to it in realtime. In early proof of concepts that I've developed, it was clear that .Net was going to swallow up far too much memory to make it viable. Therefore, after a nerve-wracking board meeting where I had to belay all fears over dev time(!) and the like, I persuaded the project board to let me use my great personal friend C++. We've already achieved our first deliverable ahead of schedule, and now I'm investigating a few potential issues in lunch breaks, etc, to smooth out the way for the next phase.
I'll try implementing as an extension DLL as well, and see if it works - I'd thought about doing that, perhaps I should have tried it before posting... will come back if the problem still occurs.
Cheers.