今天,继续学习中,不过遇到若干内存泄漏的问题。
先说说程序构成吧,一个自制的继承自CListBox的类COwnerDrawListBox。
BOOL COwnerDrawListBox::PreCreateWindow(CREATESTRUCT& cs)
{
if(!CListBox::PreCreateWindow(cs))
return FALSE;
cs.style &= -(LBS_OWNERDRAWVARIABLE|LBS_SORT);
cs.style |= LBS_OWNERDRAWFIXED;
return TRUE;
}
void COwnerDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemHeight = 32;
}
void COwnerDrawListBox::DrawItem(LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CRect rect = lpdis->rcItem;
UINT nIndex = lpdis->itemID;
CBrush* pBrush = new CBrush(
::GetSysColor((lpdis->itemState&ODS_SELECTED)?COLOR_HIGHLIGHT:COLOR_WINDOW)
);
dc.FillRect(rect,pBrush);
delete pBrush;
dc.SelectObject(&FontGlob);
if(nIndex!=(UINT)-1)
{
dc.SetBkMode(TRANSPARENT);
CString str;
GetItemData(nIndex);
dc.TextOut(rect.left,rect.top,*((CString*)GetItemData(nIndex)));
}
dc.Detach();
}
int CMainWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
FontGlob.CreatePointFont(240,"Arial");
CClientDC dc(this);
TEXTMETRIC tm;
dc.GetTextMetrics(&tm);
m_cxChar = tm.tmAveCharWidth;
m_cyChar = tm.tmHeight + tm.tmExternalLeading;
CRect rect(
m_cxChar*2,
m_cyChar*1,
m_cxChar*40,
m_cyChar*10);
// TODO: 在此添加您专用的创建代码
m_wndCNumEdit.Create(WS_CHILD|WS_VISIBLE|WS_VSCROLL|ES_MULTILINE|ES_AUTOVSCROLL|WS_BORDER|ES_WANTRETURN,rect,this,IDC_CNUMEDIT);
rect.SetRect(m_cxChar*2,
m_cyChar*11,
m_cxChar*40,
m_cyChar*20);
m_wndCODL.Create(WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_BORDER,rect,this,IDC_CODLB);
for(int i = 0;i<10;i++)
{
CString* pStr = new CString;
pStr->Format(_T("%d"),i);
m_wndCODL.AddString(_T(""));
m_wndCODL.SetItemData(i,(DWORD)pStr);
}
return 0;
}
直接先贴代码。。。
下面是析构的东西。。。
void CMainWnd::PostNcDestroy()
{
// TODO: 在此添加专用代码和/或调用基类
delete this;
}
CMainWnd::~CMainWnd()
{
CWnd::~CWnd();
}
void CMainWnd::OnDestroy()
{
for(int i = 0;i<m_wndCODL.GetCount();i++)
{
delete (CString*)m_wndCODL.GetItemData(i);
}
CWnd::OnDestroy();
// TODO: 在此处添加消息处理程序代码
}
好了,代码贴完,开始分析。。。
因为是手写,刚开始自己写的OnPaint(){},里面没有任何东西的时候,CPU直接满载,刚开始以为是控件重画导致的,可是,直到注释掉所有的代码,才发现是OnPaint的问题,最后查了一下,是OnPaint里必须至少要有一个CPaintDC的操作,原因很简单,就是没有这样的操作,系统默认没画过,ON_WM_PAINT消息一直存在,无限循环跟while(1)差不多,以后必须注意。
后来,想用这个自制列表框来存储文字,比如现实你存进去的文字啦什么的。这次,当然失败了。。。
原来用的以下代码:
m_wndCODL.AddString(_T("Hello"));
然后在OnDrawItem中加入如下代码:
m_wndCODL.GetText(nIndex,str);
dc.TextOut(rect.left,rect.top,str);
这时候输出乱码,我以为是数据形式的问题,就各种换,但是不行,于是只好单步。
但不之后发现,AddString之后,AddString,对于不同的数值敏感,但是,数值一样,输出也一样。。
这时已经出现了Cwnd Object泄漏了。。。
这时我就想到可能是重画的原因了,去掉重画,直接Pass,这说明,开启重画之后,AddString加入的值并不是字符串了。查了一下,然后看了一下CListBox里的类函数,看到了SetItemData,稍微测试了一下证明了我的猜想。。。
好吧,接下来,SetItemData的参数为DWORD_PTR类型,即_w64 unsigned long,大小四字节,为什么是4字节?代码如下:
#if defined(_WIN64)
typedef __int64 INT_PTR, *PINT_PTR;
typedef unsigned __int64 UINT_PTR, *PUINT_PTR;
typedef __int64 LONG_PTR, *PLONG_PTR;
typedef unsigned __int64 ULONG_PTR, *PULONG_PTR;
#define __int3264 __int64
#else
typedef _W64 int INT_PTR, *PINT_PTR;
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;
typedef _W64 long LONG_PTR, *PLONG_PTR;
typedef _W64 unsigned long ULONG_PTR, *PULONG_PTR;
#define __int3264 __int32
#endif
这时候,我就明白这个DWORD_PTR的意思了,就是让你存指针用的。。。果断申请内存,CString* pStr = new CString;然后,显示,可以了。。。
泄漏问题正式开始,前文已经提到,在错误结果的时候,出现了CWnd Object泄漏,我就知道是MainWnd没有析构。。。可是怎么会出现这个呢?首先想到的是,CWinApp,因为只有CWinApp中又new CMainWnd这一句。。。然后析构,delete m_MainWnd;果断失败,不明白为什么。。。于是看了之前的例程一下,哦~~应该加个PostNcDestroy();
加上delete this,成功。。。
肯定还有失败的,那就是自己申请的那一部分内存,习惯性的加入到PostNcDestroy()中,失败,而且又出现了,CWnd Object泄漏。这下单步,看到根本没有运行delete this。。。出问题了。。。而是在删除我自己申请的内存的时候直接跳出循环的。。。感觉PostNcDestroy()只能删除一次?事实不是如此,而是,在OnDestroy之后,就已经没有控件在内存中了,所以delete出错了。。。返回值为3。。
这次,想到还有个OnDestroy 没用,在这里删除,成功了。。。这下我就想知道这几个函数之间的关系了。。。
void CMainWnd::PostNcDestroy()
{
// TODO: 在此添加专用代码和/或调用基类
delete this;
}
CMainWnd::~CMainWnd()
{
CWnd::~CWnd();
}
void CMainWnd::OnDestroy()
{
for(int i = 0;i<m_wndCODL.GetCount();i++)
{
delete (CString*)m_wndCODL.GetItemData(i);
}
CWnd::OnDestroy();
// TODO: 在此处添加消息处理程序代码
}
CMyApp::~CMyApp()
{
delete m_pMainWnd;
}
全加断点,然后运行,可以看出时间顺序。。。
首先,OnDestroy,最开始的,接受到ON_WN_DESTROY,调用这里,所以,这里释放一些用户内存是很好的。在这里,给用户释放的时间。
然后PostNcDestroy,释放用户视图,然后把自己析构掉。。。释放视图对象,因为这时候视图已经关掉了。
再就是,PostNcDestroy中的delete this,调用的~CMainWnd(),为什么不在这里释放用户内存?因为在窗口消失的那一瞬间(不是隐藏窗口),视图句柄都已经为0了,这个过程是在OnDestroy之前,但是一些全局内存仍然可以释放。
最后,就是~CMyApp(),释放掉进程,彻底关闭。