This-Zeiger ist NULL
-
@frankthefox sagte in This-Zeiger ist NULL:
Beiwindow_.reset(new GameWindow(args));
wird
window_
erst geändert nachdem der Konstruktor vonGameWindow
fertig gelaufen ist. Bis dahin hatwindow_
noch den alten Wert, in deinem Fall haltNULL
. Da der Aufruf vonGame::HandleCreate
noch während der Ausführung des Konstruktors vonGameWindow
kommt...
-
Danke für den Hinweis, aber im Game-Konstruktor setzte ich doch static_this, im Destruktor dann wieder auf null.
-
Ich denke, das ist es. Ich erzeuge die Fenster-Ressource im Konstruktor. Die Nachrichten WM_NCCREATE, WM_CREATE werden and die GameWindowProc gesendet. Der Konstruktor von GameWindow ist allerdings nicht komplett durchgelaufen, daher der Fehler. Ich teste das mal mit einem Default-Konstruktor und einer CreateWindow - Funktion, also so ein two-stage-construction Ansatz. Sollte das Problem aus der Welt schaffen. Allerdings finde ich das der Konstruktor die Instanz auch initialisieren sollte.
-
@frankthefox Naja das ganze ist sowieso nicht sauber gekapselt. Game ist ein Singleton und agiert als Dispatcher für die Window-Messages. Und verlässt sich darauf dass eh immer nur ein Fenster existieren kann. Von daher würde two-phase Construction das ganze nicht mehr viel schlimmer machen.
Saubere Kapselung inklusive "one phase" Construction ist bei WinAPI aber generell recht schwer hinzubekommen. Wenn man darauf verzichtet die ersten paar Messages an das jeweilige Objekt zu dispatchen (
WM_NCCREATE
& Co.), dann geht es indem man den Zeiger auf das C++ Objekt als "window long ptr" setzt (SetWindowLongPtr
). Und in der Window-Proc dann entsprechend wieder überGetWindowLongPtr
rausholt. Ein weiterer Nachteil dieser Technik ist aber natürlich dass das mit anderem Code kollidieren kann, wenn dieser ebenso (SetWindowLongPtr
) verwendet.Die "beste" Variante die ich bis jetzt kenne ist jedem Fenster eine eigene Window-Proc zu geben die dynamisch generiert wird. Dabei generiert man einen kleinen Thunk in dem der Zeiger auf das Objekt mit abgespeichert ist. Der Thunk ruft dann, mit diesem Zeiger als zusätzlichen Parameter, die "echte" Window-Proc auf. Der Nachteil dieser Variante ist wieder dass sie nicht portierbar ist: du brauchst pro Compiler und/oder CPU-Architektur die du unterstützen willst eigenen Code. Wie das aussehen kann, kannst du dir z.B. in den ATL Sourcen ansehen.
-
@hustbaer
Danke für die ausführliche Antwort. Ich habe bislang mich mit der SetWindowLongPtr-Variante nicht wirklich anfreunden können. Hatte Beiträge bei z.B. Stackoverflow gesehen, die Probleme sehen, weil der Bereich überschrieben werden kann und dann null-Zeiger dereferenziert werden. Die Thunk-Methode ist leider eine Methode die Compiler/CPU abhängig ist. Der Beispiel-Code ist nicht gekapselt, weil mir mal Gedanken gemacht habe wie ich das angehen konnte. Ich werde mal schauen das ganze sauber zu implementieren.
-
Du kannst auch zusätzlichen Speicher für die Fensterklasse anfordern (WNDCLASS::cbWndExtra) und die Instanz (oder eine WNDPROC) dort speichern, falls du
GWLP_USERDATA
(das meint Hustbär) anders verwenden möchtest.
Das ist zumindest sehr viel besser, als gleich mit einer solch krassen Einschränkung ein Projekt zu beginnen.
-
Ich weiß das ein statischer Zeiger sowie ist eine two-phase-construction nicht das gelbe vom Ei sind. Ich könnte auch 'ne std::map nehmen und dort eben Zeiger und HWND's speichern, Da ich aber nur ein einziges Fenster brauche, habe ich diese Möglichkeit ausgeschlossen. Maps (nicht std) werden ja z. B. von der MFC benutzt. CWnd's werden dort abgelegt. CWnd::FromHandle(hWnd) usw.
-
Variante 1:
So würde ich es machen wenn mehrere Fenster existieren:
class GameWindow { static GameWindow* wnd_being_created; static std::unordered_map<HWND, GameWindow*> hwnd_map; HWND wnd_; static LRESULT CALLBACK Window::GameWindowProc( HWND wnd, UINT msg, WAPRAM wparam, LPARAM lparam); public: void Create (const GameArgs& args) { wnd_being_created = this; ::CreateWindow (args); } }; static LRESULT CALLBACK GameWindow::GameWindowProc( HWND wnd, UINT msg, WAPRAM wparam, LPARAM lparam) { GameWindow* this_window {}; if(wnd_being_created) { hwnd_map[HWND] = wnd_being_created; wnd_being_created->wnd_ = wnd; this_window = wnd_being_created; wnd_being_created = nullptr; return DefWindowProcW(hwnd, msg, wparam, lparam); } auto window = hwnd_map.find(wnd); this_window = window; return this_window->WindowProc(msg, wparam, lparam); }
Variante 2:
Das ist der Tipp von euch mit GWLP_USERDATA mal von mir aus der Hüfte geschossen und kurz skizziert:
wc.cbWndExtra = sizeof(GameWindow *); HWND wnd = CreateWindowExW(...); SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); LRESULT CALLBACK GameWindow::GameWindowProc( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { GameWindow *window = reinterpret_cast<GameWindow *>(GetWindowLongPtr(wnd,GWLP_USERDATA)); return window->wndProc(msg, wparam, lparam); }
So ist das doch gemeint...also jetzt diese Variante...
-
@FrankTheFox
Ja genau so war das gemeint. (Wobei ich nicht weiss ob man fürGWLP_USERDATA
überhauptwc.cbWndExtra > 0
braucht - aber schaden tut's sicher nicht.)Was bei den Maps wieder doof ist ist dass die Sache nicht thread-safe ist. Für ne Library bzw. allgemein wiederverwendbaren Code halt doof. Für ein Spiel das nur einen UI-Thread hat natürlich egal. Oder auch nicht egal. Doof ist nämlich wenn es so aussieht als ob es wiederverwendbar wäre, und dann solche Fallstricke darin begraben sind. Also vielleicht zumindest überall wo auf diese statischen Variablen zugegriffen wird ein
assert(::GetCurrentThreadId() == ui_thread_id);
reinmachen. Damit kann man sich erstmal unnötige Arbeit bzw. Overhead das ganze thread-safe zu machen sparen, aber die Gefahr dass jemand (vielleicht sogar man selbst) irgendwann darüber stolpert ist minimiert.
-
Hab mal ein fast vollständig funktionierendes Beispiel...Hier setzte ich allerdings in der WindowProc den Zeiger...darum der Umstand mit dem static pointer...wenn ich also den Zeiger schon haben will wenn die WM_CREATE Nachricht aufschlägt.
#include <windows.h> #include <string> #include <iostream> #include <stdexcept> class GameWindow { public: GameWindow(HINSTANCE instance); bool CreateGameWindow(HINSTANCE instance); std::wstring GetWindowClassName(HINSTANCE instance); private: static GameWindow* wnd_being_created; static LRESULT CALLBACK GameWindowProc( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam); LRESULT WindowProc( UINT msg, WPARAM wparam, LPARAM lparam); HWND wnd_; }; GameWindow* GameWindow::wnd_being_created; GameWindow::GameWindow(HINSTANCE instance): wnd_ {} { static const auto kWindowClassName = GetWindowClassName(instance); if(kWindowClassName.empty()){ throw std::runtime_error("Failed to register window class."); } wnd_being_created = this; HWND wnd = CreateWindowExW( 0, kWindowClassName.c_str(), L"", WS_POPUP, 0, 0, 32, 32, nullptr, nullptr, instance, nullptr); if(wnd != wnd_){ throw std::runtime_error("Failed to create window."); } } std::wstring GameWindow::GetWindowClassName(HINSTANCE instance) { WNDCLASSEXW wnd_class {}; wnd_class.cbSize = sizeof(WNDCLASSEXW); wnd_class.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; wnd_class.hInstance = instance; wnd_class.cbWndExtra = sizeof(GameWindow *); wnd_class.lpfnWndProc = GameWindowProc; wnd_class.lpszClassName = L"GameWindowClass"; wnd_class.hbrBackground = static_cast<HBRUSH>( GetStockObject(BLACK_BRUSH)); wnd_class.hIcon = reinterpret_cast<HICON>( LoadImage( nullptr, IDI_APPLICATION, IMAGE_ICON, 0,0, LR_DEFAULTSIZE | LR_SHARED)); wnd_class.hCursor = reinterpret_cast<HCURSOR>( LoadImage( nullptr, IDC_ARROW, IMAGE_CURSOR, 0,0, LR_DEFAULTSIZE | LR_SHARED)); if(!RegisterClassExW(&wnd_class)){ return L""; } return wnd_class.lpszClassName; } LRESULT CALLBACK GameWindow::GameWindowProc( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { GameWindow *window = reinterpret_cast<GameWindow *>( GetWindowLongPtr(wnd, GWLP_USERDATA)); if(!window) { SetWindowLongPtr( wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(wnd_being_created)); wnd_being_created->wnd_ = wnd; wnd_being_created = nullptr; return DefWindowProcW(wnd, msg, wparam, lparam); } return window->WindowProc(msg, wparam, lparam); } LRESULT GameWindow::WindowProc( UINT msg, WPARAM wparam, LPARAM lparam) { std::cout << "message: " << msg << " wparam: " << wparam << " lparam: " << lparam << std::endl; return DefWindowProcW(wnd_, msg, wparam, lparam); } int WINAPI WinMain( HINSTANCE instance, HINSTANCE, LPSTR, int) { GameWindow window {instance}; return EXIT_SUCCESS; }
Mit diesem Beispiel kann ich mich auch anfreunden
Ausgabe:
message: 131 .... WM_NCCALCSIZE
message: 1 .... WM_CREATE
....Process returned 0 (0x0) execution time : 0.017 s
Press any key to continue.
-
Wenn du
wnd_being_created
jetzt noch thread local machst, dann wäre das schonmal nicht schlecht.
-
Zuerst habe ich mich gefragt ob es einen Unterschied macht, ob eine statische Variable thread_local ist oder nicht. Aber der C++ Standard sagt dazu (n4296.pdf)
3.7.2 Thread storage duration [basic.stc.thread]
1 All variables declared with the thread_local keyword have thread storage duration. The storage for these entities shall last for the duration of the thread in which they are created. There is a distinct object or reference per thread, and use of the declared name refers to the entity associated with the current thread.
2 A variable with thread storage duration shall be initialized before its first odr-use (3.2) and, if constructed, shall be destroyed on thread exit.Die akzeptierte Antwort hier Are C++11 thread_local variables automatically static? sagen das thread_local auch bei statischen Variablen durchaus Sinn macht.
Vielen Dank an alle für die hilfreichen Tipps und Antworten