最近我维护的一个项目出了问题,会莫名其妙地崩溃,分析崩溃现场,发现是内存被莫名其妙冲坏,但是对调查没有任何帮助,今天终于查到了原因,和大家分享,希望大家引以为戒:
MSDN上说,在DLL中注册的窗口类在DLL卸载时不会自动反注册,我们程序必须保证在DLL卸载时,将所有在DLL中注册的窗口类反注册掉(原文:Windows NT/2000 or later: No window classes registered by a .dll are unregistered when the .dll is unloaded. A .dll must explicitly unregister its classes when it is unloaded. )。
MFC也是这样做的,只要我们的DLL中都使用AfxRegisterWndClass来注册窗口类,就不会出问题,MFC框架能够保证在DLL卸载时将所有在DLL中注册的窗口类反注册掉,所以微软建议在DLL中注册窗口类使用AfxRegisterWndClass调用注册窗口类。
MFC根据AfxRegisterWndClass提供的参数组装成一个窗口类名,并且使用该窗口类名注册一个窗口类,如过该窗口是在DLL中注册的,注册成功后会将该窗口类名记录到MFC的一个内部变量中,该变量是一个4096大小的字符数组,各个窗口类名之间使用”\n\0” 分隔。
为了防止相同类型的窗口类被反复注册,AfxRegisterWndClass会先检查根据参数组装的窗口类名的窗口类是否已经注册,如果已经注册,AfxRegisterWndClass就会直接返回那个窗口类名。
MFC记录下窗口类名的目的是为了在DLL卸载时,将所有在DLL中注册的窗口类都反注册掉,否则会有问题。注意,如果DLL中注册的窗口类超过了MFC设计容限,反注册列表会溢出,进而导致程序崩溃,没有任何警告,往往在运行中某一时刻爆发,这种缺陷是比较隐蔽的,再现性一般不确定,往往修正成本很高。
我们的项目的某些操作会无节制地注册大量的窗口类,会轻易超过了微软设计人员的设计界限,导致数组越界,进而导致程序崩溃。
下面附上MFC相关源代码,代码比较简单明了,很容易理解:
LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,
HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)
{
// Returns a temporary string name for the class
// Save in a CString if you want to use it for a long time
LPTSTR lpszName = AfxGetThreadState()->m_szTempClassName;
// generate a synthetic name for this class
HINSTANCE hInst = AfxGetInstanceHandle();
if (hCursor == NULL && hbrBackground == NULL && hIcon == NULL)
wsprintf(lpszName, _T("Afx:%x:%x"), (UINT)hInst, nClassStyle);
else
wsprintf(lpszName, _T("Afx:%x:%x:%x:%x:%x"), (UINT)hInst, nClassStyle,
(UINT)hCursor, (UINT)hbrBackground, (UINT)hIcon);
// see if the class already exists
WNDCLASS wndcls;
if (::GetClassInfo(hInst, lpszName, &wndcls))
{
// already registered, assert everything is good
ASSERT(wndcls.style == nClassStyle);
// NOTE: We have to trust that the hIcon, hbrBackground, and the
// hCursor are semantically the same, because sometimes Windows does
// some internal translation or copying of those handles before
// storing them in the internal WNDCLASS retrieved by GetClassInfo.
return lpszName;
}
// otherwise we need to register a new class
wndcls.style = nClassStyle;
wndcls.lpfnWndProc = DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = hIcon;
wndcls.hCursor = hCursor;
wndcls.hbrBackground = hbrBackground;
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = lpszName;
if (!AfxRegisterClass(&wndcls))
AfxThrowResourceException();
// return thread-local pointer
return lpszName;
}
// like RegisterClass, except will automatically call UnregisterClass
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
WNDCLASS wndcls;
if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName,
&wndcls))
{
// class already registered
return TRUE;
}
if (!::RegisterClass(lpWndClass))
{
TRACE1("Can't register window class named %s\n",
lpWndClass->lpszClassName);
return FALSE;
}
if (afxContextIsDLL)
{
AfxLockGlobals(CRIT_REGCLASSLIST);
TRY
{
// class registered successfully, add to registered list
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
LPTSTR lpszUnregisterList = pModuleState->m_szUnregisterList;
// the buffer is of fixed size -- ensure that it does not overflow
ASSERT(lstrlen(lpszUnregisterList) + 1 +
lstrlen(lpWndClass->lpszClassName) + 1 <
_countof(pModuleState->m_szUnregisterList));
// append classname + newline to m_szUnregisterList
lstrcat(lpszUnregisterList, lpWndClass->lpszClassName);
TCHAR szTemp[2];
szTemp[0] = '\n';
szTemp[1] = '\0';
lstrcat(lpszUnregisterList, szTemp);
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_REGCLASSLIST);
THROW_LAST();
// Note: DELETE_EXCEPTION not required.
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_REGCLASSLIST);
}
return TRUE;
}
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
BOOL bResult = FALSE;
#ifdef _AFXDLL
// wire up resources from core DLL
AfxCoreInitModule();
#endif
_AFX_THREAD_STATE* pState = AfxGetThreadState();
AFX_MODULE_STATE* pPrevModState = pState->m_pPrevModuleState;
// Initialize DLL's instance(/module) not the app's
if (!AfxWinInit(hInstance, NULL, _T(""), 0))
{
AfxWinTerm();
goto Cleanup; // Init Failed
}
// initialize the single instance DLL
CWinApp* pApp; pApp = AfxGetApp();
if (pApp != NULL && !pApp->InitInstance())
{
pApp->ExitInstance();
AfxWinTerm();
goto Cleanup; // Init Failed
}
pState->m_pPrevModuleState = pPrevModState;
#ifdef _AFXDLL
// wire up this DLL into the resource chain
VERIFY(AfxInitExtensionModule(controlDLL, hInstance));
CDynLinkLibrary* pDLL; pDLL = new CDynLinkLibrary(controlDLL);
ASSERT(pDLL != NULL);
#else
AfxInitLocalData(hInstance);
#endif
bResult = TRUE;
Cleanup:
pState->m_pPrevModuleState = pPrevModState;
#ifdef _AFXDLL
// restore previously-saved module state
VERIFY(AfxSetModuleState(AfxGetThreadState()->m_pPrevModuleState) ==
&afxModuleState);
DEBUG_ONLY(AfxGetThreadState()->m_pPrevModuleState = NULL);
#endif
return bResult;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
#ifdef _AFXDLL
// set module state for cleanup
ASSERT(AfxGetThreadState()->m_pPrevModuleState == NULL);
AfxGetThreadState()->m_pPrevModuleState =
AfxSetModuleState(&afxModuleState);
#endif
CWinApp* pApp = AfxGetApp();
if (pApp != NULL)
pApp->ExitInstance();
#ifdef _DEBUG
// check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE1("Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
#endif
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
// terminate the library before destructors are called
AfxWinTerm();
#ifdef _AFXDLL
AfxTermExtensionModule(controlDLL, TRUE);
#else
AfxTermLocalData(hInstance, TRUE);
#endif
}
else if (dwReason == DLL_THREAD_DETACH)
{
AFX_MANAGE_STATE(&afxModuleState);
#ifdef _DEBUG
// check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE1("Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
#endif
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
AfxTermThread(hInstance);
}
return TRUE;
}
void AFXAPI AfxWinTerm(void)
{
// unregister Window classes
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
AfxLockGlobals(CRIT_REGCLASSLIST);
LPTSTR lpsz = pModuleState->m_szUnregisterList;
while (*lpsz != 0)
{
LPTSTR lpszEnd = _tcschr(lpsz, '\n');
ASSERT(lpszEnd != NULL);
*lpszEnd = 0;
UnregisterClass(lpsz, AfxGetInstanceHandle());
lpsz = lpszEnd + 1;
}
pModuleState->m_szUnregisterList[0] = 0;
AfxUnlockGlobals(CRIT_REGCLASSLIST);
// cleanup OLE if required
CWinThread* pThread = AfxGetApp();
if (pThread != NULL && pThread->m_lpfnOleTermOrFreeLib != NULL)
(*pThread->m_lpfnOleTermOrFreeLib)(TRUE, FALSE);
// cleanup thread local tooltip window
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
if (pThreadState->m_pToolTip != NULL)
{
if (pThreadState->m_pToolTip->DestroyToolTipCtrl())
pThreadState->m_pToolTip = NULL;
}
if (!afxContextIsDLL)
{
// unhook windows hooks
if (pThreadState->m_hHookOldMsgFilter != NULL)
{
::UnhookWindowsHookEx(pThreadState->m_hHookOldMsgFilter);
pThreadState->m_hHookOldMsgFilter = NULL;
}
if (pThreadState->m_hHookOldCbtFilter != NULL)
{
::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);
pThreadState->m_hHookOldCbtFilter = NULL;
}
}
}
- 标 题:新查出的一个BUG,和MFC注册窗口类管理相关
- 作 者:xxxDebug
- 时 间:2010-04-01 16:14:01
- 链 接:http://bbs.pediy.com/showthread.php?t=110020