duilib菜单开发遇见“0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突”
我的程序是這樣一個邏輯。 首先創(chuàng)建用戶列表,點擊列表項彈出菜單,點擊菜單上“設(shè)備選項”,彈出設(shè)備列表,上面顯示這個用戶擁有的設(shè)備。
菜單的創(chuàng)建參考了這為博主的教程:http://www.cnblogs.com/Alberl/category/520438.html
如圖點擊列表項,彈出菜單中點擊“設(shè)備”,運行新的窗口 “設(shè)備列表”。
接下來問題出現(xiàn)了,上面操作重復(fù)兩遍,會在第二次關(guān)閉設(shè)備列表的時候 發(fā)生異常,程序崩潰。
這就讓我非常頭痛了。
我知道這種錯誤是內(nèi)存訪問問題,一般都是指針操作不當(dāng)造成的。
調(diào)試程序,中斷發(fā)生位置是notify函數(shù)(duilib響應(yīng)函數(shù))結(jié)束位置。總之不是發(fā)生錯誤的位置。
下面貼出菜單程序源代碼:
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(); // 私有化析構(gòu)函數(shù),這樣此對象只能通過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);
// 顯示該用戶設(shè)備列表
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);
}
創(chuàng)建菜單的代碼,在user列表的notify函數(shù)里,POINT用來記錄菜單生成的位置坐標(biāo):
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);
}
發(fā)生中斷的位置就是notify函數(shù)結(jié)束的位置,真是看的我一頭霧水啊,中斷位置跳到反匯編來看也看不出所以然。
試了一天,最后到了晚上才發(fā)現(xiàn)問題所在,那就是delete。
.h文件可知,該程序私有化析構(gòu)函數(shù),使得只能new來創(chuàng)建,這就需要在合適時機去delete。
程序原本將delete寫在OnFinalMessage函數(shù)里。但在實際調(diào)試過程中,發(fā)現(xiàn)在執(zhí)行了OnFinalMessage函數(shù)的delete后,程序竟然又進入到notify函數(shù)里,隨后報錯。
我也不是很明白,為什么點擊一次菜單,會進入兩次notify函數(shù),對于duilib的消息機制也不是那么精通。
最后我的解決方案,就加入一個計數(shù)的變量。進入notify創(chuàng)建一次設(shè)備列表,則計數(shù)變量+1。如果計數(shù)變量大于0,則不再創(chuàng)建設(shè)備列表。且只有計數(shù)變量大于0的時候,才執(zhí)行delete。
如下,計數(shù)變量為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(); // 私有化析構(gòu)函數(shù),這樣此對象只能通過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);
// 顯示該用戶設(shè)備列表
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);
}
轉(zhuǎn)載一下原作者對于duilib菜單的理解https://www.cnblogs.com/Alberl/p/3352461.html,覺得講的挺好的:
【菜單類小知識】
如果不用指針的方式,而直接用變量的方式顯示菜單 CDuiMenu menu(_T("Menu/menu.xml")),則不能用ShowWindow,否則會崩潰,因為出了作用域后窗口被銷毀了,所以此時可以將CDuiMenu 定義為成員變量、全局變量、或者靜態(tài)變量,但是做為一個局部使用的類,這些方法顯然不怎么好;
這時可以用ShowModal代替ShowWindow,于是就能看到窗口啦,但是卻產(chǎn)生了一個問題,那就是菜單窗口不會失去焦點,或者說點擊主窗口的其他區(qū)域,菜單不會消失,當(dāng)然,小伙伴們可以自己捕獲鼠標(biāo),來判斷是否點擊了主窗口的其他區(qū)域,但顯然這種方法也不太好;
這個時候delete this就派上用場啦(用智能指針也會崩潰,因為出了作用域同樣會銷毀內(nèi)存,所以只能用delete this啦~ 用delete this就是將作用域交給duilib了),據(jù)說COM里面就是用delete this來銷毀內(nèi)存的。Alberl在duilib的Demo里面見到了大量的delete this,覺得這種自殺的方法很不靠譜,這不,前面教程就提到了ActiveX的一個bug,也是和delete this脫不了干系的~不過既然COM里面都用了delete this,那就說明如果用好這把雙刃劍,還是可以帶來很多好處的。
因為duilib提供了一個機制,就是窗口的最后一個函數(shù)一定是OnFinalMessage,之后不再調(diào)用窗口類的其他函數(shù),這就為自殺提供了兩個必要條件;delete this而還有一個必要條件就是這個類必須是通過new來申請內(nèi)存的(而非 "new[]",亦非placement的"new" ,一定要是最原始的 "new",當(dāng)然malloc也行(需要用free,而不是delete)),所以就將析構(gòu)函數(shù)設(shè)置成私有函數(shù),就保證了只有通過new申請內(nèi)存的方式才能編譯通過。 而duilib的Demo中大量使用delete this卻沒有保證這些必要條件,只要直接用變量的方式來聲明類,則關(guān)閉窗口時就會崩潰,作為Demo,如此不嚴謹,有待好好規(guī)范。 當(dāng)然,沒有XX黨,就沒有新中國,沒有那些大神的Demo,也就輪不到Alberl唧唧歪歪啦,這里Alberl只是覺得Demo應(yīng)該嚴謹和權(quán)威,畢竟是官方的,并沒有其他意思,請多多諒解~O(∩_∩)O~
最后要吸取教訓(xùn),如果遇到0xC0000005這種異常,一定要檢查對內(nèi)存的操作。數(shù)組啊、指針一類的。
也有可能是,釋放了對象的對內(nèi)存后繼續(xù)對對象進行操作引發(fā)的。
總結(jié)
以上是生活随笔為你收集整理的duilib菜单开发遇见“0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim添加复制(crtl+c),粘贴(c
- 下一篇: 2020年8月PYPL编程语言流行指数排