duilib菜单开发遇见“0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突”
我的程序是這樣一個邏輯。 首先創建用戶列表,點擊列表項彈出菜單,點擊菜單上“設備選項”,彈出設備列表,上面顯示這個用戶擁有的設備。
菜單的創建參考了這為博主的教程: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,覺得講的挺好的:
【菜單類小知識】
如果不用指針的方式,而直接用變量的方式顯示菜單 CDuiMenu menu(_T("Menu/menu.xml")),則不能用ShowWindow,否則會崩潰,因為出了作用域后窗口被銷毀了,所以此時可以將CDuiMenu 定義為成員變量、全局變量、或者靜態變量,但是做為一個局部使用的類,這些方法顯然不怎么好;
這時可以用ShowModal代替ShowWindow,于是就能看到窗口啦,但是卻產生了一個問題,那就是菜單窗口不會失去焦點,或者說點擊主窗口的其他區域,菜單不會消失,當然,小伙伴們可以自己捕獲鼠標,來判斷是否點擊了主窗口的其他區域,但顯然這種方法也不太好;
這個時候delete this就派上用場啦(用智能指針也會崩潰,因為出了作用域同樣會銷毀內存,所以只能用delete this啦~ 用delete this就是將作用域交給duilib了),據說COM里面就是用delete this來銷毀內存的。Alberl在duilib的Demo里面見到了大量的delete this,覺得這種自殺的方法很不靠譜,這不,前面教程就提到了ActiveX的一個bug,也是和delete this脫不了干系的~不過既然COM里面都用了delete this,那就說明如果用好這把雙刃劍,還是可以帶來很多好處的。
因為duilib提供了一個機制,就是窗口的最后一個函數一定是OnFinalMessage,之后不再調用窗口類的其他函數,這就為自殺提供了兩個必要條件;delete this而還有一個必要條件就是這個類必須是通過new來申請內存的(而非 "new[]",亦非placement的"new" ,一定要是最原始的 "new",當然malloc也行(需要用free,而不是delete)),所以就將析構函數設置成私有函數,就保證了只有通過new申請內存的方式才能編譯通過。 而duilib的Demo中大量使用delete this卻沒有保證這些必要條件,只要直接用變量的方式來聲明類,則關閉窗口時就會崩潰,作為Demo,如此不嚴謹,有待好好規范。 當然,沒有XX黨,就沒有新中國,沒有那些大神的Demo,也就輪不到Alberl唧唧歪歪啦,這里Alberl只是覺得Demo應該嚴謹和權威,畢竟是官方的,并沒有其他意思,請多多諒解~O(∩_∩)O~
最后要吸取教訓,如果遇到0xC0000005這種異常,一定要檢查對內存的操作。數組啊、指針一類的。
也有可能是,釋放了對象的對內存后繼續對對象進行操作引發的。
總結
以上是生活随笔為你收集整理的duilib菜单开发遇见“0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim添加复制(crtl+c),粘贴(c
- 下一篇: 2020年8月PYPL编程语言流行指数排