【问题标题】:Crash when calling virtual function调用虚函数时崩溃
【发布时间】:2010-07-30 18:15:11
【问题描述】:

好的,这是一个非常奇怪的问题。我想首先说我不是 C++ 的初学者,而且我当然不是高级的。我在中间的某个地方。我想要做的是制作 Win32 API 的 C++ OOP 包装库 (dll)。这是我图书馆的课程。我用 Mingw 编译它,使用命令:

g++ -shared -o bin\win32oop.dll src\Application.cpp src\Form\Form.cpp -Wall

src\Application.h:

#ifndef WOOP_APPLICATION_H_
#define WOOP_APPLICATION_H_

namespace Woop
{
 class Application
 {
 public:
  bool Init(void);
  virtual bool OnInit(void);
 };
}

#endif // WOOP_APPLICATION_H_

src\Application.cpp

#include <windows.h>
#include "Application.h"
#include "Form\Form.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

namespace Woop
{
 bool Application::Init(void)
 {
  WNDCLASSEX wc;

  wc.cbSize        = sizeof(WNDCLASSEX);
  wc.style         = 0;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = GetModuleHandle(NULL);
  wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = "woop";
  wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

  if(RegisterClassEx(&wc) == 0)
  {
   MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
   return false;
  }

  this->OnInit();

  return true;
 }

 bool Application::OnInit(void)
 {
  return true;
 }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Woop::Form *wnd = 0;

    if (uMsg == WM_NCCREATE) 
 {   
        SetWindowLong (hwnd, GWL_USERDATA, long((LPCREATESTRUCT(lParam))->lpCreateParams));
    }

 wnd = (Woop::Form *)(GetWindowLong (hwnd, GWL_USERDATA));

    if (wnd) return wnd->WndProc(hwnd, uMsg, wParam, lParam);

    return ::DefWindowProc (hwnd, uMsg, wParam, lParam);
}

src\Form\Form.h

#ifndef WOOP_FORM_FORM_H_
#define WOOP_FORM_FORM_H_

namespace Woop
{
 class Form
 {
 public:
  bool Show(void);
  virtual LRESULT WndProc(HWND, UINT, WPARAM, LPARAM);
 protected:
  HWND _handle;
 };
}

#endif // WOOP_FORM_FORM_H_

src\Form\Form.cpp

#include <windows.h>
#include "Form.h"

namespace Woop
{
 bool Form::Show(void)
 {
  _handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandle(NULL), this);

  if(_handle == NULL)
  {
   MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
   return false;
  }

  ShowWindow(_handle, SW_SHOWNORMAL);

  return true;
 }

 LRESULT Form::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
  switch(uMsg)
  {
   case WM_DESTROY:
    PostQuitMessage(0);
   break;            
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }
}

这是我用来测试库的程序:

class SampleApp : public Woop::Application
{
 bool OnInit(void)
        {
         Form form;
         form.Show();

         return true;
        }
};

INT APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
{
 SampleApp application;
 if(application.Init() == false) return 0;

 MSG Msg;
 while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

 return 0;
}

好的,现在问题来了。您在 Form 类中看到那个虚拟窗口过程了吗?如果我从声明中删除虚拟,程序编译并运行良好。但是当我重新添加它时,它会崩溃。臭名昭著的“不要发送”对话框出现了。我不确定它什么时候崩溃,我会尝试使用 MessageBox() 来解决这个问题(哈哈,这是我没有学习如何使用 gdb 进行调试的结果)。我正在尝试这样做,以便我可以创建诸如 LoginForm 之类的类并从 Form 派生并覆盖窗口过程。我希望我能很好地解释这个问题:D。这可能是编译器错误或我的愚蠢:P。无论如何,提前谢谢。

【问题讨论】:

  • 您从GetWindowLong() 获得并转换为Form*(在全局WndProc() 中)实际上是指向Form 的有效指针吗?如果没有,那么虚拟呼叫就会爆炸。
  • 看看你在这里尝试做什么很好地解释了 MS 为了让他们的 MFC C++ OO API 在 Win32 窗口系统上运行而采取的措施。
  • 感谢参考马丁。我正在寻找一本关于在 c++ OOP 中包装 Win32 的好书。

标签: c++ windows debugging winapi crash


【解决方案1】:

问题出在这里:

bool OnInit(void) 
{ 
     Form form; 
     form.Show(); 

     return true; 
}

该方法返回时表单对象被销毁。
因此,您在调用 Show() 时存储的 this 指针不再有效。

  _handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL,
                           GetModuleHandle(NULL), 
  /* Here ----> */         this
                          ); 

当您尝试执行调度时,它真的搞砸了,因为它使用this 指针来计算要调用的虚函数的地址。

它与virtual而不是在你带走virtual时崩溃的原因是虚拟方法地址是在运行时计算的,而普通方法地址是在编译时种植的。

在计算虚拟方法的地址时,this 指针以某种方式被取消引用(在这种情况下导致 UB),但由于对象已被销毁,该地址处的数据可能已被重新使用,因此你为函数获得的地址是一些随机的垃圾,调用它永远不会好。

一个简单的解决方案是让表单成为应用程序对象的一部分。
因此它的生命周期与应用程序相同:

class SampleApp : public Woop::Application 
{ 
    Form form;

    bool OnInit(void) 
    { 
        form.Show(); 

        return true; 
    } 
}; 

【讨论】:

  • 是的,我想你明白了。我将尝试在'return true;'之前将消息循环放入该函数中。
  • 是的,这就是问题所在。感谢您的解决方案。当我将消息循环放在 OnInit 函数中时,也起作用了。但你的更简单和优雅。我想知道我是否将 OnInit 函数内联,只是为了让自己在函数内部创建 Form 对象?
  • 内联函数不起作用。您知道如何在 OnInit() 函数中创建 Form 对象吗?
【解决方案2】:
 wc.lpfnWndProc   = WndProc;

这在一般情况下不起作用,尽管 WndProc 的位置并不明显。 Windows 不会提供实例方法在进行回调时所需的“this”指针。您现在可以摆脱它,因为您没有在 Form::WndProc() 方法中访问 Form 类的任何成员。它在没有 virtual 关键字的情况下偶然工作,但是一旦你开始接触成员,运气就会很快耗尽。这将引发 AccessViolation 异常。

您需要将 Form::WndProc() 方法设为静态方法。

虚拟化需要您编写将 HWND 映射到 Form 实例的代码。这是包装 Win32 API 的任何类库的一个非常标准的功能。不必重新发明那个轮子有很多价值。

【讨论】:

  • WndProc 的使用将引用全局函数,而不是 Form 的成员。
  • WndProc 是在 Application.cpp 中本地声明的,Form 中的 WndProc 不能是静态的,因为所有不同的表单都需要处理自己的事件。
  • 是的,明白了。我告诉过你需要做什么才能使其成为虚拟,你必须将窗口句柄映射到包装它的 C++ 类对象。 std::map 可以做到这一点。查看您最喜欢的 C++ 编程书籍,了解有关成员函数指针如何工作的建议。
  • 是的,哈哈,我本来打算放弃这个,只使用 std::map 但我想使用 MFC、WTL 和 wxWidgets 等消息映射
【解决方案3】:

不知道是不是这里的问题,但总是间接调用虚函数。这意味着在调用虚函数之前访问对象以读取虚函数表。这意味着如果对象已被覆盖(已删除、堆或堆栈上的缓冲区溢出等),虚拟方法比其他方法更有可能崩溃。

尤其如此,因为大多数成员变量在损坏时不会导致崩溃,因此有时您可以轻松地监督堆/堆栈损坏问题。

顺便说一句:有时使用 gdb 开始调试非常简单:只需将其加载到 gdb (gdb myprogram) 中并输入 run。当程序崩溃时,使用“bt”获取回溯,您应该查看崩溃是发生在虚拟方法内部还是在调用它时发生的。

【讨论】:

    猜你喜欢
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多