Problem
Versucht man die WNDPROC als Memberfunktion einer Klasse zu deklarieren und beim Registrieren der Fensterklasse zu verwenden, so erweist sich der Compiler gerne als Spaßbremse. Typische Fehlermeldung:
Die Ursache hängt damit zusammen wie der Compiler Memberfunktionen (= Methoden) intern behandelt. Diese erhalten stets noch den impliziten Funktionsparameter this mit auf den Stack geschoben.Unable to convert from LRESULT (CALLBACK MyWindowClass::*) (HWND, MSG, WPARAM, LPARAM) to LRESULT (CALLBACK*) (HWND, MSG, WPARAM, LPARAM)
Beispiel
Code: Alles auswählen
MyFooClass* pcObject = new MyFooClass();
// Methodenaufruf
int iFooVar = 150392;
pcObject->Foo(iFooVar);
// Der Compiler macht daraus sinngemäß (nicht kompilationsfähig!):
MyFooClass::Foo(pcObject,iFooVar);
Lösungsansätze
Es existieren mehrere Lösungsansätze. Den meisten gemein ist, dass die WNDPROC zunächst als statische Funktion definiert wird, aber die eintreffenden Nachrichten dann auf eine WNDPROC-ähnliche Memberfunktion des dem HWND zugehörigen Fensterobjektes umleitet.
- Speichern aller Instanzen der Fensterklasse in einer Map (z.B. std::map). Diese verknüpft dann jedes Fensterhandle mit einem Objekt. Nachteil: Nicht Threadsicher, langsam.
- Speichern des Pointers auf das Fensterobjekt im HWND selber mittels SetWindowLongPtr() und GetWindowLongPtr(). Nachteil: Eventuell Überschneidungen. Möglichkeit a): Übermittlung des Pointers auf das Fensterobjekt in der WM_NCCREATE-Nachricht Möglichkeit b): Ablegen des Fensterobjektes in einem TLS-Index (Thread Local Storage). Bei der ersten Nachricht für die GetWindowLongPtr() 0 zurückgibt wird der gespeicherte Pointer aus dem TLS geholt und im Fensterhandle abgelegt.
- Dito, aber mithilfe der SetProp() und GetProp() Funktionen des WinAPIs. Nachteil: Langsam, da mühsames Stringhashing und Tabellenlookup erforderlich.
- Rumspielen an den Funktionsparametern mittels Assemblereinschüben. Compilerabhängig, plattformabhängig, komplex und eigentlich sinnlos :-)
Thread-local storage (kurz TLS) ist ein Mechanismus der es einem Thread erlaubt private Daten anzulegen, auf die andere Threads keinen Zugriff haben. Um TLS nutzen zu können muss zuerst ein TLS-Slot via TlsAlloc() angelegt werden. Danach kann ein Thread mittels der Funktion TlsSetValue() auf den Slot zugreifen. In diesem Fall nutzen wir TLS um dafür zu sorgen dass es keine Probleme gibt falls zufälligerweise zwei Threads nahezu zur selben Zeit versuchen Fenster zu erstellen (Unwahrscheinlich, aber gewiss nicht undenkbar). Da jeder Thread seine eigene Kopie der globalen Variable, in der der Pointer auf das Fensterobjekt übergeben wird, besitzt sind Überschneidungen ausgeschlossen.
Beispielimplementierung
Das folgende Beispiel implementiert die oben an zweiter Stelle aufgeführte Variante.
a) Klassendeklaration:
Code: Alles auswählen
namespace myProject
{
// ---------------------------------------------------------------------------------
/** \brief Window _class_.
*
* Wraps the native Windows API and allows the creation of multiple windows
*/
// ---------------------------------------------------------------------------------
class Window
{
public:
Window( /* your window parameters here */ );
// Provide a copy c'tor and assignment operator to prevent the
// compiler from duplicating the handle
Window(const Window& window);
Window& operator= (const Window& window);
// d'tor to destroy the window handle
~Window();
// ---------------------------------------------------------------------------
/** \brief Create the window
*
*/
bool Create();
protected:
// ---------------------------------------------------------------------------
/** \brief Window message handler _private_ to the object.
*
* Can be overriden by deriving classes.
*/
virtual LRESULT CALLBACK ObjectCallback(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam);
// ---------------------------------------------------------------------------
/** \brief Retrieves the native handle of the window.
*/
HWND GetHandle() const {return this->m_hWnd;}
private:
// ---------------------------------------------------------------------------
/** \brief Static function to serve as WNDPROC _for_ all created Windows.
* The function identifies the CWindow object corresponding to a HWND
* and dispatches the event to the ObjectCallback()-method of the object.
*/
static LRESULT CALLBACK CallbackProxy(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam);
private:
/** \brief Native HWND handle of the window
*/
HWND m_hWnd;
/** \brief Number of window instances
*/
static unsigned int s_iInstanceCount;
/** \brief TLS index that is currently used
*/
static DWORD s_tlsIndex;
}; // ! Window
}; // ! myProject
Code: Alles auswählen
namespace myProject
{
unsigned int Window::s_iInstanceCount = 0;
DWORD Window::s_tlsIndex = 0;
// ---------------------------------------------------------------------------------
Window::Window()
{
if (0 == s_iInstanceCount)
{
assert(!s_tlsIndex);
s_tlsIndex = ::TlsAlloc();
if (s_tlsIndex == TLS_OUT_OF_INDEXES)
{
// Error handling
}
}
++s_iInstanceCount;
}
// ---------------------------------------------------------------------------------
Window::~Window()
{
--s_iInstanceCount;
if (0 == s_iInstanceCount)
{
::TlsFree(s_tlsIndex);
s_tlsIndex = 0;
}
}
// ---------------------------------------------------------------------------------
bool Window::Create(/* your window parameters here */ )
{
// ....
// a) Register a window _class_, be sure to create an unique name _for_ it
WNDCLASSEX sWndClass;
memset(&sWndClass,0,sizeof(WNDCLASSEX));
sWndClass.lpfnWndProc = &Window::CallbackProxy;
// b) Store the _this_ pointer in the TLS index
::TlsSetValue(s_tlsIndex, this);
assert(!::TlsGetValue(s_tlsIndex)); // For debugging
// c) Create your window, passing the _class_ instance as creation
// parameter to the WNDPROC
if(0 == (m_hWnd = ::CreateWindowEx(/* your window parameters */ this)))
{
// ... error handling
}
return true;
}
// ---------------------------------------------------------------------------------
/*_static_*/ LRESULT CALLBACK Window::CallbackProxy(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
// extract the object instance from the HWND
Window* pcThis = reinterpret_cast<Window*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (!pcThis)
{
// extract the window object from the TLS index
pcThis = static_cast<Window*>(::TlsGetValue(s_tlsIndex));
assert(pcThis);
::TlsSetValue(s_tlsIndex, 0); // For debugging
// and store it in the HWND itself ...
if(!::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pcThis)))
{
// error handling ...
}
// store the window handle in the _class_ instance itself
pcThis->m_hWnd = hWnd;
}
// and dispatch the message to the object's _private_ handler
const LRESULT ret = pcThis->ObjectCallback(hWnd, msg, wParam, lParam);
if (msg == WM_NCDESTROY)
{
pcThis->m_hWnd = 0;
}
return ret;
}
// ---------------------------------------------------------------------------------
LRESULT CALLBACK Window::ObjectCallback(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
// WNDPROC code _for_ the window
}
}; // ! myProject
Hinweise
- SetWindowLongPtr()/GetWindowLongPtr() müssen verwendet werden um die Kompatibilität mit 64-Bittigen Windowsversionen sicherzustellen.
- Der Einfluss der Messageredirection auf die Performance ist allenfalls gering.
- Im Code wird der Gültigkeitsauflösungsoperator '::' verwendet um eventuellen Namenskonflikten sicher aus dem Weg zu gehen
- Der namespace MyProject dient hier nur als Platzhalter
- Ein weiterer Lösungsansatz ist es zur Laufzeit dynamisch Code zu generieren und die Callback-Funktionsaufrufe umzuleiten. Diese Methode wird hier näher beschrieben.
Von Biolunar und *mir*