我的程序是这样一个逻辑。 首先创建用户列表,点击列表项弹出菜单,点击菜单上“设备选项”,弹出设备列表,上面显示这个用户拥有的设备。
菜单的创建参考了这为博主的教程:http://www.cnblogs.com/Alberl/category/520438.html
如图点击列表项,弹出菜单中点击“设备”,运行新的窗口 “设备列表”。
接下来问题出现了,上面操作重复两遍,会在第二次关闭设备列表的时候 发生异常,程序崩溃。
这就让我非常头痛了。
我知道这种错误是内存访问问题,一般都是指针操作不当造成的。
调试程序,中断发生位置是notify函数(duilib响应函数)结束位置。总之不是发生错误的位置。
下面贴出菜单程序源代码:
MenuWnd2.h:
#pragma once #include <windows.h> #include "my_duilib.h" #include <iostream> class CUserManageMenuWnd: public CXMLWnd { public: explicit CUserManageMenuWnd(LPCTSTR pszXMLPath,int tag); protected: virtual ~CUserManageMenuWnd(); // 私有化析构函数,这样此对象只能通过new来生成,而不能直接定义变量。就保证了delete this不会出错 public: void Init(HWND hWndParent, POINT ptPos); virtual void OnFinalMessage(HWND hWnd); virtual LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); virtual LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual void Notify( TNotifyUI& msg ); private: int tag; };
MenuWnd2.cpp:
#include "MenuWnd2.h" #include "my_including.h" #include "page_info.h" #include "mysql_utils.h" #include "user_dev_lst.h" extern c_page_info page_info; extern user_sel_ret* user_arr; CUserManageMenuWnd::CUserManageMenuWnd( LPCTSTR pszXMLPath, int tag) : CXMLWnd(pszXMLPath){ this->tag = tag; } CUserManageMenuWnd::~CUserManageMenuWnd(){ } void CUserManageMenuWnd::Init( HWND hWndParent, POINT ptPos ){ Create(hWndParent, _T("MenuWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); ::ClientToScreen(hWndParent, &ptPos); ::SetWindowPos(*this, NULL, ptPos.x, ptPos.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } void CUserManageMenuWnd::OnFinalMessage( HWND /*hWnd*/ ) { delete this; } LRESULT CUserManageMenuWnd::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam ) { LRESULT lRes = 0; BOOL bHandled = TRUE; switch( uMsg ) { case WM_KILLFOCUS: lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break; default: bHandled = FALSE; } if(bHandled || m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes)) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); } void CUserManageMenuWnd::Notify( TNotifyUI& msg ) { int num; string user_id; int dev_num; dev_sel_ret* devs; if( msg.sType == _T("itemclick") ) { string click_menu_option = msg.pSender->GetName().ToString(); if( !click_menu_option.compare(_T("check_devs")) ) { PostMessage(WM_KILLFOCUS); num = page_info.get_begin_index() + this->tag; user_id = user_arr[num].id; devs = MYSQL_INTERFACES::select_devs_of_user("", &dev_num, user_id); // 显示该用户设备列表 create_usr_dev_lst_win(dev_num, devs); } __super::Notify(msg); } LRESULT CUserManageMenuWnd::OnKillFocus( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { Close(); bHandled = FALSE; return __super::OnKillFocus(uMsg, wParam, lParam, bHandled); }
创建菜单的代码,在user列表的notify函数里,POINT用来记录菜单生成的位置坐标:
void CUsrManageWnd::Notify( TNotifyUI& msg ) { if(msg.sType == _T("itemclick")) { int i_index = msg.pSender->GetTag(); POINT pt = {msg.ptMouse.x, msg.ptMouse.y}; CUserManageMenuWnd *p_menu = new CUserManageMenuWnd(_T("Menu/menu2.xml"), i_index); p_menu->Init(g_usr_manage_win_hwnd, pt); p_menu->ShowWindow(TRUE); } __super::Notify(msg); }
发生中断的位置就是notify函数结束的位置,真是看的我一头雾水啊,中断位置跳到反汇编来看也看不出所以然。
试了一天,最后到了晚上才发现问题所在,那就是delete。
.h文件可知,该程序私有化析构函数,使得只能new来创建,这就需要在合适时机去delete。
程序原本将delete写在OnFinalMessage函数里。但在实际调试过程中,发现在执行了OnFinalMessage函数的delete后,程序竟然又进入到notify函数里,随后报错。
我也不是很明白,为什么点击一次菜单,会进入两次notify函数,对于duilib的消息机制也不是那么精通。
最后我的解决方案,就加入一个计数的变量。进入notify创建一次设备列表,则计数变量+1。如果计数变量大于0,则不再创建设备列表。且只有计数变量大于0的时候,才执行delete。
如下,计数变量为new_win_num。
#pragma once #include <windows.h> #include "my_duilib.h" #include <iostream> class CUserManageMenuWnd: public CXMLWnd { public: explicit CUserManageMenuWnd(LPCTSTR pszXMLPath,int tag); protected: virtual ~CUserManageMenuWnd(); // 私有化析构函数,这样此对象只能通过new来生成,而不能直接定义变量。就保证了delete this不会出错 public: void Init(HWND hWndParent, POINT ptPos); virtual void OnFinalMessage(HWND hWnd); virtual LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); virtual LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual void Notify( TNotifyUI& msg ); private: int tag; int new_win_num; };
#include "MenuWnd2.h" #include "my_including.h" #include "page_info.h" #include "mysql_utils.h" #include "user_dev_lst.h" extern c_page_info page_info; extern user_sel_ret* user_arr; CUserManageMenuWnd::CUserManageMenuWnd( LPCTSTR pszXMLPath, int tag) : CXMLWnd(pszXMLPath){ this->tag = tag; this->new_win_num = 0; } CUserManageMenuWnd::~CUserManageMenuWnd(){ } void CUserManageMenuWnd::Init( HWND hWndParent, POINT ptPos ){ Create(hWndParent, _T("MenuWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); ::ClientToScreen(hWndParent, &ptPos); ::SetWindowPos(*this, NULL, ptPos.x, ptPos.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } void CUserManageMenuWnd::OnFinalMessage( HWND /*hWnd*/ ) { if (new_win_num >0) delete this; } LRESULT CUserManageMenuWnd::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam ) { LRESULT lRes = 0; BOOL bHandled = TRUE; switch( uMsg ) { case WM_KILLFOCUS: lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break; default: bHandled = FALSE; } if(bHandled || m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes)) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); } void CUserManageMenuWnd::Notify( TNotifyUI& msg ) { int num; string user_id; int dev_num; dev_sel_ret* devs; if( msg.sType == _T("itemclick") ) { string click_menu_option = msg.pSender->GetName().ToString();if( !click_menu_option.compare(_T("check_devs")) ) { if (new_win_num == 0) { PostMessage(WM_KILLFOCUS); num = page_info.get_begin_index() + this->tag; user_id = user_arr[num].id; devs = MYSQL_INTERFACES::select_devs_of_user("", &dev_num, user_id); // 显示该用户设备列表 create_usr_dev_lst_win(dev_num, devs); } new_win_num++; } } __super::Notify(msg); } LRESULT CUserManageMenuWnd::OnKillFocus( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { Close(); bHandled = FALSE; return __super::OnKillFocus(uMsg, wParam, lParam, bHandled); }
转载一下原作者对于duilib菜单的理解https://www.cnblogs.com/Alberl/p/3352461.html,觉得讲的挺好的:
最后要吸取教训,如果遇到0xC0000005这种异常,一定要检查对内存的操作。数组啊、指针一类的。
也有可能是,释放了对象的对内存后继续对对象进行操作引发的。