vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 17

DLLs

Lektion 16 brachte Ihnen bei, wie man eine Funktionsgruppe erstellen kann, die sich in mehreren Anwendungen einsetzen läßt, und wie man sie in einer Bibliotheksdatei verpackt, die man mit diesen Anwendungen linkt. Heute wiederholen Sie das Ganze, allerdings mit einer weitaus flexibleren Lösung.

Oftmals weist eine Anwendungsfamilie gleiche Funktionen auf. Wenn Sie diese gemeinsam genutzte Funktionalität in DLLs statt in Bibliotheksmodulen unterbringen, können alle Anwendungen auf die gleiche Funktionalität zugreifen, wozu nur ein Exemplar des Funktionspakets in Form von DLLs zu vertreiben ist, statt die gleiche Funktionalität mehrfach für die einzelnen Anwendungen bereitzustellen. Dieses Verfahren spart Platz auf allen Systemen, wo die Anwendungen installiert sind.

Die heutige Lektion zeigt, ...

Warum DLLs erstellen?

Microsoft hat bereits in den frühen Tagen von Windows sogenannte dynamische Linkbibliotheken (DLLs - Dynamic Link Libraries) eingeführt. DLLs stimmen mit Bibliotheksmodulen darin überein, daß sie beide eine Funktionsgruppe verpacken, auf die Anwendungen zurückgreifen können. In der Bindung der Bibliothek zur Anwendung unterscheiden sich beide Typen. Wenn Sie eine Anwendung mit einem Bibliotheksmodul (LIB) erstellen, wird die Funktionalität der Bibliothek zur Anwendung gelinkt. Die in der Bibliotheksdatei enthaltene Funktionalität wird Teil der ausführbaren Anwendungsdatei. Bei einer DLL dagegen bindet die Anwendung die in der Bibliotheksdatei enthaltene Funktionalität zur Laufzeit der Anwendung. Die Bibliotheksdatei bleibt eine separate Datei, die von der Anwendung referenziert und aufgerufen wird.

Es gibt mehrere Gründe, warum man DLLs anstelle von Bibliotheksmodulen erstellen sollte. Erstens läßt sich die Größe der ausführbaren Anwendungsdateien verringern, indem man die von mehreren Anwendungen gemeinsam genutzte Funktionalität in DLLs unterbringt. Die Funktionalität in den DLLs kann man aktualisieren und modifizieren, ohne die ausführbare Anwendung auf den neuesten Stand bringen zu müssen (vorausgesetzt, daß die von der DLL exportierte Schnittstelle unverändert bleibt). Schließlich kann man DLLs mit nahezu jeder anderen Programmiersprache unter Windows verwenden, wodurch die von Ihnen erstellte Funktionalität einer breiteren Masse von Programmierern zur Verfügung steht und nicht nur den Anhängern von Visual C++.

DLLs erstellen und einsetzen

DLLs sind Bibliotheksdateien mit kompiliertem Code, auf den andere Anwendungen zurückgreifen können. Die DLLs legen bestimmte Funktionen und Klassen für diese Anwendungen frei, indem sie die Funktion exportieren. Eine exportierte Funktion wird in eine Tabelle eingetragen, die zur DLL gehört. Diese Tabelle enthält den Standort aller exportierten Funktionen, die in der DLL vorhanden sind. Alle nicht exportierten Funktionen erscheinen auch nicht in der Tabelle, und eine Anwendung außerhalb der DLL kann diese weder sehen noch aufrufen.

Eine Anwendung kann die Funktionen in der DLL nach zwei Verfahren aufrufen. Bei der komplizierteren Methode sucht die Anwendung nach dem Standort der gewünschten Funktion in der DLL und holt einen Zeiger auf diese Funktion. Über den Zeiger findet dann der Aufruf der Funktion statt.

Der andere und wesentlich einfachere Weg (und der einzige Weg, den Sie in allen Beispielen in diesem Buch gehen) ist, die Anwendung mit der LIB-Datei, die mit der DLL erzeugt wird, zu verknüpfen. Diese LIB-Datei behandelt der Linker als normale Bibliotheksdatei - genau wie die gestern erstellte. Allerdings enthält diese LIB-Datei Funktionsrümpfe für alle exportierten Funktionen der DLL. Diese Pseudofunktionen haben den gleichen Namen und die gleiche Argumentliste wie die echten Funktionen, enthalten aber im Inneren nur einen winzigen Codeabschnitt, der die echte Funktion in der DLL aufruft und alle an die Pseudofunktion übergebenen Parameter an die echte Funktion weiterreicht. Damit können Sie die Funktionen in der DLL genauso behandeln, als wären sie Teil des Anwendungscodes und nicht einer separaten Datei.

Die LIB-Datei für eine DLL wird automatisch beim Kompilieren der DLL erzeugt. Auf Ihrer Seite sind keine weiteren Schritte dafür erforderlich.

Es ist nicht nur leichter, Anwendungen mit Hilfe von LIB-Dateien für alle verwendeten DLLs zu erstellen, sondern auch sicherer, wenn Sie die Anwendung ausführen. Wenn Sie mit LIB-Dateien arbeiten, werden alle DLLs, auf die Sie in Ihrer Anwendung zurückgreifen, zu dem Zeitpunkt in den Arbeitsspeicher geladen, zu dem die Anwendung startet. Sollte irgendeine DLL fehlen, informiert Windows den Benutzer automatisch über dieses Problem, und Ihre Anwendung startet nicht. Wenn Sie dagegen nicht mit den LIB-Dateien arbeiten, müssen Sie die DLL selbst in den Speicher laden und sind auch dafür verantwortlich, alle Fehler zu behandeln, die bei einer fehlenden DLL auftreten können.

Zwei DLL-Typen lassen sich mit Visual C++ sehr einfach erstellen. Dabei handelt es sich um erweiterte MFC-DLLs und Standard-DLLs.

Mit Visual C++ lassen sich auch andere DLL-Typen erstellen. Diese umfassen aber einen beträchtlichen Teil der ActiveX-Funktionalität, so daß wir im Rahmen dieses Buches nicht darauf eingehen können. Wenn Sie mit Active Technology sogenannte prozeßinterne Server in einer DLL unterbringen möchten oder andere Arten von ActiveX-DLLs erstellen wollen, sollten Sie sich nach einem weiterführenden Buch zu Visual C++ umsehen, das sich speziell diesen Themen widmet.

Erweiterte MFC-DLLs

MFC-DLLs lassen sich am einfachsten kodieren und erstellen, da man sie genauso behandeln kann wie jede andere Sammlung von Klassen. Bei allen Klassen, die Sie aus einer DLL exportieren wollen, brauchen Sie lediglich das Makro AFX_EXT_CLASS wie folgt in die Klassendeklaration einzufügen:

class AFX_EXT_CLASS CMyClass
{
.
.
.
};

Dieses Makro exportiert die Klasse und macht sie damit für Visual-C++-Anwendungen sichtbar. Das Makro ist in die Header-Datei einzubinden, die von den Anwendungen, die auf die DLL zurückgreifen, verwendet wird. Damit importieren Sie die Klasse aus der DLL, so daß sie sich verwenden läßt.

Ein Nachteil bei der Erstellung von erweiterten MFC-DLLs besteht darin, daß sie sich nicht in anderen Programmiersprachen nutzen lassen, sondern nur mit anderen C++- Compilern, solange diese Compiler MFC unterstützen (wie das bei den Compilern von Borland - bzw. Inprise - und Symantec der Fall ist).

Standard-DLLs

Standard-DLLs exportieren Standardfunktionen aus der DLL und keine C++-Klassen. Im Ergebnis kann dieser DLL-Typ etwas mehr Nachdenken und Planung erfordern als eine erweiterte MFC-DLL. Innerhalb der DLL selbst können Sie allerdings alle Klassen wie gewünscht verwenden, für die externen Anwendungen müssen Sie jedoch reine Funktionsaufrufe vorsehen.

Um eine Funktion zu exportieren, ist sie als Exportfunktion zu deklarieren. Dazu versehen Sie den Funktionsnamen mit folgendem Vorsatz:

extern "C" <Funktionstyp> PASCAL EXPORT <Funktionsdeklaration>

Diese zusätzlichen Angaben sind wohl in der Header-Datei für den Prototyp der Funktion als auch im eigentlichen Quellcode erforderlich. Der Abschnitt extern "C" deklariert, daß es sich um einen normalen Funktionsaufruf in C handelt, so daß bei der C++-Namensanalyse nicht der Funktionsname zerrissen wird. PASCAL weist den Compiler an, daß alle Funktionsargumente in der PASCAL-Reihenfolge zu übergeben sind, bei der die Argumente auf dem Stack in umgekehrter Reihenfolge gegenüber der Schreibweise plaziert werden. Schließlich teilt EXPORT dem Compiler mit, daß diese Funktion aus der DLL zu exportieren ist und von außerhalb der DLL aufgerufen werden kann.

Beim Exportieren von Funktionen aus der DLL sind darüber hinaus alle exportierten Funktionsnamen in die DEF-Datei für das DLL-Projekt einzutragen. Diese Datei ist für das Erstellen der LIB-Datei mit den Pseudofunktionen sowie der Exporttabelle in der DLL erforderlich. Sie enthält den Namen der DLL oder Bibliothek, eine kurze Beschreibung der DLL und die Namen aller Funktionen, die exportiert werden. Für die Datei ist ein spezielles Format vorgeschrieben, so daß Sie die vom DLL-Assistenten automatisch erzeugte Standard-DEF-Datei nur insofern verändern sollten, als daß Sie exportierte Funktionsnamen hinzufügen. Eine typische DEF-Datei sieht folgendermaßen aus:

LIBRARY "mydll"
DESCRIPTION 'mydll Windows Dynamic Link Library'

EXPORTS
; Explizite Exporte können hier eingefügt werden
MyFunc1
MyFunc2

Wenn Sie MFC-Klassen in Ihren normalen DLLs verwenden, müssen Sie das Makro AFX_MANAGE_STATE als erste Codezeile in allen exportierten Funktionen aufrufen. Das ist erforderlich, um die exportierten Funktionen thread-sicher zu machen. Damit können Ihre Klassenfunktionen gleichzeitig durch mehrere Programme (oder Threads) aufgerufen werden. Das Makro AFX_MANAGE_STATE übernimmt als einziges Argument einen Zeiger auf eine AFX_MODULE_STATE-Struktur, die sich durch Aufruf der Funktion AfxGetStaticModuleState abrufen läßt. Eine typische exportierte Funktion, die mit MFC arbeitet, sieht wie folgt aus:

extern "C" void PASCAL EXPORT MyFunc(...)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// Hier normaler Funktionsrumpf
.
.
.
}

DLLs entwerfen

Wenn Sie Ihre DLLs entwerfen, sollten Sie sich dessen bewußt sein, daß alle Funktionen in den DLLs gleichzeitig durch mehrere Anwendungen, die zur gleichen Zeit laufen, aufgerufen werden können. Daraus folgt, daß die gesamte Funktionalität in allen von Ihnen erstellten DLLs thread-sicher sein muß.

Alle Variablen, die Werte über eine separate Funktion hinaus aufnehmen, sind durch die Anwendung und nicht von der DLL zu speichern und zu verwalten. Alle Anwendungsvariablen, die von der DLL manipuliert werden müssen, sind an die DLL als Funktionsargumente zu übergeben. Alle globalen Variablen, die innerhalb der DLL verwaltet werden, können bei laufender Funktion mit Variablen aus anderen Anwendungsprozessen ausgetauscht werden, was zu unvorhersagbaren Ergebnissen führt.

Erweiterte MFC-DLLs erstellen und einsetzen

Damit Sie sich davon überzeugen können, wie einfach es ist, eine erweiterte MFC- DLL zu erstellen und einzusetzen, konvertieren Sie heute das gestern erzeugte Bibliotheksmodul in eine erweiterte MFC-DLL. Nachdem Sie wissen, welche Arten von Änderungen Sie vornehmen müssen, um die DLL zu nutzen, implementieren Sie die gleiche Funktionalität wie bei einer normalen DLL erneut, so daß Sie am Ende die verschiedenen Lösungswege kennen, die bei den beiden DLL-Stilen erforderlich sind.

Die erweiterte MFC-DLL erstellen

Um das gestern erstellte Bibliotheksmodul in eine erweiterte MFC-DLL umzuwandeln, erzeugen Sie ein neues Projekt mit dem MFC-Anwendungs-Assistenten (dll) und spezifizieren das Projekt als Erweiterungs-MFC-DLL. Kopieren Sie den Quellcode und die Header-Dateien für die Linien- und Zeichenklassen in das Projektverzeichnis. Laden Sie die Dateien für die Linien- und Zeichenklassen in das aktuelle Projekt. Fügen Sie in die Zeichenklasse das Makro AFX_EXT_CLASS hinzu. Schließlich verschieben Sie die Farbtabelle von einer globalen statischen Tabelle in eine lokale Variable in die Funktion, die die Squiggles generiert.

Beginnen Sie ein neues Projekt, um die DLL zu erstellen. Das Projekt benennen Sie beispielsweise mit ModArtDll. Auf der Registerkarte Projekte des Dialogfelds Neu wählen Sie den Eintrag MFC-Anwendungs-Assistent (dll), wie es Abbildung 17.1 zeigt. Im ersten Schritt des MFC-Anwendungs-Assistenten wählen Sie die Option Erweiterungs-MFC-DLL (siehe Abbildung 17.2).

Abbildung 17.1:
Den Typ der DLL festlegen

Abbildung 17.2:
Den MFC-Anwendungs-Assistenten (dll) auswählen

Nachdem Sie das Gerüst der DLL erstellt haben, öffnen Sie den Explorer und kopieren die Quellcode- und die Header-Dateien für die Linien- und Zeichenklassen (line.cpp, line.h, ModArt.cpp und ModArt.h) aus dem gestern erstellten Projekt des Bibliotheksmoduls in das eben erstellte Projektverzeichnis. Fügen Sie alle vier Dateien in das Projekt ein. Auf der Registerkarte Klassen des Arbeitsbereichs sollten nun beide Klassen erscheinen.

Öffnen Sie die Header-Datei, die die Definition der Zeichenklasse enthält. Fügen Sie das Makro AFX_EXT_CLASS in die Klassendeklaration gemäß Listing 17.1 ein. Entfernen Sie außerdem die Variable der Farbtabelle aus der Klassendeklaration.

Listing 17.1: Die modifizierte Deklaration der Klasse CModArt

1: class AFX_EXT_CLASS CModArt : public CObject
2: {
3: public:
4: void NewDrawing();
5: virtual void Serialize(CArchive &ar);
6: void Draw(CDC *pDC);
7: void ClearDrawing();
8: void SetRect(CRect rDrawArea);
9: CModArt();
10: virtual ~CModArt();
11:
12: private:
13: void NewLine();
14: CRect m_rDrawArea;
15: CObArray m_oaLines;
16: };

In einer DLLs sollten keine öffentlichen, statischen Variablen vorkommen, so daß Sie die Farbtabelle nicht wie im gestrigen Projekt als öffentliches, statisches Element der Zeichenklasse deklarieren können. Aus diesem Grund verschieben Sie die Variable in eine lokale Variable in der Elementfunktion NewLine. In die Funktion NewLine nehmen Sie die lokale Variable auf und setzen das Verhalten der Funktion auf die ursprüngliche Realisierung zurück, wie es aus Listing 17.2 hervorgeht.

Listing 17.2: Die Funktion NewLine der Klasse CModArt

1: void CModArt::NewLine()
2: {
3: int lNumLines;
4: int lCurLine;
5: int nCurColor;
6: UINT nCurWidth;
7: CPoint pTo;
8: CPoint pFrom;
9:
10: // Rechteck normalisieren, dann erst Breite und Höhe bestimmen
11: m_rDrawArea.NormalizeRect();
12: // Breite und Höhe des Zeichenbereichs ermitteln
13: int lWidth = m_rDrawArea.Width();
14: int lHeight = m_rDrawArea.Height();
15:
16: COLORREF crColors[8] = {
17: RGB( 0, 0, 0), // Schwarz
18: RGB( 0, 0, 255), // Blau
19: RGB( 0, 255, 0), // Grün
20: RGB( 0, 255, 255), // Cyan
21: RGB( 255, 0, 0), // Rot
22: RGB( 255, 0, 255), // Magenta
23: RGB( 255, 255, 0), // Gelb
24: RGB( 255, 255, 255) // Weiß
25: };
26:
27: // Anzahl der Teile dieses Squiggles bestimmen
28: lNumLines = rand() % 100;
29: // Umfaßt das Squiggle mindestens ein Teil?
30: if (lNumLines > 0)
31: {
32: // Farbe bestimmen
33: nCurColor = rand() % 8;
34: // Stiftbreite bestimmen
35: nCurWidth = (rand() % 8) + 1;
36: // Anfangspunkt für Squiggle bestimmen
37: pFrom.x = (rand() % lWidth) + m_rDrawArea.left;
38: pFrom.y = (rand() % lHeight) + m_rDrawArea.top;
39: // Schleife durch Anzahl der Segmente
40: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
41: {
42: // Endpunkt des Segments bestimmen
43: pTo.x = ((rand() % 20) - 10) + pFrom.x;
44: pTo.y = ((rand() % 20) - 10) + pFrom.y;
45: // Neues CLine-Objekt erzeugen
46: CLine *pLine = new CLine(pFrom, pTo, crColors[nCurColor], ÂnCurWidth);
47: try
48: {
49: // Neue Linie in das Objektarray hinzufügen
50: m_oaLines.Add(pLine);
51: }
52: // Speicherausnahme?
53: catch (CMemoryException* perr)
54: {
55: // Meldung an Benutzer mit schlechten
56: // Neuigkeiten
57: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
58: // Wurde ein Linienobjekt erzeugt?
59: if (pLine)
60: {
61: // Löschen
62: delete pLine;
63: pLine = NULL;
64: }
65: // Ausnahmeobjekt löschen
66: perr->Delete();
67: }
68: // Anfangspunkt auf Endpunkt setzen
69: pFrom = pTo;
70: }
71: }
72: }

Nachdem Sie diese Änderungen an der Zeichenklasse vorgenommen haben, können Sie die DLL kompilieren. Wechseln Sie dann in den Explorer, suchen Sie die DLL im Debug-Unterverzeichnis unterhalb des Projektverzeichnisses, und kopieren Sie die DLL in das Debug-Verzeichnis des Projekts für die Testanwendung.

Die Testanwendung anpassen

Um die Testanwendung für die Zusammenarbeit mit der DLL anzupassen, öffnen Sie das gestern erstellte Projekt der Testanwendung. In diesem Projekt löschen Sie das gestern erstellte Bibliotheksmodul und fügen die LIB-Datei, die der Anwendungs-Assistent mit der DLL erzeugt hat, hinzu. Weiterhin ändern Sie die Header-Datei, die Sie für die Zeichenklasse eingebunden haben. Nach diesen beiden Änderungen ist die Testanwendung für den Einsatz der DLL vorbereitet.

Gehen Sie zunächst auf die Registerkarte Dateien im Arbeitsbereich, um das Bibliotheksmodul aus dem Projekt zu löschen. Markieren Sie die LIB-Datei in der Liste der Projektdateien, und drücken Sie die (Entf)-Taste. Anschließend wählen Sie Projekt / Dem Projekt hinzufügen / Dateien. Im Dialogfeld Dateien in Projekt einfügen wählen Sie als Dateityp Bibliothekdateien (.lib). Gehen Sie dann ins Debug-Verzeichnis des DLL-Projekts. Markieren Sie die LIB-Datei, die mit der DLL erzeugt wurde, im Beispiel ModArtDll.lib. Klicken Sie auf OK, um die Datei in das Projekt aufzunehmen.

Im nächsten Schritt bearbeiten Sie die Quellcodedateien für die Dokument-, die Ansicht- und die Anwendungsklasse. Dabei passen Sie die Include-Anweisungen für die Zeichenklasse wie in Zeile 7 von Listing 17.3 auf den Pfad für das Projektverzeichnis der DLL an.

Listing 17.3: Die Include-Anweisungen der Klasse CTestAppDoc

1: // TestAppDoc.cpp : Implementierung der Klasse CTestAppDoc
2: //
3:
4: #include "stdafx.h"
5: #include "TestApp.h"
6:
7: #include "..\ModArtDll\ModArt.h"
8: #include "TestAppDoc.h"

Nachdem Sie diese Änderung in allen drei Quellcodedateien vorgenommen haben, können Sie die Testanwendung kompilieren und ausführen. Die Testanwendung sollte genau wie gestern laufen, nur daß sie kürzere Squiggles erzeugt und lediglich acht Farben in der Farbtabelle verwendet.

Die DLL ändern

Da die Testanwendung nunmehr mit der DLL lauffähig ist, nehmen Sie an der DLL die gleichen Änderungen vor, die Sie gestern im Bibliotheksmodul durchgeführt haben. Erhöhen Sie die Anzahl der Squiggles, die in einer Zeichnung erscheinen können, vergrößern Sie die mögliche Länge jedes Squiggles, und generieren Sie eine beliebige Anzahl von Farben für das Squiggle.

Wechseln Sie zurück in das DLL-Projekt, um diese Änderungen vorzunehmen. In der Elementfunktion NewDrawing der Zeichenklasse erhöhen Sie die Anzahl der Linien, die generiert werden können. In der Elementfunktion NewLine vergrößern Sie die mögliche Länge der Squiggles und fügen wieder die zufälligen Farben ein (siehe Listing 17.4).

Listing 17.4: Die modifizierte Funktion NewLine der Klasse CModArt

1: void CModArt::NewLine()
2: {
3: int lNumLines;
4: int lCurLine;
5: // int nCurColor;
6: UINT nCurWidth;
7: CPoint pTo;
8: CPoint pFrom;
9: int cRed;
10: int cBlue;
11: int cGreen;
12:
13: // Rechteck normalisieren, dann erst Breite und Höhe bestimmen
14: m_rDrawArea.NormalizeRect();
15: // Breite und Höhe des Zeichenbereichs ermitteln
16: int lWidth = m_rDrawArea.Width();
17: int lHeight = m_rDrawArea.Height();
18:
19: // COLORREF crColors[8] = {
20: // RGB( 0, 0, 0), // Schwarz
21: // RGB( 0, 0, 255), // Blau
22: // RGB( 0, 255, 0), // Grün
23: // RGB( 0, 255, 255), // Cyan
24: // RGB( 255, 0, 0), // Rot
25: // RGB( 255, 0, 255), // Magenta
26: // RGB( 255, 255, 0), // Gelb
27: // RGB( 255, 255, 255) // Weiß
28: // };
29:
30: // Anzahl der Teile dieses Squiggles bestimmen
31: lNumLines = rand() % 200;
32: // Umfaßt das Squiggle mindestens ein Teil?
33: if (lNumLines > 0)
34: {
35: // Farbe bestimmen
36: // nCurColor = rand() % 8;
37: cRed = rand() % 256;
38: cBlue = rand() % 256;
39: cGreen = rand() % 256;
40: // Stiftbreite bestimmen
41: nCurWidth = (rand() % 8) + 1;
42: // Anfangspunkt für Squiggle bestimmen
43: pFrom.x = (rand() % lWidth) + m_rDrawArea.left;
44: pFrom.y = (rand() % lHeight) + m_rDrawArea.top;
45: // Schleife durch Anzahl der Segmente
46: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
47: {
48: // Endpunkt des Segments bestimmen
49: pTo.x = ((rand() % 20) - 10) + pFrom.x;
50: pTo.y = ((rand() % 20) - 10) + pFrom.y;
51: // Neues CLine-Objekt erzeugen
52: CLine *pLine = new CLine(pFrom, pTo, RGB(cRed, cGreen, cBlue), ÂnCurWidth);
53: try
54: {
55: // Neue Linie in das Objektarray hinzufügen
56: m_oaLines.Add(pLine);
57: }
58: // Speicherausnahme?
59: catch (CMemoryException* perr)
60: {
61: // Meldung an Benutzer mit schlechten
62: // Neuigkeiten
63: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
64: // Wurde ein Linienobjekt erzeugt?
65: if (pLine)
66: {
67: // Löschen
68: delete pLine;
69: pLine = NULL;
70: }
71: // Ausnahmeobjekt löschen
72: perr->Delete();
73: }
74: // Anfangspunkt auf Endpunkt setzen
75: pFrom = pTo;
76: }
77: }
78: }

Wenn Sie die Änderungen ausgeführt haben, kompilieren Sie die DLL erneut. Wechseln Sie anschließend in den Explorer, und kopieren Sie wieder die DLL in das Debug- Verzeichnis der Testanwendung. Dann starten Sie die Testanwendung über Start / Ausführen, wie es Abbildung 17.3 zeigt. Die Anwendung spiegelt nun den neuesten Stand wider und sollte jetzt sowohl mehr Squiggles als auch viele unterschiedliche Farben zeigen.

Abbildung 17.3:
Die Beispielanwendung starten

Eine Standard-DLL erstellen und einsetzen

Vielleicht kommt es Ihnen so vor, daß Sie die Verwendungsregeln der nicht von der Anwendung besessenen Variablen in der DLL verletzen, als Sie die erweiterte MFC- DLL erzeugt und erstellt haben. Das ist allerdings nicht der Fall. Die Instanz der Zeichenklasse ist ein Element der Dokumentklasse in der Testanwendung. Sie wird von der Anwendung und nicht von der DLL erzeugt und verwaltet. Wenn Sie jetzt darangehen, die gleiche Funktionalität in einer normalen DLL zu implementieren, wird das deutlicher.

Um die erweiterte MFC-DLL in eine Standard-DLL umzuwandeln, müssen Sie die Zeichenklasse in eine Folge von normalen Funktionsaufrufen konvertieren. Das Objektarray muß im Zuge dieser Umwandlung zu einer Elementvariablen der Dokumentklasse der Anwendung werden und ist als Argument an jede exportierte Funktion in der DLL zu übergeben.

Die Standard-DLL erstellen

Beginnen Sie mit einem neuen Projekt, um die erweiterte MFC-DLL in eine Standard- DLL zu konvertieren. Visual C++ muß ein Projekt erstellen, das dem Compiler mitteilt, welcher Dateityp erstellt wird. Dieses neue Projekt können Sie in den gleichen Schritten anlegen, wie Sie das Projekt der erweiterten MFC-DLL erstellt haben. Allerdings wählen Sie im ersten Dialogfeld des DLL-Assistenten die Option Standard- DLL. (Damit können Sie die Standardeinstellungen des Assistenten übernehmen.) Nachdem Sie das Projekt angelegt haben, können Sie die Quellcode- und Header- Dateien der Linien- und Zeichenklasse in das Projektverzeichnis kopieren und diese Dateien in das Projekt einfügen. Anschließend beginnen Sie, die Zeichenklasse in eine Folge einfacher Funktionsaufrufe umzuwandeln.

Die Header-Datei abändern

Zunächst einmal müssen Sie die Header-Datei für die Zeichenklasse radikal ändern, damit sie sich für eine Standard-DLL eignet. Dabei müssen Sie alle Spuren der eigentlichen Klasse aus der Header-Datei eliminieren und nur die Funktionsaufrufe beibehalten. Allen Funktionen sind die jeweils erforderlichen Objekte zu übergeben. (Ein Argument jeder Funktion ist das Objektarray.) Als nächstes modifizieren Sie alle Funktionsnamen etwas, so daß der Compiler nicht durcheinanderkommt und irrtümlich eine Elementfunktion irgendeiner Klasse aufruft (wie es zum Beispiel bei der Funktion Serialize der Fall ist). Schließlich sind alle öffentlichen Funktionen als exportierbare Funktionen zu deklarieren. Wenn Sie diese Änderungen an der Header-Datei vornehmen, ersetzen Sie letztendlich die gesamte Klassendeklaration durch die Funktionsprototypen wie in Listing 17.5.

Listing 17.5: Die modifizierte Header-Datei ModArt

1: extern "C" void PASCAL EXPORT ModArtNewDrawing(CRect pRect, CObArray Â*poaLines);
2: extern "C" void PASCAL EXPORT ModArtSerialize(CArchive &ar, CObArray Â*poaLines);
3: extern "C" void PASCAL EXPORT ModArtDraw(CDC *pDC, CObArray *poaLines);
4: extern "C" void PASCAL EXPORT ModArtClearDrawing(CObArray *poaLines);
5: void NewLine(CRect pRect, CObArray *poaLines);

Beachten Sie, daß das Objektarray an alle diese Funktionen immer als Zeiger zu übergeben ist. Da die Funktionen Objekte in das Array einfügen und daraus löschen, müssen sie mit dem eigentlichen Array und nicht mit einer Kopie arbeiten.

Die Funktionen zum Generieren der Zeichnung anpassen

Bei der Umwandlung der Quellcodedateien sind zahlreiche kleine und doch signifikante Änderungen an diesen Funktionen vorzunehmen. Beginnen wir bei der Funktion NewDrawing. Hier müssen Sie das CRect-Objekt übergeben, um den Zeichenbereich zu erhalten. Die Funktion für das Festlegen des Zeichenbereichs löschen Sie, da es keine lokalen Variablen gibt, in denen Sie dieses Objekt aufnehmen könnten. Letztendlich sind Sie besser dran, wenn Sie es an die Funktionen zur Generierung der Zeichnung übergeben. Die andere Änderung betrifft die Stelle, wo Sie das Objektarray als weiteres Argument an die Funktion übergeben. Mit diesen Argumenten unternehmen Sie in dieser Funktion nichts weiter, sondern reichen sie lediglich an die Funktion, die das Squiggle generiert, durch. Darüber hinaus ist in dieser Funktion das Makro AFX_MANAGE_STATE als erste Zeile im Rumpf der Funktion hinzuzufügen. Nachdem Sie diese Änderungen vorgenommen haben, sollte die Funktion NewDrawing wie in Listing 17.6 aussehen.

Listing 17.6: Die Funktion ModArtNewDrawing

1: extern "C" void PASCAL EXPORT ModArtNewDrawing(CRect pRect, CObArray Â*poaLines)
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: // Hier normaler Funktionsrumpf
5: int lNumLines;
6: int lCurLine;
7:
8: // Zufallszahlengenerator initialisieren
9: srand((unsigned)time(NULL));
10: // Anzahl der zu erzeugenden Linien bestimmen
11: lNumLines = rand() % 50;
12: // Sind Linien zu erzeugen?
13: if (lNumLines > 0)
14: {
15: // Schleife durch Anzahl der Linien
16: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
17: {
18: // Die neue Linie erzeugen
19: NewLine(pRect, poaLines);
20: }
21: }
22: }

In der Funktion NewDrawing ist eine weitere Änderung hinzugekommen: die Initialisierung des Zufallszahlengenerators in Zeile 9. Da es keinen Klassenkonstruktor mehr gibt, können Sie ihm auch keinen Startwert für den Zufallszahlengenerator übergeben. Demzufolge kommt als nächster logischer Platz die Funktion NewDrawing in Frage, bevor irgendwelche Zufallszahlen erzeugt werden.

In der Funktion NewLine machen sich umfangreichere Änderungen erforderlich. Zuerst sind das CRect-Objekt und das Objektarray als Argumente zu übergeben. Da es sich nicht um eine exportierte Funktion handelt, brauchen Sie auch nicht das Makro AFX_MANAGE_STATE hinzufügen. Im dritten Schritt ersetzen Sie alle Stellen, wo die Elementvariable CRect vorkommt, durch das CRect-Objekt, das als Argument an die Funktion übergeben wird. Schließlich ist die Aufnahme von Objekten in das Objektarray so zu ändern, daß die als Argument übergebenen Objektarrayzeiger verwendet werden. Nach diesen Änderungen sollte die Funktion NewLine Listing 17.7 entsprechen.

Listing 17.7: Die Funktion NewLine

1: void NewLine(CRect pRect, CObArray *poaLines)
2: {
3: int lNumLines;
4: int lCurLine;
5: // int nCurColor;
6: UINT nCurWidth;
7: CPoint pTo;
8: CPoint pFrom;
9: int cRed;
10: int cBlue;
11: int cGreen;
12:
13: // Rechteck normalisieren, dann erst Breite und Höhe bestimmen
14: pRect.NormalizeRect();
15: // Breite und Höhe des Zeichenbereichs ermitteln
16: int lWidth = pRect.Width();
17: int lHeight = pRect.Height();
18:
19: // COLORREF crColors[8] = {
20: // RGB( 0, 0, 0), // Schwarz
21: // RGB( 0, 0, 255), // Blau
22: // RGB( 0, 255, 0), // Grün
23: // RGB( 0, 255, 255), // Cyan
24: // RGB( 255, 0, 0), // Rot
25: // RGB( 255, 0, 255), // Magenta
26: // RGB( 255, 255, 0), // Gelb
27: // RGB( 255, 255, 255) // Weiß
28: // };
29:
30: // Anzahl der Teile dieses Squiggles bestimmen
31: lNumLines = rand() % 200;
32: // Umfaßt das Squiggle mindestens ein Teil?
33: if (lNumLines > 0)
34: {
35: // Farbe bestimmen
36: // nCurColor = rand() % 8;
37: cRed = rand() % 256;
38: cBlue = rand() % 256;
39: cGreen = rand() % 256;
40: // Stiftbreite bestimmen
41: nCurWidth = (rand() % 8) + 1;
42: // Anfangspunkt für Squiggle bestimmen
43: pFrom.x = (rand() % lWidth) + pRect.left;
44: pFrom.y = (rand() % lHeight) + pRect.top;
45: // Schleife durch Anzahl der Segmente
46: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
47: {
48: // Endpunkt des Segments bestimmen
49: pTo.x = ((rand() % 20) - 10) + pFrom.x;
50: pTo.y = ((rand() % 20) - 10) + pFrom.y;
51: // Neues CLine-Objekt erzeugen
52: CLine *pLine = new CLine(pFrom, pTo, RGB(cRed, cGreen, cBlue), ÂnCurWidth);
53: try
54: {
55: // Neue Linie in das Objektarray hinzufügen
56: poaLines->Add(pLine);
57: }
58: // Speicherausnahme?
59: catch (CMemoryException* perr)
60: {
61: // Meldung an Benutzer mit schlechten
62: // Neuigkeiten
63: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
64: // Wurde ein Linienobjekt erzeugt?
65: if (pLine)
66: {
67: // Löschen
68: delete pLine;
69: pLine = NULL;
70: }
71: // Ausnahmeobjekt löschen
72: perr->Delete();
73: }
74: // Anfangspunkt auf Endpunkt setzen
75: pFrom = pTo;
76: }
77: }
78: }

Die übrigen Funktionen anpassen

Die erforderlichen Änderungen an den anderen Funktionen sind weniger kompliziert als die Änderungen an den Funktionen zur Generierung der Zeichnung. Bei den übrigen Funktionen ist ein Zeiger auf das Objektarray als Funktionsargument hinzuzufügen und dann die Verwendung des Arrays auf diese Zeiger anstelle der nicht mehr existierenden Elementvariablen umzustellen. Außerdem müssen Sie das Makro AFX_MANAGE_STATE als erste Zeilen in allen verbleibenden Funktionen hinzufügen. Damit erhalten Sie die Funktionen entsprechend der Listings 17.8 bis 17.10.

Listing 17.8: Die Funktion ModArtDraw

1: extern "C" void PASCAL EXPORT ModArtDraw(CDC *pDC, CObArray *poaLines)
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: // Hier normaler Funktionsrumpf
5: // Anzahl der Linien im Objektarray ermitteln
6: int liCount = poaLines->GetSize();
7: int liPos;
8:
9: // Enthält das Array Objekte?
10: if (liCount)
11: {
12: // Schleife durch Array, dabei jedes Objekt zeichnen
13: for (liPos = 0; liPos < liCount; liPos++)
14: ((CLine*)poaLines->GetAt(liPos))->Draw(pDC);
15: }
16: }

Listing 17.9: Die Funktion ModArtSerialize

1: extern "C" void PASCAL EXPORT ModArtSerialize(CArchive &ar, CObArray *poaLines)
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: // Hier normaler Funktionsrumpf
5: // Archivobjekt an Array übergeben
6: poaLines->Serialize(ar);
7: }

Listing 17.10: Die Funktion ModArtClearDrawing

1: extern "C" void PASCAL EXPORT ModArtClearDrawing(CObArray *poaLines)
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: // Hier normaler Funktionsrumpf
5: // Anzahl der Linien im Objektarray bestimmen
6: int liCount = poaLines->GetSize();
7: int liPos;
8:
9: // Enthält das Array Objekte?
10: if (liCount)
11: {
12: // Schleife durch das Array, dabei alle Objekte löschen
13: for (liPos = 0; liPos < liCount; liPos++)
14: delete poaLines->GetAt(liPos)
15: // Array zurücksetzen
16: poaLines->RemoveAll();
17: }
18: }

Nachdem Sie die Änderungen an diesen Funktionen ausgeführt haben, ist nur noch der gesamte Code für den Konstruktor und Destruktor der Klasse sowie der Code für die Funktion SetRect zu löschen.

Die Moduldefinitionsdatei erstellen

Bevor Sie die DLL kompilieren, müssen Sie alle Funktionsnamen in die Moduldefinitionsdatei aufnehmen. Diese Datei finden Sie in der Liste der Quellcodedateien auf der Registerkarte Dateien des Arbeitsbereichs. Wenn Sie diese Datei öffnen, sehen Sie, daß sie kurz das zu erstellende Modul in allgemeiner Form beschreibt. Am Ende der Datei finden Sie eine Stelle, wo Sie die Exportanweisungen für die DLL unterbringen können. Bearbeiten Sie diese Datei, indem Sie die exportierbaren Funktionsnamen wie in Listing 17.11 hinzufügen.

Listing 17.11: Die Moduldefinitionsdatei der DLL

1: ; ModArtRDll.def : Deklariert die Modul-Parameter für die DLL.
2:
3: LIBRARY "ModArtRDll"
4: DESCRIPTION 'ModArtRDll Windows Dynamic Link Library'
5:
6: EXPORTS
7: ; Explizite Exporte können hier eingefügt werden
8: ModArtNewDrawing
9: ModArtSerialize
10: ModArtDraw
11: ModArtClearDrawing

Jetzt können Sie Ihre Standard-DLL kompilieren. Anschließend kopieren Sie sie in das Debug-Verzeichnis der Testanwendung.

Die Testanwendung anpassen

Um die Testanwendung an den Einsatz der neuen DLL, die Sie gerade erstellt haben, anzupassen, sind eine Reihe von Änderungen erforderlich. Erstens müssen Sie die Elementvariable der Dokumentklasse von einer Instanz der Zeichenklasse in das Objektarray ändern. Als nächstes sind die Include-Anweisungen im Quellcode des Dokuments und der Ansicht anzupassen, um die Header der neuen DLL anstelle der alten DLL einzubinden. (In der Quellcodedatei der Anwendung können Sie sämtliche Include-Anweisungen löschen.) Löschen Sie die DLL-LIB-Datei, und fügen Sie die LIB- Datei für die neue DLL in das Projekt ein. Ändern Sie alle Funktionsaufrufe der Zeichenklasse in Aufrufe von Funktionen in der neuen DLL. Schließlich ändern Sie die Funktion GetDrawing in der Dokumentklasse, so daß sie einen Zeiger auf das Objektarray statt auf das Zeichenobjekt zurückgibt.

Löschen Sie als erstes die LIB-Datei aus dem Projekt der Testanwendung. Fügen Sie anschließend die LIB-Datei für die neue DLL mit Projekt / Dem Projekt hinzufügen / Dateien in das Projekt ein.

Nachdem Sie die LIB-Dateien im Projekt ausgetauscht haben, bearbeiten Sie den Quellcode für die Dokument- und Ansichtsklassen, um die Include-Anweisung an das Projektverzeichnis für die neue DLL anzupassen. In der Quellcodedatei der Anwendung können Sie die Include-Anweisungen löschen. Da Sie keine Instanz der Zeichenklasse erzeugen, muß die Anwendungsdatei überhaupt nichts über die DLL wissen.

Nach diesen Änderungen öffnen Sie die Header-Datei für die Dokumentklasse. Bearbeiten Sie die Deklaration der Dokumentklasse wie folgt: Ändern Sie den Typ der Funktion GetDrawing, so daß die Funktion einen Zeiger auf ein Objektarray zurückgibt, entfernen Sie die Variable der Zeichenklasse, und fügen Sie eine Objektarrayvariable hinzu, wie es aus Listing 17.12 hervorgeht. Nehmen Sie nur diese drei Änderungen vor, alles andere in der Klassendeklaration bleibt erhalten.

Listing 17.12: Die Deklaration der Klasse CTestAppDoc

1: class CTestAppDoc : public CDocument
2: {
3: protected: // Nur aus Serialisierung erzeugen
4: CTestAppDoc();
5: DECLARE_DYNCREATE(CTestAppDoc)
6: .
7: .
8: .
9: // Implementierung
10: public:
11: CObArray* GetDrawing();
12: virtual ~CTestAppDoc();
13: .
14: .
15: .
16: private:
17: CObArray m_oaLines;
18: };

Die Funktionen des Dokuments modifizieren

Nachdem Sie die allgemeinen Änderungen an der Testanwendung vorgenommen haben, sind nun die Änderungen in der Funktionalität an der Reihe. Alle Aufrufe von Klassenmethoden des Zeichenobjekts sind in die entsprechenden Aufrufe von Funktionen in der neuen DLL zu ändern.

In der Funktion OnNewDocument löschen Sie den Funktionsaufruf, der das CRect-Objekt an das Zeichenobjekt übergibt, und ersetzen den Aufruf der Funktion NewDocument durch die neue DLL-Funktion - in diesem Fall ModArtNewDrawing, wie es Zeile 20 in Listing 17.13 zeigt.

Listing 17.13: Die Funktion OnNewDocument der Klasse CTestAppDoc

1: BOOL CTestAppDoc::OnNewDocument()
2: {
3: if (!CDocument::OnNewDocument())
4: return FALSE;
5:
6: // ZU ERLEDIGEN: Hier Code zur Reinitialisierung einfügen
7: // (SDI-Dokumente verwenden dieses Dokument)
8:
9: // Position der Ansicht ermitteln
10: POSITION pos = GetFirstViewPosition();
11: // Ist Position gültig?
12: if (pos != NULL)
13: {
14: // Zeiger auf die Ansicht holen
15: CView* pView = GetNextView(pos);
16: RECT lWndRect;
17: // Rechteck des Anzeigebereichs holen
18: pView->GetClientRect(&lWndRect);
19: // Neue Zeichnung erzeugen
20: ModArtNewDrawing(lWndRect, &m_oaLines);
21: }
22:
23: return TRUE;
24: }

In der Funktion Serialize ändern Sie den Aufruf der Serialize-Funktion des Zeichenobjekts in die neue Serialisierungsfunktion der DLL - in diesem Fall ModArtSerialize , wie in Listing 17.14 angegeben.

Listing 17.14: Die Funktion Serialize der Klasse CTestAppDoc

1: void CTestAppDoc::Serialize(CArchive& ar)
2: {
3: // Zeichnung serialisieren
4: ModArtSerialize(ar, &m_oaLines);
5: }

In der Funktion DeleteContents ist der Aufruf der Funktion ClearDrawing durch den Aufruf der neuen DLL-Funktion ModArtClearDrawing entsprechend Zeile 5 von Listing 17.15 zu ersetzen.

Listing 17.15: Die Funktion DeleteContents der Klasse CTestAppDoc

1: void CTestAppDoc::DeleteContents()
2: {
3: // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
4: // Zeichnung löschen
5: ModArtClearDrawing(&m_oaLines);
6:
7: CDocument::DeleteContents();
8: }

Schließlich müssen Sie für die Funktion GetDrawing die Funktionsdeklaration ändern, um zu kennzeichnen, daß sie einen Zeiger auf ein Objektarray zurückgibt, genau wie Sie es in der Header-Datei vorgenommen haben. Als nächstes ändern Sie die Variable, die zurückgegeben wird, in die Objektarrayvariable, die Sie in die Header-Datei aufgenommen haben (siehe Listing 17.16).

Listing 17.16: Die Funktion GetDrawing der Klasse CTestAppDoc

1: CObArray* CTestAppDoc::GetDrawing()
2: {
3: // Das Zeichenobjekt zurückgeben
4: return &m_oaLines;
5: }

Die Funktionen der Ansicht modifizieren

In der Ansichtsklasse ist eine einfache Änderung an der Funktion OnDraw vorzunehmen. In dieser Funktion ist der Typ des Zeigers, der aus der Funktion GetDrawing abgerufen wird, von einem Zeichenobjekt in ein Objektarrayobjekt zu ändern, wie es Zeile 9 von Listing 17.17 zeigt. Unmittelbar danach - in Zeile 11 von Listing 17.17 - rufen Sie die DLL-Funktion ModArtDraw auf, um das Zeichnen im Fenster auszuführen.

Listing 17.17: Die Funktion OnDraw der Klasse CTestAppView

1: void CTestAppView::OnDraw(CDC* pDC)
2: {
3: CModTestAppDoc* pDoc = GetDocument();
4: ASSERT_VALID(pDoc);
5:
6: // ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten Âhinzufügen
7:
8: // Zeichenobjekt holen
9: CObArray* m_oaLines = pDoc->GetDrawing();
10: // Zeichnung ausgeben
11: ModArtDraw(pDC, m_oaLines);
12: }

Nachdem Sie alle Änderungen an der Testanwendung vorgenommen haben, können Sie die Anwendung kompilieren und testen. Die Anwendung sollte genauso arbeiten wie mit der vorherigen DLL. Sie können auch damit experimentieren, indem Sie zurückgehen und die DLL ändern, die neue DLL in das Debug-Verzeichnis für die Testanwendung kopieren und verfolgen, wie sich die Änderungen im Verhalten der Testanwendung widerspiegeln.

Die im heutigen Beispiel entwickelte Version einer normalen DLL ist noch nicht mit anderen Programmiersprachen einsetzbar. Der Grund dafür liegt darin, daß Sie MFC-Klassen als Argumente für alle Funktionen der DLL übergeben. Damit ist die Einsatzbreite auf Anwendungen beschränkt, die mittels MFC erstellt wurden. Um diese DLL tatsächlich portierbar zu machen, müssen Sie die nackten Strukturen anstelle der Klassen übergeben (beispielsweise die RECT-Struktur anstelle der CRect-Klasse) und dann die Strukturen innerhalb der DLL in Klassen konvertieren.

Zusammenfassung

Heute haben Sie zwei Möglichkeiten kennengelernt, wie Sie Ihre Funktionalität für andere Programmierer verpacken können. Es wurde dargestellt, wie leicht sich Klassen in einer erweiterten MFC-DLL unterbringen lassen und wie einfach man sie in einer Visual-C++-Anwendung einsetzen kann. Sie haben gesehen, wie man Änderungen an der DLL vornehmen kann, ohne die darauf zugreifenden Anwendungen erneut kompilieren zu müssen. Weiterhin haben Sie gelernt, was zum Erstellen einer Standard- DLL gehört, die sich mit anderen - nicht mit Visual C++ erstellten - Anwendungen einsetzen läßt. Sie haben gesehen, wie Sie die exportierten Klassen aus der DLL in normale Funktionen nach C-Konventionen konvertieren mußten und was erforderlich ist, um eine Anwendung, die diese Art von DLL verwendet, anzupassen.

Fragen und Antworten

Frage:
Wie kann ich die Standard-DLL konvertieren, so daß sie sich in Anwendungen einsetzen läßt, die nicht mit Visual C++ erstellt wurden?

Antwort:
Zuerst sind alle Argumente an die Funktionen mit reinen Strukturen anstelle von MFC-Klassen zu übergeben. Um zum Beispiel die Funktion ModArtNewDrawing umzuwandeln, ändern Sie sie dahingehend, daß sie die RECT-Struktur anstelle der CRect-Klasse sowie einen generischen Zeiger anstelle eines Zeigers auf ein Objektarray erhält. Die Umwandlungen sind in den entsprechenden Klassen in der DLL vorzunehmen, wie es die Zeilen 4 bis 9 von Listing 17.18 zeigen.

Listing 17.18: Die Funktion ModArtNewDrawing

1: extern "C" void PASCAL EXPORT ModArtNewDrawing(RECT spRect, LPVOID lpoaLines)
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: CRect pRect;
5: pRect.top = spRect.top;
6: pRect.left = spRect.left;
7: pRect.right = spRect.right;
8: pRect.bottom = spRect.bottom;
9: CObArray* poaLines = (CObArray*)lpoaLines;
10: // Hier normaler Funktionsrumpf
11: int m_lNumLines;
12: int m_lCurLine;
13:
14: // Zufallszahlengenerator intialisieren
15: srand((unsigned)time(NULL));
16: // Anzahl der zu erzeugenden Linien bestimmen
17: m_lNumLines = rand() % 50;
18: // Sind Linien zu erzeugen?
19: if (m_lNumLines > 0)
20: {
21: // Schleife durch Anzahl der Linien
22: for (m_lCurLine = 0; m_lCurLine < m_lNumLines; m_lCurLine++)
23: {
24: // Die neue Linie erzeugen
25: NewLine(pRect, poaLines);
26: }
27: }
28: }

Darüber hinaus müssen Sie bei der Anwendung, die das Objektarray als generischen Zeiger speichert, Funktionen hinzufügen, um das Objektarray zu erzeugen und zu zerstören, wie es aus Listing 17.19 hervorgeht.

Listing 17.19: Die Funktion ModArtInit

1: extern "C" LPVOID PASCAL EXPORT ModArtInit()
2: {
3: AFX_MANAGE_STATE(AfxGetStaticModuleState());
4: // Objektarray erzeugen
5: return (LPVOID)new CObArray;
6: }

Frage:
Wann muß ich die Anwendungen, die meine DLL verwenden, neu kompilieren?

Antwort:
Immer dann, wenn Sie die Aufrufe von exportierten Funktionen ändern. Wenn Sie Argumente in diesen Funktionen ändern, hinzufügen oder entfernen, müssen Sie die Anwendungen, die auf die DLL zurückgreifen, neu kompilieren. Arbeiten Sie mit einer erweiterten MFC-DLL, ist die Anwendung, die die DLL verwendet, neu zu kompilieren, wenn sich die öffentliche Schnittstelle für die exportierten Klassen ändert oder eine neue Funktion oder Variable hinzugefügt oder entfernt wird. Es spielt keine Rolle, wenn die Anwendung gar nicht auf die geänderten Funktionen zurückgreift. Allerdings ist es übliche Praxis, die Anwendungen neu zu kompilieren, einfach um sicher zu sein.

Workshop

Kontrollfragen

1. Welche Art von DLL müssen Sie erstellen, um Klassen in der DLL für die Anwendungen verfügbar zu machen?

2. Was müssen Sie in die Klasse einfügen, um sie aus einer DLL zu exportieren?

3. Welche Art von DLL läßt sich mit anderen Programmiersprachen nutzen?

4. Müssen Sie Anwendungen, die auf eine DLL zurückgreifen, neu kompilieren, wenn Sie die betreffende DLL verändern?

5. Welche Funktion stellt die LIB-Datei für eine DLL bereit?

Übungen

1. Spalten Sie die Linienklasse in ihre eigene erweiterte MFC-DLL auf, und verwenden Sie sie mit der zweiten (Standard-) DLL.

2. Ändern Sie die DLL für die Linienklasse, so daß sie eine einheitliche Linienbreite für alle Linien verwendet.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1