ZFX
ZFX Neu
Home
Community
Neueste Posts
Chat
FAQ
IOTW
Tutorials
Bücher
zfxCON
ZFXCE
Mathlib
ASSIMP
NES
Wir über uns
Impressum
Regeln
Suchen
Mitgliederliste
Membername:
Passwort:
Besucher:
4420808
Jetzt (Chat):
31 (0)
Mitglieder:
5239
Themen:
24223
Nachrichten:
234554
Neuestes Mitglied:
-insane-

FAQ - Frequently Asked Questions - Allgemeine Programmierung


Wie bringe ich die WNDPROC in einer Klasse unter?

I 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:

Zitat von Compiler:

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 bzw. abhängig von der calling convention in einem Register übergeben.

II Beispiel:

Code:
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. Das lässt sich zwar mit einem Assemblerhack umgehen, dieser führt aber einfach zum Absturz da die WNDPROC-Methode dann mit der falschen Parameterzahl aufgerufen wird.

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

1) 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.

2) 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.

3) Dito, aber mithilfe der SetProp() und GetProp() Funktionen des WinAPIs. Nachteil: Langsam, da mühsames Stringhashing und Tabellenlookup erforderlich.

4) Rumspielen an den Funktionsparametern mittels Assemblereinschüben. Compilerabhängig, plattformabhängig, komplex und schlussendlich sinnlos


IV 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 dazu um dafür zu sorgen dass es keine Probleme gibt falls zufälligerweise zwei Threads nahezu zur selben Zeit versuchen Fenster zu erstellen. Da jeder Thread seine eigene Kopie der globalen Variable, in der der Pointer auf das Fensterobjekt übergeben wird, besitzt, sind Überschneidungen ausgeschlossen.


V Implementierung von 2b):

a) Klassendeklaration:
Code:
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:
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 (TLS_OUT_OF_INDEXES == s_tlsIndex)
      {
            // 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);

    // c) Create your window
    m_hWnd = ::CreateWindowEx(/* ... */);
    assert(!::TlsGetValue(s_tlsIndex)); 

   if(0 == m_hWnd)
   {
      // ... 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(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
    if (!pcThis)
    {
      // extract the window object from the TLS index
      pcThis = static_cast(::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(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



VI 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 Aramis und dLoB