2010-07-30 106 views
0

好吧,这是一个非常奇怪的问题。我想先说我不是C++的初学者,我当然不会进步。我在中间的某个地方。我想要做的是制作Win32 API的C++ OOP包装库(dll)。这是我的图书馆的类。调用虚拟函数时崩溃

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.h

我使用下面的命令编译的MinGW它
#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()来弄清楚()(lol,这是我没有学习如何使用gdb进行调试的结果)。我试图让我可以创建一个类,比如LoginForm,并从窗体派生并覆盖窗口过程。我希望我能够很好地解释这个问题:D。这可能是一个编译器错误或我的愚蠢:P。无论如何,在此先感谢。

+0

是从'GetWindowLong()'并且转换为'Form *'(在全局的'WndProc()')中实际上是一个指向Form的有效指针?如果不是,那么虚拟呼叫会爆炸。 – 2010-07-30 18:26:30

+0

看看你在这里做什么,我会说看看这本书:http://www.amazon.com/MFC-Internals-Microsoft-Foundation-Architecture/dp/0201407213/(MFC内部,由乔治牧羊人)它恕我直言很好*解释通过什么箍MS微软跳转到获得他们的MFC C++ OO API运行在Win32窗口系统之上。 – 2010-07-30 18:30:43

+0

感谢您的参考martin。我正在寻找一本关于在C++ OOP中封装Win32的好书。 – 2010-07-30 18:40:24

回答

7

的问题是在这里:返回此方法时

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指针制定出虚函数的地址,调用越来越reallyed搞砸了调度。

虚拟方法地址是在运行时计算的,而在编译时植入普通方法地址时,虚拟方法地址是虚拟的而不是虚拟方法的原因。

当计算虚拟方法的地址时,this指针以某种方式(在这种情况下导致UB)被解除引用,但由于该对象已被销毁,所以该地址处的数据已被重新使用,因此地址你得到的功能是一些随机垃圾,并称这将永远不会好。

一个简单的解决方案是使窗体成为应用程序对象的一部分。
因此它的寿命是一样的应用程序:

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

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

     return true; 
    } 
}; 
+0

是的,我认为你知道了。在'返回true'之前,我会尝试将该函数内的消息循环放入内部。 – 2010-07-30 18:36:26

+0

是的,这是问题。感谢您的解决方案。当我在OnInit函数中放置消息循环时,也起到了作用。但你的是更简单和优雅。我想知道我是否内联了OnInit函数,只是为了让自己在函数内部创建Form对象? – 2010-07-30 18:43:48

+0

井内函数不起作用。你有什么想法可以在OnInit()函数内创建Form对象吗? – 2010-07-30 19:57:35

2
wc.lpfnWndProc = WndProc; 

不能在一般的情况下工作,尽管其中的WndProc位于它并不明显。 Windows不会提供实例方法在进行回调时需要的“this”指针。您现在正在远离它,因为您在Form :: WndProc()方法中不访问Form类的任何成员。它在没有虚拟关键字的情况下偶然运行,但一旦开始触摸成员,运气就会很快耗尽。这将以AccessViolation异常进行轰炸。

您需要使Form :: WndProc()方法成为静态方法。

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

+0

“WndProc”的使用将引用全局函数,而不是“Form”的成员。 – 2010-07-30 18:27:44

+0

WndProc是在Application.cpp中本地声明的,而Form中的WndProc不能是静态的,因为所有不同的表单都需要处理它们自己的事件。 – 2010-07-30 18:45:01

+0

是的,理解。我告诉过你需要做些什么才能使其变为虚拟,您必须将窗口句柄映射到包装它的C++类对象。一个std :: map可以做到这一点。查看您最喜爱的C++编程手册,了解成员函数指针如何工作。 – 2010-07-30 18:52:57

0

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

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

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