WindowsDLL

From HerzbubeWiki
Jump to navigation Jump to search

This page contains German notes about the DLL mechanics on Windows.

TODO: Translate and beautify this page.


Basics


Type of DLLs

  • http://msdn.microsoft.com/en-us/library/9se914de.aspx
  • Win32 DLL
    • Dies ist ein allgemeiner Begriff f¸r DLLs auf der Win32 Plattform
    • Ich habe den Begriff auch schon als Synonym zu "Non-MFC DLL" gesehen
  • Non-MFC DLL
  • Regular DLL (aka "MFC DLL" oder "Win32 DLL")
    • Benutzt MFC und linkt entweder gegen die statische oder die dynamische Version von MFC
    • Eine Regular DLL kann sowohl von MFC als auch von Non-MFC Applikationen benutzt werden
    • Eine Regular DLL (egal ob statisch oder dynamisch gegen MFC gelinkt) muss ein CWinApp Objekt erzeugen
      • InitInstance() und ExitInstance() Methoden des Objekts werden automatisch beim Laden der DLL augerufen (von der MFC-Version von DllMain())
      • Das Objekt hat jedoch keinen Main Message Pump, deswegen treffen auch die sonst ¸blichen Dinge in Bezug auf CWinApp::Run() nicht zu
    • Statisches Linken gegen MFC
    • Dynamisches Linken gegen MFC
      • http://msdn.microsoft.com/en-us/library/30c674tx.aspx
      • Muss global die folgenden Preprocessor Makros verwenden:
        • _USRDLL
        • _AFXDLL
        • _foo_ (steht nur hier, um WikidPad dazu zu bringen, den folgenden Text nicht kursiv darzustellen)
      • Muss folgendes Code-Schnipsel am Anfang jeder exportierten Funktion (bzw. Funktion, die von aussen aufgerufen werden kann) aufrufen
               AFX_MANAGE_STATE(AfxGetStaticModuleState())
    • Speicher, der in einer Regular DLL alloziert wird, sollte in der DLL bleiben, insbesondere sollte folgendes *nicht* gemacht werden:
      • Keine MFC-Objekte mit dem aufrufenden Executable austauschen
      • Keine Pointers auf Speicher, der von MFC angelegt wurde, mit dem aufrufenden Executable austauschen
      • Falls dies doch gemacht werden muss, so muss die DLL eine MFC Extension DLL sein
      • TODO: Zur Zeit nicht klar, was die Folgen sind, wenn man die Regel nicht befolgt
      • TODO: Zur Zeit nciht ganz klar, was "MFC Objekt" bzw. "von MFC angelegter Speicher" ist
  • MFC Extension DLL
    • http://msdn.microsoft.com/en-us/library/20ytt2wa.aspx
    • Eine MFC Extension DLL kann nur von MFC Applikationen benutzt werden
    • Diese Art von DLL muss verwendet werden...
      • If your DLL implements reusable classes derived from the existing MFC classes
      • Or you need to pass MFC-derived objects between the application and the DLL
    • Es ist mir nicht klar, was damit gemeint ist - schliesslich verwenden wir die ganze Zeit CDialog usw.
    • Um eine MFC Extension DLL zu erzeugen m¸ssen die folgenden Makros projektweit definiert sein
      • _AFXDLL
      • _AFXEXT
      • _foo_ (steht nur hier, um WikidPad dazu zu bringen, den folgenden Text nicht kursiv darzustellen)
    • Dieser Typ von DLL wurde fr¸her als "AFXDLL" bezeichnet
  • Resource-Only DLL
  • USRDLL und AFXDLL
    • Diese Begriffe sind historisch, sie bezeichnen DLL-Typen in Visual C++ 3.x und fr¸her
    • USRDLL heute = Regular DLL, die statisch gegen MFC linkt
    • AFXDLL heute = MFC Extension DLL
  • Redistribution von MFC
    • Linkt eine DLL dynamisch gegen MFC so muss man die DLL zusammen mit der passenden Version der MFC DLLs ausliefern


Export symbols from a DLL

  • http://msdn.microsoft.com/en-us/library/z4zxe9k8.aspx
  • Exports Table
    • Funktionen, die von einer DLL exportiert werden, sind in der "Exports Table" der DLL aufgelistet
    • Die Tabelle kann mit "dumpbin /exports" angesehen werden
    • Funktionen in der Tabelle werden als *Entry Point* bezeichnet
    • Funktionen, die nicht in der Exports Table auftauchen, gelten als "private"
  • Import Library
    • .lib = Import Library File
    • Im Gegensatz zur Exports Table, die direkt in der DLL selbst steht, kann beim Builden der DLL eine sogenannte Import Library generiert werden
    • Die Import Library enth‰lt alle von der DLL exportierten Symbole
    • Die Import Library wird verwendet, wenn man gegen die DLL linken will - man *importiert* also quasi die von der DLL exportierten Symbole
  • Exportieren mit .def File
    • .def = Module Definition File
    • Im wesentlichen verwendet man dieses File, um Funktionen ¸ber eine "Ordinal", eine Art Index-Zahl, zu exportieren
    • C++ Funktionen m¸ssen mit ihrem "decorated name" aufgef¸hrt werden
    • Details siehe http://msdn.microsoft.com/en-us/library/d91k01sh.aspx
  • Exportieren mit __declspec(dllexport)
    • Wird dieses Schl¸sselwort bei der Deklaration eines Symbols verwendet, so wird das Symbol exportiert, ohne dass man es im .def File auff¸hren muss
    • Besonders interessant, da man keine C++ Name Decoration im .def File zusammenbasteln muss
  • Exportieren mit AFX_EXT_CLASS
    • MFC Extension DLLs benutzen AFX_EXT_CLASS, um ganze Klassen oder einzelne Funktionen zu exportieren
    • Das Makro expandiert zu declspec(dllexport), wenn die korrekten Makros f¸r eine MFC Extension DLL gesetzt sind
    • Details siehe http://msdn.microsoft.com/en-us/library/9xyb5w93.aspx
  • Importieren mit __declspec(dllimport)
    • Will man gegen eine DLL linken, so benˆtigt man die Import Library der DLL
    • Zus‰tzlich benˆtigt man die Header Files des Source Codes
    • In den Header Files muss bei allen exportierten Symbolen declspec(dllimport) stehen


Initialising a DLL

  • http://msdn.microsoft.com/en-us/library/988ye33t.aspx (Run-Time Library Behavior)
  • http://msdn.microsoft.com/en-us/library/7h0a8139.aspx
  • Wenn eine DLL geladen wird, so kann spezieller Initialisierungs- und Terminierungs/Cleanup-Code ausgef¸hrt werden
  • Der Typ der DLL entscheidet, wo dieser Code stehen muss
    • Non-MFC DLL = Funktion DllMain()
    • Regular DLL = CWinApp Klasse, Methoden InitInstance() und ExitInstance()
    • MFC Extension DLL = Funktion DllMain(), die vom "MFC DLL Wizard" (was auch immer das genau ist) generiert wird
  • In allen DLL Typen kann optional eine DllMain() Funktion geschrieben werden
    • Bei Regular DLLs sollte man allerdigns DllMain() nicht selber codieren, da MFC eine eigene Version der Funktion hat, die InitInstance() etc. aufruft
    • DllMain() wird sowohl beim Initailisieren als auch beim Terminieren aufgerufen wird
    • Der Grund f¸r den Aufruf ist anhand eines Funktionsparameters erkennbar


Module State (AFX_MANAGE_STATE)

  • Referenzen
  • Konzeptionell beinhaltet MFC ein Set von globalen Daten wie z.B.
    • Pointer auf das aktuelle CWinApp Objekt
    • Handle maps
  • Dieses Konzept wirkt sich auf das MFC API aus
    • AfxGetApp() gibt z.B. einen Pointer auf "das" CWinApp Objekt zur¸ck
    • Es gibt keine Mˆglichkeit, um anzugeben, welche CWinApp Instanz gew¸nscht ist
    • AfxGetApp() nimmt stillschweigend an, dass es nur ein globales CWinApp Objekt geben kann
  • Das Konzept ist historisch bedingt und geht davon aus, dass es diese globale Daten nur einmal pro Applikation gibt
    • Fr¸her konnte man nur statisch gegen MFC linken, weshalb das Konzept ok war
    • Heute dagegen kann man eine DLL dynamisch gegen MFC linken, was dazu f¸hrt, dass das Konzept nicht mehr sauber funktioniert
  • Details zum Module State (aus TN058)
    • Jeder Thread Context enth‰lt einen Pointer auf den aktuellen Module State
    • Der Pointer kann ge‰ndert werden mit Hilfe von AfxSetModuleState()
    • Der Pointer kann (vermutlich) abgefragt werden mit AfxGetModuleState()
    • Gem‰ss TN058 ‰ndert sich der Pointer auf den aktuellen Module State automatisch, wenn beim Ausf¸hren von Code von einem Module in ein anderes gewechselt wird ("when the thread of execution passes a module boundary")
      • Es ist nicht ganz klar, was "automatisch" hier bedeutet; nachfolgend jedoch eine starke Vermutung
      • Irgendwo in MFC ist der Module State als globale Variable hinterlegt
      • Beim Linken gegen die statische MFC "erh‰lt" die linkende DLL eine private Kopie der Variable
      • Wird nun in die DLL gesprungen, so wird diese private Kopie verwendet, wenn auf das Symbol der Variable zugegriffen wird
      • Beim Linken gegen die dynamische MFC "erh‰lt" die linkende DLL eine Referenz auf die externe globale Variable
      • Wird nun in die DLL gesprungen, so wird die globale Variable verwendet, die in der dynamischen MFC abgelegt ist
      • Ruft man AFX_MANAGE_STATE auf so ver‰ndert man die globale Variable auch f¸r alle anderen DLLs, die gegen die dynamische MFC gelinkt sind
    • TN058 erw‰hnt das Makro AFX_MANAGE_STATE
      • http://msdn.microsoft.com/en-us/library/ba9d5yh5.aspx
      • Das Makro funktioniert wie ein Stack: Beim Ausf¸hren wird der Module State gepushed, beim Verlassen der Funktion wird er gepopped
      • Dazu wird vermutlich AfxSetModuleState() verwendet
      • Mehrmaliges Pushen des gleichen Module States ist kein Problem
    • TN058 erw‰hnt ebenfalls die Funktion AfxGetStaticModuleState()
      • http://msdn.microsoft.com/en-us/library/cc6feexs.aspx
      • Die Funktion gibt einen Pointer zur¸ck, der auf den Module State zeigt, der f¸r das "aktuelle Module" g¸ltig ist
      • Das "aktuelle Module" ist dasjenige, aus dem gerade im Moment Code ausgef¸hrt wird
      • Vermutlich wird dieses Problem ¸ber eine statische Struktur gelˆst, die beim Laden der DLL initialisiert wird
  • Fazit: Damit MFC weiterhin so funktionieren kann, wie es urspr¸nglich angedacht war, wird folgendes empfohlen
    • In jeder Funktion, die von einer *dynamisch gegen MFC gelinkten Regular DLL* exportiert wird, soll folgendes Code-Schnipsel am Anfang stehen:
           AFX_MANAGE_STATE(AfxGetStaticModuleState())
    • Tats‰chlich sind nicht nur mit declspec(dllexport) exportierte Funktionen betroffen, sondern jeder andere "Entry Point" in eine DLL
    • Z.B. also die Funktionen einer COM Schnittstelle
    • *Nicht betroffen* von dem ganzen State Switching sind
      • Statisch gelinkte Regular DLLs: Diese Module verwenden nicht das von der DLL-Version von MFC exportierte Symbol f¸r den globalen Module State, sondern enthalten wegen dem statischen Linken eine private Kopie davon. MFC Funktionsaufrufe gehen nicht in die MFC DLL, sondern bleiben innerhalb des DLL.
      • MFC Extension DLLs: Bei diesen ist die Idee, dass sie den Module State der Applikation verwenden
  • Inhalt des Module State
    • Der Module State ist in einer Struktur vom Type AFX_MODULE_STATE abgelegt
    • m_pCurrentWinApp = Pointer auf CWinApp Objekt, welches von AfxGetApp() zur¸ckgegeben wird
    • m_hCurrentInstanceHandle = Module Handle, welches von AfxGetInstanceHandle() zur¸ckgegeben wird
    • m_hCurrentResourceHandle = Ressource Module Handle, welches von AfxGetResourceHandle() zur¸ckgegeben wird
    • m_lpszCurrentAppName
    • etc.
  • Praktische Anwendung von AFX_MANAGE_STATE
    • Verwenden, falls Ressourcen geladen werden m¸ssen
      • Es sollte auf jeden Fall das Ressource Handle verwendet werden
      • Hat man sich das Ressource Handle einmal am Anfang gemerkt, kann man sogar ohne AFX_MANAGE_STATE auskommen
    • Verwenden, falls Windows und Dialoge erstellt werden
      • Beim Erstellen von Windows werden Handles (HWND) im Module State hinterlegt; evt. sogar noch mehr
    • In einigen F‰llen muss AFX_MANAGE_STATE nicht verwendet werden, da MFC das bereits f¸r uns tut. Bekannte Beispiele
      • CWinApp::InitInstance()
      • Message Map Handlers
  • Details zu AFX_MANAGE_STATE (gesehen w‰hrend Debugging)
    • AFX_MANAGE_STATE ist ein Makro, das eine lokale Variable vom Type AFX_MAINTAIN_STATE2 deklariert
      • AFX_MAINTAIN_STATE2 ist eine struct, die in afxstat.h deklariert ist
      • Constructor AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2() ist implementiert in afxstate.cpp
      • Destructor AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2() ist implementiert in afxwin1.inl
    • Im Constructor des AFX_MAINTAIN_STATE2 Objekts wird der aktuelle globale Module State gesetzt
    • Beim Verlassen des aktuellen Kontexts wird das AFX_MAINTAIN_STATE2 Objekt automatisch zerstˆrt
    • Im Destructor wird der vorhergehende globale Module State wiederhergestellt
    • Neben dem Module State wird auch noch der sogenannte "Activation Context" (siehe SideBySide) gemanaged
      • Aber nur, falls AfxGetAmbientActCtx() == true