2010-07-30 128 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」得到回溯,你應該看看崩潰是發生在虛擬方法內部還是調用它。