Tipp: Wie bringe ich die WNDPROC in einer Klasse unter?

Hier können Artikel, Tutorials, Bücherrezensionen, Dokumente aller Art, Texturen, Sprites, Sounds, Musik und Modelle zur Verfügung gestellt bzw. verlinkt werden.
Forumsregeln
Möglichst sinnvolle Präfixe oder die Themensymbole nutzen.
Gesperrt
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Tipp: Wie bringe ich die WNDPROC in einer Klasse unter?

Beitrag von Aramis »

Wie bringe ich die WNDPROC in einer Klasse unter?

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:
Unable to convert from LRESULT (CALLBACK MyWindowClass::*) (HWND, MSG, WPARAM, LPARAM) to LRESULT (CALLBACK*) (HWND, MSG, WPARAM, LPARAM)
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.

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);
Aufgrund dieses Verhaltens ist der einzige Cast der in C++ selbst mit einem reinterpret_cast fehlschlägt der Cast eines Pointers auf eine Methode nach void* (bzw. einem nach void* castbaren Pointer) - wie zum Beispiel ein ganz gewöhnlicher Funktionspointer.

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 :-)
Was ist Thread-local storage ?

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
b) Implementierung:

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.
Fragen/Anregungen/Kritik/Anmerkungen bitte direkt in den Diskussionsthread zu diesem FAQ-Eintrag posten.

Von Biolunar und *mir*
Gesperrt