Die Unsitte immer GetModuleHandle(NULL) für hInstance in CreateWindow und RegisterClass zu verwenden

Carsten hat mich dazu inspiriert noch ein wenig mehr Don Quichotte zu spielen und gegen Windmühlen zu kämpfen: Damit hier mein zweiter Beitrag zum Thema Unsitten.

Der hInstance Parameter in CreateWindow(Ex) und RegisterClass (WNDCLASS) wird oft genug nicht verstanden. Man braucht für diese Funktionen einen HINSTANCE Wert. Den hat aber niemand in der Tasche, wenn man mit der Win32 API pur mal eben so ein Programm schreibt. Die wenigsten kommen auf die Idee den hInstance Wert aus WinMain und DllMain irgendwo global zu speichern und zu verwenden. Globals sind ja irgendwie „böse“, und nicht OOP-like… 😉

Was also tun? Irgendein Unwissender empfiehlt einfach GetModuleHandle(NULL) zu verwenden und seit dem dieser Unwissende diesen Tipp in die Welt gesetzt hat kursiert er durch die Foren… unaufhaltsam…

❗ Das Problem: Es geht… manchmal… aber portabel ist der Code damit nicht und man erlebt eigentümliche Sachen damit an anderer Stelle unter annderen (DLL-)Umständen. Aber warum?

Das Geheimnis steckt darin, dass Fensterklassen nicht einfach so unter ihrem Namen abgelegt werden, sondern unter einem Namen und der hInstance, die bei RegisterClass mit angegeben wird. So hat jeder Prozess seine Liste der Fensterklassen die aus hInstance und dem Namen der Klasse bestehen. Auch die Standard Fensterklassen wie Edit, Button, Static etc. werden mit der hInstance der User32.dll gespeichert.

Wird nun ein Fenster erzeugt mit CreateWindow(Ex), dann benutzt der Windowsmanager die Kombination aus hInstance Wert, der bei CreateWindow(Ex) angegeben wird und dem Namen der Klasse um den entsprechenden Klassen Eintrag zu finden.

Es ist also vollkommen OK wenn DLLs selber Klassen registrieren mit ein und unterschiedliche Module den selben Namen verwenden. Da gibt es keinen Konflikt, denn durch die hInstance Werte bleiben die Klassen eineindeutig. Und auch jede DLL kann damit, dass von Ihr gewünschte Fenster mit der entsprechenden Klasse erzeugen, denn jede DLL hat eine unterschiedliche hInstance.

Einzige Ausnahme ist eine Klasse, die mit dem Klassenstil CS_GLOBALCLASS registriert wird. Bei solch einer Klasse wird der Windowsmanager nur auf den Namen und nicht auf den Wert der hInstance achten. Jedem wird klar sein, dass die Standardfensterklassen der USER32.DLL und auch die Klassen der COMCTL32.DLL mit diesem Stil registriert wurden.
Und klar ist auch, dass man seine eigenen Klassen, die in DLLs liegen mit diesem Flag CS_GLOBALCLASS registrieren muss, wenn man diese Klassen applikationsweit verwenden will. Würde man also ein Dialogtemplate mit einer eigenen Klasse anlegen, die in einer DLL liegt, so kann der Dialogmanager, der wieder hInstance der Applikation verwendet, die entsprechende Klasse nicht finden, wenn diese nicht mit CS_GLOBALCLASS registriert wurde.

Es ist und bleibt eine Unsitte GetModuleHandle(NULL) beim Registrieren der Windowsklasse zu verwenden, denn dieses hInstance, das man erhält ist natürlich das hInstance der Applikation, und nicht das des Modules, welches die Klasse enthält, z.B. eben eine DLL. Es wundert nicht wenn man etwas später seine Klassen in eine DLL auslagert erstaunt feststellt, dass auf einmal die Sachen nicht mehr funktionieren wie sie sollen.

Um solchen Problemen aus dem Weg zu gehen sollte man immer das hInstance verwenden, das zu dem entsprechenden Modul gehört. Diesen Wert erhält man durch WinMain oder DllMain. Am Besten man speichert diesen in einer globalen Variablen. Die MFC hat hierzu eine spezielle Funktion AfxGetInstanceHandle, die dies für das entsprechende Modul erledigt. Aber ein MFC Programmierer würde ja auch AfxRegisterClass verwenden und nicht in diese Probleme laufen, wiederum vorausgesetzt er verwendet auch brav AFX_MANAGE_STATE 🙂

❗ Anmerkung: Es gibt auch Code, der einem erlaubt das hInstance Handle zu einem Code Abschnitt zu bestimmen.
http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx (sofern man einen MS-Linker verwendet)

Auch der folgende etwas trickreichere Code ist einen Blick wert:

HINSTANCE Get_hInstance()
{
 MEMORY_BASIC_INFORMATION mbi; 
 VirtualQuery(Get_hInstance, &mbi, sizeof(mbi)); 
 return reinterpret_cast<HINSTANCE>(mbi.AllocationBase); 
}

Aber bitte diesen Code nicht in eine DLL auslagern :mrgreen:

14 Gedanken zu „Die Unsitte immer GetModuleHandle(NULL) für hInstance in CreateWindow und RegisterClass zu verwenden“

  1. Nun, der GetModuleHandle(NULL) liefert doch sowieso immer
    nur 0x00400000 . Da kann ich mir nicht vorstellen, dass der
    prozessuebergreifend zu irgendeiner Unterscheidung taugt.
    Im MSDN ist mit dem Umstieg von 16Bit auf 32 Bit beschrieben,
    Dass von WinMain() der hPrevInstance immer NULL und damit
    bedeutungslos ist. Das scheint, wenn auch dokumentarisch im MSDN
    vergessen, auch mit hInstance so zu sein.
    Es ist auch moeglich, aus einer Konsolapplikation heraus RegisterClass()
    und CreateWindow() aufzurufen. Dabei hat man dann wegen dem
    main() statt WinMain() als Eintrittsfunktion kein hInstance mehr.
    Wie belegt man dabei hInstance im struct WNDCLASS ?
    Ich persoenlich habe es einfach NULL belassen (bzw. mit
    „static WNDCLASS“ das hInstance einfach links liegen lassen.
    Das funtioniert wunderbar, wobei beim Start meiner Anwendung
    stets ein neue Instanz davon gestartet wird (und werden soll).
    Es mag also Probleme geben, wenn der Startversuch einer Anwendung in
    den Wechsel auf eine moeglicherweise davon bestehende Instanz
    muenden soll.

    1. Du hast in keiner Weise verstanden wotum es hier geht.
      1. Ist GetModuleHandle nicht immer 0x00400000. Das hängt einzig und alleine von den Linkereinstalleungen ab, und wenn DEP und ASLR verwendet werden dann ist der Wert zufällig.
      2. Geht es eben nicht im die Modul-Adresse des Executables sondern evtl. um die Moduladresse der DLL!
      3. Wir man in jedem Fall HINSTANCE korrekt bestimmt ist auch in dem Artikel erwähnt, auch wenn man nichts übergeben bekommt:
      http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx

      Ich bleibe dabei! Immer GetModuleHandle(NULL) zu verwenden führt spätestens bei Verwednung des selben Codes in einer DLL zu Problemen. Punkt.

  2. Hoppla, bitte entschuldige erstmal, dass ich nichts verstehe.
    zu 1:
    Da hast Du Recht, er ist nicht immer 0x00400000 . Ich wuerde den freilich auch niemals so fest einkodieren. Dann lieber
    GetModuleHandle(NULL) oder welche Funktion auch immer wir eroertern
    moegen. Ich wollte auch nicht hervorheben, dass GetModuleHandle(NULL)
    meist 0x00400000 ist , oder dass hInstance von WinMain() auch meist
    0x00400000 ist, sondern das daraus folgend GetModuleHandle(NULL)
    gleich hInstance von WinMain() ist (oft? meist? immer?)
    zu 2:
    Auch hier hast Du Recht. Mit GetModuleHandle(„irgendwas.dll“) bekommen
    wir freilich sinnvolle brauchbare und vor allem unterscheidbare Handles
    zurueck. Ich wollte bzw. will mich aber nur zum GetModuleHandle(NULL)
    aeussern, im Zusammenhang mit der Frage, wie „hInstance“ in RegisterClass() bzw. CreateWindow() als Ersatz bei fehlendem
    WinMain() (z.B. Konsolenapplikation) am besten zu belegen ist.
    zu 3:
    Vielen Dank fuer den Artikel (und Deine damit verbundene Muehen)
    Aber ich habe ja nicht den GetModuleHandle(NULL) benutzt, und schon
    gar nicht die 0x00400000 fest einkodiert. Ich habe den hInstance in
    WNDCLASS fuer RegisterClass() wie auch den vorletzten Parameter
    von CreateWindow() einfach 0 gelassen (bei CreateWindow() NULL
    uebergeben). Das klappt wunderbar und ich konnte ueber Jahre keine
    Fehlfunktionen irgendwelcher Art deswegen ausmachen.
    Dein Schlusssatz:
    Hier muss ich Dir einfach Recht geben. Denn ich habe bisher keinen
    RegisterClass() / CreateWindow() in einer eigenen DLL untergebracht,
    weshalb ich auch noch nie den GetModuleHandle(NULL) in einer DLL
    untergebracht habe. Aber da stellt sich die Frage, woher der hInstance
    stammt, bei einer DLL? Bei DllMain() bekommt man aehnlich WinMain()
    ein HINSTANCE hinstDLL uebergeben. Aber wie gesagt, das will ich
    nun jetzt nicht behaupten und habe es auch noch nicht ausprobiert.

  3. Ich habe noch etwas herausgefunden, bzw. die gute Idee mit der
    Verwendung in einer DLL auch mal verfolgt.

    Wie erwaehnt, hatte ich ja den „hInstance“ von RegisterClass() einfach
    auf NULL und den „hInstance“ des vorletzten Parameters von
    CreateWindow() auch einfach auf NULL gesetzt.
    Dabei liefert wie erwartet GetWindowLong(hwnd,GWL_HINSTANCE) diese
    NULL. Aber GetClassLong(hwnd,GCL_HMODULE) liefert 0x00400000 ,
    obwohl nie so von mir belegt. Irgendwie scheint Windows das zu richten.

    Jetzt habe ich das ganze in eine DLL gepackt. Den hinstDLL von DllMain
    bekomme ich dabei mit 0x10000000 belegt. Und
    GetWindowLong(hwnd,GWL_HINSTANCE) liefert immer noch NULL,
    wohingegen GetClassLong(hwnd,GCL_HMODULE) immer noch 0x00400000
    liefert. (Und das Programm funktioniert)

  4. Danach habe ich auch einen GetModuleHandle(NULL) mit in die DLL
    getan. Er liefert auch aus einer er DLL aufgerufen den Wert 0x00400000
    zurueck.
    Aber jetzt wuensche ich erstmal allen Gute Nacht.

  5. Hallo Martin, hallo Leute,
    ich stoße öfters auf diese Blog-Seite und wollte fairerweise auch einmal einen Beitrag leisten.
    GetModuleHandle() liefert – so wie die (meisten) Windows Handles auch – nichts Anderes als die Basis-Speicher-Adresse. In diesem Fall ist es die gewünschte(!) Basis-Adresse des Programms bzw. der DLL. Standardmäßig ist bei Compilern für ausführare Programme 0x00400000 eingestellt und wird so im EXE-Header übenommen.
    Wenn jetzt z.B. eine DLL geladen wird, die unüblicherweise ebenfalls 0x00400000 als gewünschte Basis-Adresse angegeben hat, so wird diese beim Laden in einen anderen Speicherbereich gelegt und hat dann z.B die Basisadresse 0x00FA0000 UND eben das ModulHandle 0x00FA0000 anstatt 0x00400000. Kann es sein, dass oben erwähnte Konflikte damit zu tun haben?

  6. Mit „Konflikten“ meinte ich (Zitat):
    „Es geht… manchmal… aber portabel ist der Code damit nicht und man erlebt eigentümliche Sachen damit an anderer Stelle unter annderen (DLL-)Umständen“
    Ich wollte eigenlich nur eine Anregung geben.
    Aber ein Beispiel:

    Kopiere z.B. die kernel32.dll nach c:\test\kernel32_1.dll
    Dann schreibst du in irgend einer Sprache ein kleines Programm, mit dem du diese DLLs lädst.

    hModuleA = LoadLibrary(c:\windows\system32\kernel32.dll)
    hModuleB = LoadLibrary(c:\test\kernel32_1.dll)
    ModuleHandleA = GetModuleHandle(‚kernel32.dll‘)
    ModuleHandleB = GetModuleHandle(‚kernel32_1.dll‘)

    Damit hat man zwei DLLs mit der gleichen „gewünschten“ Basisadresse geladen.

    Man sieht: hModuleA = ModuleHandleA und hModuleB = ModuleHandleB

    Ein Blick in den Speicherauszug eines CPU-Fensters zeigt, dass jeweils am Anfang dieses „MZ“ steht, also die ersten beiden Bytes Der DLL-Datei.
    Und das meinte ich mit Basis-Adresse.

    GetModuleHandle(NULL) liefert natürlich immer die gewünschte Basisadresse des gestarteten Programms, weil ja beim Start ja erst ein virtueller Speicher zur Verfügung gestellt wird und kein Grund besteht, das Programm in einen anderen Speicherbereich zu verschieben.

    Aber wie gesagt war mein Beitrag nur eine Anregung/ ein Diskussionsansatz.

    1. Das ist nicht ganz richtig.
      1. Können bereits permanent geladene DLLs erzwingen, dass die XE woanders hin geladne wird.
      2. ASLR sorgt für „zufällige“ Startadressen.

      All das änder aber nichts daran das GetModuleHandle(NULL) immer die Startadresse des prozesses liefert.
      Und diese Basis wird z.B. für bestimmte Lookups (Windows Classes) verwendet. Allerdings kann dies eben fatal sein, wenn man Klassen in DLLs hat.

      Aber evtl. reden wir hier aneinander vorbei und meinen das gleiche.

  7. In den allermeisten Beispielen mit einem LoadString() darin bekommt
    LoadString() seinen ersten Parameter mit hInstance aus WinMain()
    belegt. Der gleiche hInstance wie bei RegisterClass() / CreateWindow() .

    Warum ich aber ausgerechnet LoadString() zitiern moechte?

    In der derzeitigen (07/2013) MSDN-Dokumentation zum hInstance des
    LoadString() heisst es am Schluss:

    … . To get the handle to the application itself, call the GetModuleHandle function with NULL.

    Der Link dahin:
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms647486(v=vs.85).aspx

    1. Und genau hier zeigt sich, dass man wissen muss was man machen will.
      Mit LoadString und dem hInstance aus GetModuleHandle(NULL) liefert ebennur die Ressource aus der Applikation.
      Was aber wen man eine DLL hat? Man muss sich eben klar sein was man macht!

  8. Auf der Seite von microsoft (Stand 5.4.2017):
    https://msdn.microsoft.com/de-de/library/windows/desktop/ff381400(v=vs.85).aspx
    ist im unteren Teil ein Quelltext, der beginnt mit:

    template

    Darin findet sich in der Methode create() im unteren Teil:

    wc.hInstance = GetModuleHandle(NULL);

    und:

    m_hwnd = CreateWindowEx(
    dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
    nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
    );

    Damit ist es fuer mich auch „offiziell“ 😉

    1. Hast Du meine Einschränkungen gelesen?
      Vermutlich nicht, wenn nicht hast Du es evtl. nicht verstanden. Der der diesen Code geschrieben hat, wusste es nicht besser 😉
      Es bleibt dennoch „für allgemein falsch“.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.