zoukankan      html  css  js  c++  java
  • 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这种异常,一定要检查对内存的操作。数组啊、指针一类的。

    也有可能是,释放了对象的对内存后继续对对象进行操作引发的。

  • 相关阅读:
    vs 2015 安装
    NPOI封装
    c#事件求解
    一个ERP系统的磕磕碰碰
    谁动了我的产品
    MVC Sesion丢失问题
    设计模式之类关系
    免费的SqlServer优化辅助工具:SqlOptimize (原创)
    Entity Framework Linq 简单笔记
    RhinoMocks简单范例
  • 原文地址:https://www.cnblogs.com/rixiang/p/8554203.html
Copyright © 2011-2022 走看看