|
|
|
|
Programmieren mit C++AllgemeinesObjekt Orientierte ProgrammierungDLLs in MFC Applikationen einsetzen
|
Wie werden DLLs mit MFC-Applikationen eingesetzt?
| Frage | |
DLLs haben trotz moderner und leistungsfähigerer COM-Technologie nach wie vor eine Existenzberechtigung, da Funktionen einer DLL schneller geladen werden als COM-Objekte. Somit sind DLLs weiterhin eine ideale Schnittstelle für Module, die einer häufigen Überarbeitung unterliegen.
Allerdings gibt es immer noch einige Ungereimtheiten im Umgang der MFC mit DLLs. Ein großes Problem speziell bei Einsatz des MFC Frameworks stellen Debug- und Release-versionen von DLLs dar, denn diese Versionen sind schlichtweg inkompatibel, sodass man nicht einfach eine Debug-Applikation mit einer Release-DLL ausführen kann.
Der einfachste Weg zum Umschiffen dieser Klippe besteht darin, Microsofts Rat zu folgen und für die beiden DLL-Versionen unterschiedliche Namen zu vergeben. Die Release-Version der DLL behält den Namen des Visual C++-Projekts, während die Debug-Version einen Namen der Form
[Projektname]D.DLL
erhält. Bei diesem Ansatz können beide DLLs in einem Verzeichnis stehen, ohne dass es zu Komplikationen kommt. Die notwendigen Schritte für diese Lösung bei Projekt AAA sind:
- Zunächst wird AAA.def nach AAAD.def kopiert und alle Vorkommen von AAA nach AAAD umbenannt.
- In Project/Settings wird Win32 Debug ausgewählt.
- Auf dem Linker-Tab ist der Ausgabename auf AAAD. DLL abzuändern.
Nun erzeugt die Debug-Version AAAD.lib und AAAD.DLL Dateien. Der Include-Header für die DLL führt alle exportierten Klassendefinitionen auf und kann zur optimalen Effizienz auch gleich Linker-Flags enthalten, damit für das Nutzen der DLL nicht extra eine Bibliotheksdatei in die Projekteinstellungen eingesetzt werden muss. Die Headerdatei bekommt dann das nachfolgende generelle Aussehen:
| Lösung |
#ifndef DEF_MYDUMMYDLL
#define DEF_MYDUMMYDLL
#ifdef _DEBUG
#pragma comment(lib, AAAD.lib)
#else
#pragma comment(lib, AAA.lib)
#endif
//...Klassendefinitionen #endif //MYDUMMYDLL
Programmierung für Modifikationen
Die bevorzugte DLL-Variante für den Export von Klassen bildet die MFC Extension DLL. Ihr Einsatz erlaubt das einfache Instanziieren einer Klasse, die sich in einer DLL befindet. Dazu ist lediglich eine Deklaration wie die folgende nötig:
| DLL-Header |
class AFX_EXT_CLASS CFoo
{
//...
}
In der Applikation, die auf die Klasse zugreift, ist dann nur noch die DLL-Headerdatei einzubinden, und alles geht seinen Gang.
Allerdings ergeben sich Probleme, wenn Methoden und Membervariable in die exportierten Klassen neu hinzugefügt werden sollen.
Für jede Änderung ist der DLL-Header zu ändern und dies zieht nach sich, dass alle Module, die auf die DLL zugreifen, neu kompiliert werden müssen. Während dies für Methoden uneingeschränkt gilt, gibt es für Membervariable zumindest einen kleinen Ausweg, indem die Membervariable nicht direkt im Klassenkörper deklariert werden, sondern in einer Art Implementationsklasse. Es ergibt sich dann eine Abhängigkeit.
class CFooImpl;
class CFoo{
protected:
CFooImpl* m_pThis;
};
Auf diese Weise muss die Klasse CFooImpl nicht für alle Nutzer der DLL deklariert werden. Die Implementation der Klasse CFooImpl erhält damit folgendes Aussehen:
| Deklaration für DLL-Klasse |
class CFooImpl{
public:
CString m_sName;};
Die Klasse CFoo greift dann auf CFooImpl zurück.
| Klasse CfooImpl |
CFoo::CFoo(){
m_pThis = new CFooImpl;
m_pThis->m_sName = _T("Unknown");}CFoo::~CFoo(){
delete m_pThis;}
Eine alternative Methode, mit Änderungen zu leben und zu arbeiten, ist, Strukturen in der Art des Windows API zu verwenden. Dazu verwenden Methoden einfach einen LPVIOD-Zeiger als Adresse einer Strukturinstanz für die Ein- und Ausgangsparameter, die methodenintern auf die entsprechende Struktur gecastet werden. Die Strukturen selbst definieren als erstes Member ein DWORD, das die Größe der Struktur übergibt. Auf diese Weise kann sowohl die DLL als auch die Anwendung klar erkennen, welche Daten übergeben werden, wenn neue Felder der Struktur immer hinten angehängt werden. Eine solche Struktur könnte z.B. wie folgt aussehen:
| Klasse Cfoo |
typedef struct tagCHANGEABLE{
DWORD dwSize;
long lBytes;} CHANGEABLE, *LPCHANGEABLE;
Zugriffe in einer Methode der Klasse CFoo erfolgen nach Cast und Check der Strukturgröße über typisierte Zeiger.
BOOL CFoo::Method(LPVOID lpIn)
{
LPCHANGEABLE lpChangeable = (LPCHANGEABLE)lpIn;
if (lpChangeable->dwSize == sizeof(CHANGEABLE))
{
//...
return TRUE;
}
return FALSE;
}
Der Einsatz der Klasse ähnelt dann dem Block.
CFoo myFoo;
CHANGEABLE changeable;
// Initialisierung:
memset(&changeable, 0, sizeof(changeable));
changeable.dwSize = sizeof(changeable);
// Methodenaufruf:
myFoo.Method(&changeable);
DLL bei Bedarf laden
Befinden sich in der DLL Dialoge oder selten benötigte Klassen, wird man regelmäßig darauf verzichten, diese schon beim Starten der Applikation zu laden bzw. zu erzeugen. Vielmehr genügt es, das Laden oder Erzeugen dann durchzuführen, wenn die entsprechenden Objekte tatsächlich benötigt werden, so wie es auch COM macht. Hierbei handelt es sich um das dynamische Laden der DLL. Die exportierte Funktion ist wie folgt zu deklarieren:
| Strukturdefinition |
__declspec( DLLexport )
void MyExportedFunc(DWORD dw){
//...}
Diese Funktion muss in die Definitionsdateien der Debug- und Release-Version eingebunden werden. Für die Debug-Version der Def-Datei ergibt sich somit:
LIBRARY "AAAD"
DESCRIPTION 'AAAD Windows Dynamic Link Library'EXPORTS
MyExportedFunc @1
Damit die Applikation die Funktion verwenden kann, ist die Bibliothek zu laden, der Funktionseinsprungspunkt zu finden und der Aufruf vorzunehmen. Damit der Code für die Debug- und Release-Version nicht ineffizient aufgebläht wird, setzt man sinnvollerweise Defines ein, die das Laden der „richtigen“ Bibliothek steuern.
typedef void (*MYFUNC)(DWORD); #ifdef _DEBUG
HINSTANCE hDLL = AfxLoadLibrary("AAADLLD");#else
HINSTANCE hDLL = AfxLoadLibrary("AAADLL");#endif
In hDLL steht nun das Handle der Funktion. Um welche Version es sich dabei handelt, braucht der nachfolgende Code nicht zu wissen, er greift lediglich auf das Handle zu, das zuvor geladen wurde.
if (hDLL)
{
FARPROC pnProc = GetProcAddress(hDLL, "MyExportedFunc");
MYFUNC pnMyfunc = (MYFUNC)pnProc;
pnMyfunc(0);
FreeLibrary(hDLL);
}
| Funktionsdeklaration | |
Bezüglich Dialoge blieb bei diesem grundsätzlichen Beispiel außer Acht, dass auch die Ressourcen aus der DLL geladen werden müssen (AfxSetResource..).
Der gezeigte Ansatz kann auch für das Erzeugen von Klassen verwendet werden. Dazu muss die Klasse virtuelle Funktionen verwenden, um „unresolved external symbol“ Compilierfehler zu vermeiden. Die Klassendefinition sollte dann mit der Initialisierungsfunktion wie folgt aussehen:
| Anmerkung |
class CFoo{
public:
virtual void Initialize (CString sName) = 0;
};
Implementiert wird dieses „Interface“ mit einer anderen Klasse, die nicht über die DLL-Headerdateien sichtbar ist.
| Klasse CFoo |
class CFooImp : public CFoo{
public:
CFooImp();
virtual ~CFooImp();
void Initialize (CString sName)
{
m_sName = sName;
}
protected:
CString m_sName;
};
Um eine Instanz dieses Klassen-Interface zu erzeugen, ist eine exportierte Funktion zu implementieren.
| Klasse CFooImp |
__declspec(DLLexport)CFoo* CreateFoo(DWORD dwVersion)
{
if (dwVersion == CURRENT_VERSION)
return new CFooImp;
return NULL;
}
Die Applikation erzeugt die Klasseninstanz wie folgt, indem zunächst die passende Bibliothek geladen wird.
typedef CFoo* (*MYFUNC)(DWORD);
#ifdef _DEBUG
HINSTANCE hDLL = AfxLoadLibrary("AAADLLD");
#else
HINSTANCE hDLL = AfxLoadLibrary("AAADLL");
#endif
Über das Handle hDLL lässt sich die Adresse der Funktion CreateFoo() ermitteln.
if (hDLL)
{
FARPROC pnProc = GetProcAddress(hDLL, "CreateFoo");
Mithilfe des nun erhaltenen Zeigers, der hier nicht wie in realen Anwendungen erforderlich explizit verifiziert wurde, kann dann eine Instanz der Klasse CFoo erzeugt und initialisiert werden.
MYFUNC pnMyfunc = (MYFUNC)pnProc;
CFoo* pFoo = pnMyfunc(0);
pFoo->Initialize(_T("Hi"));
Abschließend ist die Instanz der Klasse CFoo wieder zu zerstören, da ansonsten die Bibliothek nicht wieder freigegeben werden kann. Sie bleibt solange im Speicher, wie noch eine Instanz der Klasse existiert.
delete pFoo;
Erst nach dem Zerstören der Instanz pFoo kann auch die Bibliothek wieder aus dem Speicher entfernt werden.
FreeLibrary(hDLL);
}
Auf diese Weise lassen sich DLLs dynamisch mittels MFC-Applikationen laden und effizient warten und erweitern. Damit leisten sie auch im COM-Zeitalter einen sinnvollen Beitrag zur verteilten Applikationsentwicklung.
| Funktion CreateFoo
|
|
|