朱金灿
最近使用VC的计时器。计时器一般是先设置一个Timer,然后响应WM_TIMER消息,然后销毁计时器。但是我发现在哪里设置计时器和销毁计时器是有讲究的。
开始我的代码是这样的:
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
SetTimer(1, 5000, 0);
}
// WM_TIMER消息响应函数
void CMainFrame::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
AfxMessageBox("Hello World!");
CFrameWnd::OnTimer(nIDEvent);
}
CMainFrame::~CMainFrame()
{
KillTimer(1);
}
编译结果是,debug模式编译下出现Assertion Fauled,
调试时发现出错是在BOOL CTestTimerApp::InitInstance()函数中的一句:
if (!ProcessShellCommand(cmdInfo))
return FALSE;
发生的。
但是在release模式编译成功。想来是因为release模式忽略Assert宏的缘故,但是运行程序却没有Hello World!的对话框弹出。
后来我想莫非是SetTimer(1, 5000, 0);函数放置的地方不对的缘故。后来我把它
框架类的OnCreate函数。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
SetTimer(1, 5000, 0); // 在这里设置计时器
return 0;
}
这样编译debugm通过, Hello World!的对话框也出来了。但是在程序退出时检测到有内存泄露:Detected memory leaks!
我估计在销毁计时器的代码有问题。于是我把放到响应WM_DESTROY消息的函数里。
void CMainFrame::OnDestroy()
{
CFrameWnd::OnDestroy();
// TODO: Add your message handler code here
KillTimer(1);
}
为什么设置计时起不能放在窗口类构造函数,销毁计时器不能放在窗口类的析构函数里?让我们看看MFC的源码:
CWnd::SetTimer(UINT nIDEvent, UINT nElapse,
void (CALLBACK* lpfnTimer)(HWND, UINT, UINT, DWORD))
{ ASSERT(::IsWindow(m_hWnd)); return ::SetTimer(m_hWnd, nIDEvent, nElapse,
(TIMERPROC)lpfnTimer); }
果然有一个ASSERT宏。而且这个宏很明确地告诉我们必须在窗口句柄有效的时候才能设置计时器。同样道理,在销毁计时器时也必须确保窗口句柄是有效的。