Diese Session beschreibt die Erstellung und Verwendung externer Bibliotheken in Form von DLLs, FLLs, ActiveX-Controls und COM Servern. Da es nicht darum geht, VFP-Anwendungen als COM Server zu erstellen und zugänglich zu machen, sondern Erweiterungen zu FoxPro und anderen Sprachen zu erstellen, ist FoxPro als Programmiersprache ungeeignet und die in dieser Session beschriebenen Verfahren basieren auf der Programmierung in C++. Für das allgemeine Verständnis sind daher Kenntnisse in C / C++ empfohlen. Außerdem hat, ungeachtet der möglichen Vor- oder Nachteile gegenüber anderen C-Compilern, Visual C++ von Microsoft sicherlich die größte Verbreitung und wird deshalb bei den Code-Beispielen verwendet.
Der Vortrag gliedert sich in zwei Teilabschnitte:
Da wären als erstes einfache DLLs, die häufig dann zum Einsatz kommen, wenn Funktionen der Windows API verwendet werden sollen, die sich z.B. in FoxPro nicht direkt ansprechen lassen. Außerdem sind DLLs sehr interessant, wenn es darum geht, z.B. Berechnungen und Stringmanipulationen einer schnelleren Sprache zu überlassen. Eine Sonderform der einfachen DLLs sind sog. FLLs, die zu Ihrer Grundfunktionalität noch eine Schnittstelle zu FoxPro beinhalten. Diese Schnittstelle ermöglicht es u.a., innerhalb der FLL auf FoxPro-Funktionen zuzugreifen.
Spätestens dann, wenn externe Komponenten auch aus anderen Sprachen genutzt werden sollen, sind FLLs nicht mehr in Betracht zu ziehen, und wenn objektorientierte Funktionalitäten, wie die vollständige Einkapselung verschiedener Instanzen einer Komponente, gefordert sind, stoßen auch "normale" DLLs an ihre Grenzen. Der zweite Abschnitt beschäftigt sich deshalb mit den objektorientierten Varianten, nämlich ActiveX (OCX-) Controls und einfachen COM Servern.
DLLs (Dynamic Link Libraries) sind Funktionsbibliotheken, die gegenüber der statischen, also festen Einbindung von Bibliotheken drei Vorteile besitzen: Dynamisches Linken bedeutet, daß diese Bibliotheken nur bei Bedarf von einem Programm geladen und verwendet werden; werden sie nicht mehr benötigt, besteht die Möglichkeit, den entsprechenden Speicherplatz wieder freizugeben. Zum zweiten können DLLs im allgemeinen mehrfach von verschiedensten Programmen genutzt werden, was wiederum Ressourcen spart. Und drittens hat man so eine einfache Möglichkeit, bestimmte Teile einer Anwendung z.B. gegen Weiterentwicklungen oder Bugfixes auszutauschen, ohne die gesamte Anwendung zu ändern.
Das Grundgerüst für eine DLL ist sehr schnell erstellt. Hierfür stellt Microsoft einen Projektassistenten zur Verfügung, der die groben Vorarbeiten in wenigen Sekunden erledigt und die benötigten Dateien erstellt. Allerdings sind die Anforderungen hier so gering, daß man durchaus auch auf den Assistenten verzichten könnte. Der Mindestumfang sind lediglich eine C++-Sourcedatei (.CPP) und eine sogenannte Definitionsdatei (.DEF). Im Normalfall gehört noch eine Header-Datei (.H) dazu, in der #include-Anweisungen und Konstantendefinitionen vorgenommen werden. Dies Definitionsdatei hat hierbei eine besondere Bedeutung. Zur Unterscheidung, welche der definierten Funktionen nur für den internen Gebrauch vorgesehen sind, und welche nachher tatsächlich z.B. von FoxPro genutzt werden können, werden exportierte Funktionen in dieser .DEF-Datei gesondert aufgeführt:
; Meine.def : Declares the module parameters for the DLL.
LIBRARY "Meine"
DESCRIPTION 'Meine Windows Dynamic Link Library'
EXPORTS
MeineErsteFunktion @2p
MeineZweiteFunktion @3p
In diesem Definition File sind zwei Funktionen für den Export angegeben, MeineErsteFunktion() und MeineZweiteFunktion(). Zu beachten ist hier, daß weder Funktionsargumente noch mögliche Rückgabewerte angegeben werden. Dies muß bei der Verwendung der entsprechendenden Funktion, z.B. mit DECLARE DLL unter VFP geschehen.
Eine weitere Besonderheit der exportierten Funktionen ist die Art und Weise, in der mögliche Parameter an die Funktion übergeben werden. Z.T. aus Kompatibilitätsgründen, z. T. auch aus historischen Gründen wird bei DLLs im allgemeinen das Übergabeformat von Pascal verwendet. Daher erhalten zu exportierende Funktionen immer eine entsprechende Calling Convention, also Aufrufkonvention. In älteren Compilerversionen war die Aufrufkonvention __pascal bzw. __far __pascal zu verwenden. Inzwischen sollte stattdessen PASCAL oder noch besser WINAPI verwendet werden. Die vollständige Funktionsdeklaration sollte also lauten
<Rückgabetyp> WINAPI <Funktioname> ( <Argumentliste> )
Auf der Seite des C-Compilers gibt es keinerlei Einschränkungen, was die Rückgabe- bzw. Argumenttypen angeht. Auf FoxPro-Seite sieht es ein klein wenig anders aus. Die in der VFP-Hilfe unter dem Befehl DECLARE DLL angegebenen Typen sind wie folgt zu verstehen:
SHORT 16-bit integer
FoxPro kennt zwar keine 16-Bit-Variablen, aber die Übergabe sowohl bei den Parametern als auch bei den Rückgabewerten funktioniert dennoch reibungslos. Zu beachten ist lediglich, daß VFP genau wie immer keinerlei Prüfung vornimmt. Beispiel:
C++
short sample ( short i )
{
return i ;
}
VFP
DECLARE SHORT sample IN meine.dll SHORT
? sample( 33333 )
Obwohl der Übergebene Wert in der C-Funktion unverändert zurückgegeben wird, lautet das Ergebnis –32203. Die Ursache hierfür liegt allerdings nicht in der C-Funktion, sondern bei FoxPro. VFP erweitert den zurückgegebenen Wert immer vorzeichenrichtig auf 32 Bit. Noch mißverständlicher wird es dann, wenn Werte außerhalb des zulässigen Bereichs übergeben werden. Da FoxPro-seitig wie gesagt keine Prüfungen durchgeführt werden, führt der Befehl
? sample( 65540 )
nicht zu einer Fehlermeldung; VFP merkt also nicht, daß die Zahl 65540 den Wertebereich einer 16-Bit-Variablen überschreitet. Die Folge ist, daß bei der Übergabe die vorderen 16 Bit einfach abgeschnitten werden und der verbleibende Teil der unteren 16 Bit zurückgegeben wird. Die Rückgabe lautet also 4.
Übrigens hat VFP sowohl in Version 5.0 als auch in Version 6.0 ein generelles Problem mit Referenzen auf 16Bit-Werte. Bei DLL-Aufrufen müssen diese FoxPro-Seitig als "INTEGER @" deklariert werden, bei ActiveX und jeder Form von COM-Server sind sie nicht zu verwenden! Die einzige Abhilfe ist hier tatsächlich eine entsprechende Änderung im Control selbst.
INTEGER 32-bit integer
LONG 32-bit long integer
Diese beiden Typen sind völlig identisch, LONG ist also auch nicht longer als INTEGER. Beide arbeiten, wie schon der Typ SHORT, vorzeichenbehaftet, unabhängig von der Deklaration in C. Beispiel:
C++
unsigned long sample2 ( unsigned long i )
{
return i ;
}
VFP
DECLARE LONG sample2 IN meine.dll LONG
? sample2( -1 )
-> -1
? sample2( 4000000000 )
-> -294967296
SINGLE 32-bit floating point
DOUBLE 64-bit floating point
VFP arbeitet intern ausschließlich mit 64-Bit-Fließkommazahlen, die bei der Übergabe an die DLL ggf. automatisch in einen SINGLE umgewandelt werden. Es ist also praktisch gleichgültig, welche der beiden Möglichkeiten verwendet wird. Bei der Rückgabe ist es gewissermaßen genauso, zumindest in VFP 6.0 – gewissermaßen deshalb, weil dank eines VFP5.0-Bugs weder SINGLE noch DOUBLE Rückgabewerte FoxPro 5.0 lebend erreichen, sondern lediglich der ganzzahlige Vorkommateil.
STRING Character string
Als STRING können nicht nur wirkliche Zeichenketten übergeben werden, sondern jede Art von Speicherbereich. Verlangt eine DLL-Funktion also z.B. die Adresse eines struct, ist dieser in VFP auch als @STRING zu deklarieren.
Einzelheiten speziell zum Aufruf von WinAPI-DLLs können Sie auch der Session D-API von Christof Lange entnehmen.
Technisch gesehen ist eine FLL (FoxPro Link Library) nichts anderes als eine DLL und wird auch ähnlich gehandhabt. Wesentlicher Unterschied ist eine Schnittstelle, die es zum einen der FLL ermöglicht, auf FoxPro-Internas und Funktionen zuzugreifen, zum anderen aber auch FoxPro in die Lage versetzt, ohne gesonderte Deklarationen die enthaltenen Funktionen so zu verwenden, als seien sie fester Bestandteil von FoxPro. Für die Erstellung von FLLs werden zwei Dateien benötigt, die Datei PRO_EXT.H, die die Prototypen der Bibliotheksfunktionen und die Definitionen der verwendeten Strukturen beinhaltet und die Interface-Funktionen, die den Zugriff auf FoxPro-Internas erlauben. Diese befinden sich in der Datei WINAPIMS.LIB.
Zwei Strukturen haben hierbei besondere Bedeutung, nämlich FoxInfo und FoxTable. Aus letzterer wird eine Variable gebildet, die den Namen _FoxTable haben muß und exportiert wird.
Die Struktur FoxInfo beinhaltet eine Kurzbeschreibung aller zu exportierenden Funktionen sowie Art und Anzahl der Übergabeparameter. Der exakte Aufbau sieht in der C-Deklaration so aus:
typedef struct {
char FAR * funcName; // Der Funktionsname in Großbuchstaben. Das
// ist der Name, der in VFP verwendet wird
FPFI function; // Ein Pointer auf die Funktion in C; Hier
// muß dementsprechend der tatsächliche Name
// der Funktion in C angegeben werden, der
// vom FoxPro-Namen abweichen kann
short parmCount; // Die Anzahl der zu übergebenden Parameter.
// Hier wird die Maximalzahl angegeben, d.h.
// optionale Parameter zählen mit.
char FAR * parmTypes; // Hier wird für jeden Parameter dessen Typ
// mit einem Großbuchstaben angegeben
} FoxInfo;
Im Normalfall wird also in dem C-Programm ein Array aus dieser Struktur erstellt. Jeder Arrayeintrag ist für eine Funktion zuständig. Die entsprechende Arrayvariable muß exportiert werden, und für den Fall, daß nicht ANSI-C sondern C++ programmiert wird, muß auch das durch die Angabe "C" angegeben werden. Ein Beispiel zur Veranschaulichung: Das Programm enthält 2 Funktionen func1 und func2. func1 erhält einen String und einen Integerwert und soll unter dem Namen StringInteger verwendet werden. func2 erhält eine logische Variable und einen optionalen Datumsausdruck und soll als "OptionalDat" erscheinen. Das Array wird dann wie folgt gebildet:
extern "C" FoxInfo MeineFunktionen[] = {
{ "STRINGINTEGER", (FPFI) func1, 2, "CI" },
{ "OPTIONALDAT", (FPFI) func2, 2, "L.D" },
} ;
Für das Element parmCount gibt es noch 3 Sonderfälle, nämlich
INTERNAL (-1)
um anzuzeigen, daß diese Funktion in FoxPro nicht verwendet werden kann. Diese Einstellung macht allerdings praktisch nie Sinn und wird dementsprechend häufig verwendet.
CALLONLOAD (-2)
bedeutet, daß die hier angegebene Funktion automatisch ausgeführt wird, wenn die FLL unter FoxPro mittels SET LIBRARY TO eingebunden wird.
CALLONUNLOAD (-3)
gibt schließlich analog zu CALLONLOAD an, daß diese Funktion immer dann automatisch durchlaufen wird, wenn die FLL wieder freigegeben wird.
Das Element parmTypes läßt beliebige Kombinationen der nachfolgenden Zeichen zu:
Optionale Parameter werden durch einen führenden Punkt gekennzeichnet.
Die zweite wichtige Struktur heißt FoxTable und enthält normalerweise nur einen Zeiger und eine Beschreibung des FoxInfo-Arrays. Normalerweise deshalb, weil auch die Verknüpfung weiterer Libraries gestattet ist. Sollte eine solche Verknüpfung verwendet werden, ist diese im ersten Element der Strukur einzutragen.
typedef struct _FoxTable {
struct _FoxTable FAR *nextLibrary;
// Dieser Eintrag ist wie gesagt für weitere
// verknüpfte Listen zu verwenden und wird im
// Normalfall auf 0 gesetzt.
short infoCount; // Die Anzahl der in FoxInfo eingetragenen
// Funktionen. Diese Anzahl wird i.d.R. mit
// sizeof() Makro abgebildet
FoxInfo FAR *infoPtr; // Und schlußendlich ein Zeiger auf das Array
} FoxTable;
Wie bereits erwähnt erhält die Variable für diese Strukur den fest definierten Namen _FoxTable. Damit sieht, das Array "MeineFunktionen" aus dem obigen Beispiel vorausgesetzt, die Deklaration wie folgt aus:
extern "C" FoxTable _FoxTable = {
( FoxTable FAR * ) 0,
sizeof( MeineFunktionen ) / sizeof( FoxInfo ),
MeineFunktionen
};
Im folgenden sind mit "FLL-Funktionen" die Funktionen gemeint, die exportiert werden. Im Unterschied dazu sind mit "interne Funktionen" alle anderen gemeint.
Damit eine FLL-Funktion Rückgabewerte an FoxPro liefern kann, stellt die FoxPro-API spezielle Funktionen zur Verfügung, die innerhalb der FLL-Funktionen verwendet werden können. D.h. die FLL-Funktionen selbst haben keinen Rückgabewert und werden deshalb als void far deklariert. Ähnlich verhält es sich mit der Parameterübergabe. Auch hier wird nicht der übliche Weg gewählt, sondern es werden alle Variablen in Form einer Struktur übergeben. Damit erhalten FLL-Funktionen nur einen einzigen Parameter, einen Zeiger auf eine Struktur vom Typ
ParamBlk
typedef struct
{
short pCount; // die Anzahl Parameter, die übergeben wurden
Parameter p[1]; // ein Array, daß für jeden Parameter ein
// Element vom Typ Parameter enthält
} ParamBlk;
Damit lautet die Deklaration einer FLL-Funktion
void far MeineFunktion( ParamBlk far * fpa )
Die erwähnte Struktur Parameter wiederum hat folgenden Aufbau:
Parameter
typedef union
{
Value val; // wenn der Parameter als Wert übergeben wurde
Locator loc; // wenn der Parameter als Referenz übergeben wurde
} Parameter;
Da es sich hier um einen Union-Typ handelt, ist entweder ein Value oder ein Locator enthalten. Das Element Value wird verwendet, wenn der Parameter als Wert übergeben wurde, bei einer Referenz ist der Locator ausschlaggebend.
Die Strukturen Value und Locator haben folgenden Aufbau:
typedef struct {
char ev_type; // Typ des Parameters
char ev_padding;
short ev_width;
unsigned ev_length;
long ev_long;
double ev_real;
CCY ev_currency;
MHANDLE ev_handle;
unsigned long ev_object;
} Value;
Je nachdem, welcher Variablentyp übergeben wurde, haben die einzelnen Elemente unterschiedliche Bedeutung:
Datentyp in ev_type | Element | Bedeutung |
---|---|---|
"C" Zeichen |
ev_length ev_handle |
Länge der Zeichenfolge Speicherhandle vom Typ MHANDLE |
"N" Numerisch |
ev_width ev_length ev_real |
Vorkommastellen für die Anzeige Nachkommastellen für die Anzeige Der eigentliche Wert (double) |
"I" Integer |
ev_width ev_long |
Stellenzahl für die Anzeige Der eigentliche Wert (long) |
"D" Datum |
ev_real |
Das Datum im Vorkommateil der Zahl 1) |
"T" DatumUhrzeit |
ev_real |
Das Datum im Vorkommateil, die Uhrzeit im Nachkommateil 2) |
"Y" Währung |
ev_width ev_currency |
Stellenzahl für die Anzeige der eigentliche Wert 3) |
"L" Logisch |
ev_length |
0 oder 1 für FALSE oder TRUE 4) |
"M" Memo |
ev_width ev_long ev_real |
FCHAN (Filehandle der Memodatei) Länge des Memofeldes Versatz innerhalb der Memo-Datei |
"G" General |
ev_width ev_long ev_real |
FCHAN (Filehandle der Datei) Länge des Objektfeldes Versatz innerhalb der Datei |
"O" Objekt |
ev_object |
Zeiger auf das Objekt 5) |
"0" NULL |
ev_long |
Enthält den Datentyp 6) |
Hinweise:
typedef struct
{
char l_type; // Typ des Wertes wie in der Value Struktur
short l_where, // gibt entweder den Arbeitsbereich an, wenn
// der Wert in einer Tabelle steht, oder -1,
// wenn es sich um eine Speichervariable handelt
l_NTI, // "Name Table Index"
l_offset, // Bei Tabellenfeldern die Feldnummer
l_subs, // 0 bei einfachen Varialben
// Bei Array ist hier zu erkennen, ob sie
// 1 - oder 2 - dimenstional sind
l_sub1, // Größe des Arrays in der 1. Dimension; nur
// gültig, wenn l_subs >= 1 ist
l_sub2; // Größe des Arrays in der 2. Dimension; nur
// gültig, wenn l_subs = 2 ist
} Locator;
Ob ein Parameter als Wert oder als Referenz übergeben wurde, kann dem jeweils ersten Element beider Strukturen Value und Locator entnommen werden. Dieses Element ist ein einzelnes Zeichen und enhält bei einer Referenz den Großbuchstaben "R". Ob also der erste Parameter ein Wert ist, kann folgendermaßen festgestellt werden:
Art des ersten Parameters:
void far MeineFunktion( ParamBlk far * fpa )
{
if ( fpa->p[0].val.ev_type == 'R'; // Referenz
// alternativ geht auch
if ( fpa->p[0].loc.l_type != 'R'; // keine Referenz
}
Normalerweise wird ein Wert als Value übergeben, d.h. die FLL erhält eine Kopie. Soll der Originalwert verändert werden, muß stattdessen eine Referenz übergeben werden. In dem Moment kommt dieser ominöse Name Table Index zum tragen. Angenommen, die FLL-Funktion "Runden" soll die numerische Variable "UNRUND" bearbeiten, dann würde das etwa so aussehen:
VFP
Runden( @unrund )
C++
void far runden( ParamBlk far * fpa )
{
// Achtung: diese Funktion muß in der FoxInfo-Struktur mit dem
// Parametertyp "R" deklariert werden!
Locator loc ;
Value val ;
if ( _FindVar( fpa->p[0].loc.l_NTI, // der Name Table Index
-1, // Speichervariable
&loc)) // und der Locator für's Ergebnis
{
if ( loc.l_type == 'N' ) // prüfen ob numerisch
{
_Load( &loc, &var ) // den Wert lesen
var.ev_real = .... // irgendeine Rundungsfunktion
_Store( &loc, &var ) // Ergebnis zurückschreiben
}
}
}
Weitere Beispiele können den API-Dokumentationen zu VFP 5.0 / 6.0 entnommen werden.
Bis jetzt wurde nur von reinen Funktionsbibliotheken gesprochen. Um den objektorientierten Aspekt mit hinein zu bringen, gibt es weitere Möglichkeiten. Zuerst zum bekanntesten Vertreter dieser Gattung.
Ursprünglich hießen sie OLE-Controls oder von der Dateiendung abgeleitet einfach "OCXe", aber nachdem sie Web-tauglich gemacht wurden, brauchte das Kind einen neuen Namen: ActiveX. Der Einsatz in HTML-Seiten ist nicht Gegenstand dieses Vortrages, aber da sie bereits vielfach als weitgehend sprachunabhängige, vielseitig verwendbare Komponenten eingesetzt werden, sollen sie etwas genauer betrachtet werden.
Ein ActiveX-Control, oder genauer gesagt die dazugehörige .OCX-Datei, ist quasi als Klasse zu verstehen, von der praktisch beliebig viele, voneinander unabhängige Instanzen, also Objekte, erstellt werden können. Bei der Verwendung solcher Klassen wird im Prinzip genauso vorgegangen wie bei jedem anderen VFP Objekt auch, d.h. mittels AddObject. Da die Klassendefinition allerdings nicht von VFP stammt, muß diese Klasse vom Betriebssystem registriert sein. Diese Registrierung wird normalerweise bei der Installation eines OCX automatisch vorgenommen, kann aber auch jederzeit von Hand nachvollzogen werden. Hierfür stellen Windows 95 und NT jeweils ein Programm zur Verfügung namens REGSVR32.EXE.
Im Rahmen dieser Registrierung kommt eine sogenannte ClassID, kurz CLSID, zum Tragen, mit der die notwendigen Einstellungen zu dem ActiveX-Control ermittelt werden können. Wie dieser Mechanismus im einzelnen funktioniert, soll am Beispiel des Toolbar-OCX erläutert werden.
Eine Liste der registrierten Controls ist in der Windows Registry unter HKEY_CLASSES_ROOT abgelegt. Bei der Auswahl eines Controls wird der Klassenname mit übergeben, der z.B. bei AddObject zu verwenden ist. In diesem Beispiel lautet der Name
COMCTL.Toolbar
COMCTL ist hierbei der Klassenname innerhalb der .OCX-Datei, die alle enthaltenen Controls zusammenfaßt; anders gesagt kann jedes .OCX mehrere Controls enthalten. Beispielsweise ist auch das Slider-Control Bestandteil der COMCTL und kann dementsprechend über COMCTL.Slider erreicht werden. Diesen Eintrag kann man nun, wie gesagt unter HKEY_CLASSES_ROOT, in der Windows Registry suchen. Der Schlüssel enthält einen weiteren Schlüssel CLSID und möglicherweise unter CurVer einen Verweis auf ein anderes Control, falls das gerade angewählte nicht der aktuelle Stand ist. Da Folgeversionen die gleiche CLSID verwenden, ist es praktisch problemlos möglich, ein Versionsupdate des Controls einzuspielen, ohne alle Anwendungen, die das Control verwenden, explizit von dem Update in Kenntnis setzen zu müssen. Nun zu der CLSID selbst. Jede Klasse erhält in Form dieser CLSID eine eindeutige Kennung. Diese ID wird bei der Neuerstellung eines ActiveX-Controls vom Compiler erzeugt und setzt sich im wesentlichen aus Zufallswerten zusammen. Um sicherzustellen, daß keine ID von keinem Compiler weltweit zweimal vergeben wird, ist sie ein furchtbar langes Zeichenkonglomerat. Die ID des erwähnten Toolbar-Controls lautet
{612A8624-0FB3-11CE-8747-524153480004}
Diese ID selbst ist wieder als Schlüssel in der Registry vorhanden der alle benötigten Informationen, wie z.B. den OCX-Dateinamen, enthält.
In den beiden vorangegangenen Kapiteln wurden keine besonderen Funktionen des Compilers verwendet, da ein einfaches CPP-File genügt hat. Lediglich für FLLs war die Einbindung der Bibliothek WINAPIMS.LIB notwendig, der Ausschluß von MSVCRT.LIB und die Einstellung der Compileroption "Multithreaded DLL" notwendig. Mit der Verwendung von COM werden die Programme allerdings ungleich umfangreicher und man sollte zumindest das Grundgerüst tunlichst einem Assistenten überlassen. Bei der Erstellung eines neuen ActiveX-Controls gehen Sie wie folgt vor: Unter Datei/Neu erscheint ein Dialog für die Neuanlage von Dokumenten. In diesem Dialog wählen Sie in der Karteikarte "Projekte" den "MFC ActiveX Steuerelement Assistenten" aus, geben dem neuen Projekt einen passenden Namen und drücken die OK-Taste. Der folgende Dialog stellt von oben nach unten folgende Fragen zu dem Projekt:
Wie am Beispiel des COMCTL.OCX bereits erwähnt kann eine OCX-Datei mehrere Controls enthalten. Für unsere Zwecke belassen Sie diese Einstellung bei 1.
Die erwähnte Laufzeitlizenz findet sich in einer .LIC-Datei wieder, die zwar vorhanden sein muß, aber lediglich einen Copyright-Vermerk enthält. Die Datei stellt also keinen echten Kopierschutz dar und ist für den ersten Versuch sicherlich mehr als überflüssig.
Im wesentlichen enthalten diese Kommentare nützliche Hinweise, was z.B. ein bestimmter Code-Abschnitt enthält und wo von Ihnen Code zu ergänzen ist. Zumindest in der Anfangsphase können diese Kommentare sehr hilfreich sein und bleiben deshalb eingeschaltet.
Für's erste sind online-Hilfetexte sicherlich nicht notwendig, außerdem wird ohnehin nur der erste Ansatz vorproduziert.
Im zweiten Dialog kann der Name nochmals nachbearbeitet werden, aber das sei hier mal unwichtig. Die weiteren Optionen sind interessanter:
Sobald der unter dem ActiveX-Control liegende Container, z.B. die Form, sichtbar wird, soll das Control aktiviert werden. Die Option ist sinnvoll und bleibt erhalten.
Unser Beispiel soll nur als Demonstration von Eigenschaften, Methoden und Ereignissen dienen und hat vorerst keinerlei grafische Ausgabe. Das heißt, daß es zur Laufzeit nicht sichtbar sein soll und deshalb wird diese Option eingeschaltet.
Unser Control soll genau wie alle anderen OCXe jederzeit unter VFP mittels dieses Dialoges einsetzbar sein, deshalb wird die Option eingeschaltet.
Diese Option erstellt eine Dialogressource, die in einer "AboutBox"- Methode verwendet werden kann. Alleine schon, damit man dieses Verfahren mal kennengelernt hat, sollte man die Option einschalten.
Frame-Controls können weitere Controls beinhalten. Ein Beispiel ist die OptionGroup in VFP. Da, wie gesagt, unser Beispiel überhaupt nicht sichtbar sein soll, können wir auf solche Fähigkeiten verzichten.
Wenn man sich beispielsweise eine eigene Combobox bauen möchte, kann man auf bereits vorhandene Klassen der MFC (Microsoft Foundation Classes) zurückgreifen und sich hier die Klasse aussuchen, die den eigenen Wünschen am nächsten kommt. Lassen Sie die Einstellung vorerst auf "(none)".
Alles notwendige wurde gemacht, clicken Sie nun auf "Fertigstellen". Sie erhalten eine Kurzinformation darüber, was der Assistent alles angestellt hat und können sich dem Control widmen. Damit Sie eine ungefähre Vorstellung darüber erhalten, was bis jetzt passiert ist, wählen Sie einfach mal im Menü "Erstellen" den Menüpunkt "<projektname>.OCX erstellen". Wenn der Erstellungsprozeß abgeschlossen ist, können Sie Ihr Control, z.B. unter VFP, bereits einsetzen, d.h. auch die Registrierung mittels REGSVR32 ist bereits erledigt. Das Control hat zwar natürlich noch keine Funktionalität außer einigen Standard-Eigenschaften, aber diesen Stand haben Sie immerhin ohne eine einzige Zeile Code erreicht.
Angenommen, Sie haben Ihr Projekt "DEMO" genannt, dann finden Sie jetzt in der Übersicht des Compilers im Reiter "Dateien" unter "Quellcodedateien" vier .CPP-Files. Da wäre zunächst eine DEMO.CPP, das ist das Grundmodul, das alle enthaltenen Controls zusammenfaßt. Im Beispiel ist das zwar nur ein Control, aber das Verfahren ist trotzdem das gleiche. Das Control selbst finden Sie dann unter DEMOCtl.CPP. Zu einem ActiveX-Control gehört normalerweise ein sog. Property Sheet, ein kleiner Dialog zum Einstellen der Eigenschaften des Controls. Den Sourcecode für diesen Eigenschaftendialog finden Sie im Programm DEMOPpg.CPP. Zum Schluß enthält das Projekt noch eine STDAFX.CPP, die normalerweise nur STDAFX.H einbindet. Diese Include-Datei wird immer dann benötigt, wenn MFC zum Einsatz kommt. Die Verwendung einer separaten Source-Datei, die nur diese Includedatei einbindet, führt dazu, daß die Includedatei vorcompiliert wird. Den Vorteil werden Sie bei jeder weiteren Compilierung merken, es geht dadurch einfach schneller.
Um dem Testcontrol etwas Leben einzuhauchen, müssen Sie sich dem Code für das Control widmen, also hier der Datei DEMOCtl.CPP.
Zu Anfang dieser Datei werden Sie, neben einigen Include-Anweisungen die Deklarationen einer Message-Map, einer Dispatch-Map, einer Event-Map und der – noch unausgefüllten – Propertypages vorfinden. Es folgen die Angaben, die Sie beim Assistenten gemacht haben, diesmal in der für das Programm korrekten Form. Blättern Sie etwas weiter nach unten, bis Sie die Definition einer Funktion vorfinden, die etwa so aussieht:
CDEMOCtrl::CDEMOCtrl()
Wie es in Klassen, die unter C++ erstellt wurden, üblich ist, hat der Default-Constructor den gleichen Namen wie die Klasse selbst. Ebenso ist der Destructor
CDEMOCtrl::~CDEMOCtrl()
handelsüblich. Es liegt also die Vermutung nahe, daß auch selbstdefinierte Methoden, Events und Eigenschaften auf diese Art deklariert und definiert werden. Und so ist es auch. Theoretisch könnte man also durch einfaches Hinzufügen von Funktionen das Control erweitern. Praktisch ist davon aber abzuraten, weil alle Elemente in insgesamt 3 Dateien korrekt vermerkt werden müssen. Zunächst wäre da die Deklaration, die in die Klassendefinition gehört und dementsprechend in DEMOCtl.H eingetragen werden muß. Zum zweiten ist natürlich die Definition in der CPP-Datei notwendig. Und drittens ist für die Programme, die das OCX einsetzen, eine Typelibrary notwendig. Für die Erstellung der Typelibrary ist eine Programmdatei zuständig, die "DEMO.odl" heißt. Da das "Ctl" im Dateinamen fehlt, ist diese Datei also für alle enthaltenen Controls gemeinsam zuständig. Da es wie gesagt für den Anfang etwas zu kompliziert ist, alle 3 Dateien korrekt zu pflegen, sollte man auch hier wieder den Assistenten verwenden. Diesmal ist der der Klassenassistent und er wird einfach mit einem rechten Mausclick im Programmcode aufgerufen.
Dieser Klassenassistent ist in 5 Karteikarten unterteilt. Die gesammelten Standardevents von Windows können in den ersten beiden dieser 5 Reiter behandelt werden, die ActiveX-Events des 4. Reiters behandeln wirklich auf die ActiveX-Technologie bezogene Events und Reiter Nummer 5 liefert ein paar Informationen zu den vorhandenen Klassen. Die für den Einstieg einzig interessante Karteikarte ist die dritte und mit "Automatisierung" übertitelt. Vielleicht fragen Sie sich jetzt, was das ganze mit Automation zu tun hat. Automation ist nichts anderes als die Verfügbarkeit eines OLE Servers (bzw. COM Servers), der von beliebigen Clients, also Anwenderprogrammen genutzt werden kann. Ein ActiveX-Control ist nichts anderes und deshalb fällt die Definition der Schnittstelle zur Außenwelt unter Automation.
In der Combobox Klassenname werden Sie zwei Klassen vorfinden, die des Controls und die des Eigenschaftsdialogs zu dem Control. Wählen Sie die Control-Klasse, also z.B. "DEMOCtl" aus.
Auf der rechten Seite des Klassenassistenten finden sich zwei Buttons "Eigenschaft hinzufügen" und "Methode hinzufügen".
Als erstes wollen wir eine Methode hinzufügen. Der Name für diese Methode sei Test und sie soll einen Long Integer Wert als Parameter erhalten. Die Rückgabe soll logisch sein, und zwar TRUE, wenn die übergebene Zahl positiv war, und ansonsten FALSE. Wenn Sie "Methode hinzufügen" auswählen, werden Sie als erstes nach dem externen Namen gefragt. Das ist der Name, unter dem diese Methode z.B. in VFP erscheint. Der interne Name im C-Code kann davon abweichen und wird deshalb gesondert erfaßt. Geben Sie dennoch in beiden Feldern "Test" ein. Da ein logischer Wert zurückgegeben werden soll, wählen Sie im Feld für den Rückgabetyp den Eintrag "BOOL" aus.
Die Parameterliste ist noch leer, aber in dem Moment, in dem Sie mit der Maus in die farblich markierte erste Zeile clicken, können Sie den Variablennamen und den Typ des ersten Parameters eingeben. Genauso wird mit weiteren Parametern verfahren, aber hier soll nur einer vom Typ "long" angelegt werden. Als Namen für die Variable wählen Sie z.B. "longvar". Nachdem Sie beide Dialoge mit "OK" geschlossen haben, befinden Sie sich wieder im Sourcecode, an dessen Ende nun eine Methode angehängt wurde, die folgendermaßen ausssieht:
BOOL CDEMOCtrl::Test(long longvar)
{
// TODO: Add your dispatch handler code here
return TRUE;
}
Ändern Sie die return-Anweisung in
return ( longvar >= 0 );
um, und die oben beschriebene Funktionalität ist bereits fertig ausformuliert. Was hat der Assistent außerdem noch gemacht? Den Funktionsrumpf haben Sie bereits gesehen und bearbeitet. Öffnen Sie nun die Datei "DEMOCtl.H" und sehen Sie sich die Klassendefinition an. Innerhalb dieser Klassendefinition finden Sie den Prototyp für die Methode
// Dispatch maps
//{{AFX_DISPATCH(CDEMOCtrl)
afx_msg BOOL Test(long longvar);
//}}AFX_DISPATCH
Dieses "afx_msg" dient dem Klassenassistenten als Markierung und hat für die eigentliche Funktion keine Bedeutung. Und, wie bereits erwähnt, finden Sie auch in der für die spätere Type-Library zuständigen .ODL-Datei einen Eintrag für diese Methode:
// NOTE - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CDEMOCtrl)
[id(1)] boolean Test(long longvar);
//}}AFX_ODL_METHOD
Diese Warnungen sind übrigens ernst zu nehmen, fehlerhafte Änderungen an dieser Stelle werden nicht selten mit Abstürzen bei der Ausführung des Controls belohnt. Auch die Kommentarzeilen sollte man nicht unterschätzen, da sie zusammen mit den doppelten geschweiften Klammern in einer Microsoft-Umgebung besondere Bedeutung erlangen.
Ähnlich wie diese Methode können auch Properties hinzugefügt werden. Wählen Sie hierzu in der Karteikarte "Automation" des Klassenassistenten die Schaltfläche "Eigenschaft hinzufügen" an. Grundsätzlich werden zwei Eigenschaftsarten unterschieden. Die erste Art ist eine sogenannte "Member-Variable". Die Eigenschaft wird automatisch erstellt und erhält eine eigene Benachrichtigungsfunktion, die immer dann durchlaufen wird, wenn eine solche Eigenschaft von außen geändert wird. Wie der Name schon sagt, hat diese Funktion aber nur Mitteilungscharakter, es können also z.B. keine Änderungen verhindert werden. Aber das Prinzip der Assign- und Access-Methoden von VFP 6.0 ist auch hier verfügbar, nämlich dann, wenn man die zweite Art verwendet: Get-/Set-Methoden. Bei dieser Art wird kein Property angelegt, man muß also selbst ein geeignetes Property in der Klassendefinition eintragen. Falls Sie z.B. den Funktionsnamen für die Set-Funktion leer lassen, gibt es keine Möglichkeit mehr, die Eigenschaft zu beschreiben, d.h. es ist jetzt ein Read-Only-Property. Ebenso können natürlich auch Write-Only-Properties erstellt werden.
Und wieder ist es eine DLL, die dem Konzept zugrunde liegt. Ähnlich wie bei einem OCX gibt es eine Klasse, die quasi als "Träger" fungiert und im Prinzip beliebig viele untergeordnete Klassen. Die COM-Schnittstelle ist die gleiche wie auch bei ActiveX-Controls, wo liegt also der wesentliche Unterschied?
Es gibt zwar Möglichkeiten, ein ActiveX-Control so zu gestalten, daß ein freies Objekt erzeugt werden kann, d.h. statt das Control in eine Form einzubauen kann es auch per CreateObject() verwendet werden. Im allgemeinen haben OCXe aber eine grafische Ausgabe, die ein übergeordnetes Fenster voraussetzt, und deshalb ist dieses Verfahren unüblich.
Der zweite Unterschied kommt ebenfalls durch die Einbindung in eine Form zustande, und zwar die Möglichkeit, Methoden und Events mit eigenem Code zu füllen. Besonders bei Events ist das natürlich wichtig, denn was nützt ein Event, auf den nicht reagiert werden kann?
Dennoch sind solche Controls manchmal völlig ausreichend, wenn es z.B. darum geht, Berechnungen unter Verwendung von Properties und Methoden auszuführen. Die Erstellung ist wieder ausgesprochen einfach, nur verwenden Sie diesmal den "MFC Anwendungs-Assistenten für DLLs". Schritt 1 des Assistenten erwartet zwei wichtige Angaben:
Im Gegensatz zu ActiveX-Controls wird das Inlet noch nicht automatisch erstellt, lediglich der Sammelcontainer für die eigentlichen Controls. Wählen Sie also in der einzigen CPP-Datei, die erstellt wurde, wieder den Klassenassistenten aus und wählen Sie, wieder unter "Automatisierung", die Funktion "Klasse hinzufügen". Klassen mit vorhandener TypeLibrary sind etwas komplexer, wählen Sie daher erst einmal "Neu".
Der Name der Klasse ist völlig frei wählbar und vorerst wird eine der einfachsten Basisklassen ausreichen, wählen Sie daher "CCmdTarget" als Basisklasse. Im Unteren Teil dieses Dialoges können Angaben zur Automatisierung gemacht werden. Da das Control mittels CreateObject() in FoxPro verwendet werden soll, muß hier die letzte Einstellung, "Erstellbar nach Type-ID" angewählt sein. In dem Dazugehörigen Eingabefeld können Sie nun den Namen festlegen, der bei CreateObject angegeben wird. Z.B. kann der vordere Teil des Vorschlags inclusive des Punktes weggelassen werden.
In der neuen Klasse werden nun nach dem gleichen Verfahren wie bei den ActiveX-Controls Methoden und Properties hinzugefügt.
Bei der Erstellung eine ActiveX-Controls wird die .OCX-Datei automatisch registriert. Bei diesen einfachen COM-Servern muß das von Hand mittels REGSVR32.EXE gemacht werden.
Bei der Weitergabe muß darauf geachtet werden, daß COM-Server wie ActiveX-Controls bei der Installation registriert werden. Sinnvoll ist es auch, die Datei im Windows-System(32)-Verzeichnis abzulegen.
API ist die Abkürzung für Application Programming Interface und beschreibt - wie der Name vermuten läßt - eine zunächst einmal nicht näher spezifizierte Schnittstelle, die bei der Anwendungserstellung genutzt werden kann. APIs werden von den unterschiedlichsten Komponenten zur Verfügung gestellt und sollen deren Nutzung erleichtern und vorallem auch unter artverwanden Komponenten vereinheitlichen. Beispielsweise stellen die meisten ISDN-Karten für die Nutzung der verschiedenen ISDN-Dienste eine sog. Common ISDN API oder nochmals abgekürzt CAPI zur Verfügung. In diesem Vortrag ist mit API jedoch immer die Windows API gemeint, die im Grunde nichts weiter als eine Vielzahl häufig verwendeter Funktionen zugänglich macht.
Automation ist die Fähigkeit, die Objekte einer anderen Anwendung durch Programmcode zu steuern. Ein wichtiger Punkt hierbei ist die Fähigkeit eines Objektes, seine Eigenschaften, Methoden und Events mittels sog. Type Descriptions anderen Programmen zugänglich zu machen, was die absolute Grundlage für ActiveX-Controls ist.
ActiveX-Controls sind Objekte, die visuelle Elemente enthalten können, wie z.B. eine Treeview-Steuerung, und deren grundsätzliches Verhalten dem der VFP-eigenen Objekte gleichgesetzt werden kann. D.h. daß mit Instanzen eines ActiveX-Controls dessen (geänderte) Eigenschaften und Methoden ebenso gespeichert werden, wie das auch bei VFP-Objekten, z.B. in einer Form, der Fall ist.
COM (Component Object Model) beschreibt eine von Microsoft spezifizierte Schnittstelle zum Austausch von Daten zwischen verschiedenen Programmen. COM ging aus OLE bzw. OLE2 hervor und bildet die Basis von ActiveX, COM Servern (auch bekannt als OLE Automation) und verwandten Komponenten.
DCOM (distributed COM) bildet in erster Linie die Fähigkeit, z.B. einen COM Server auf einem Rechner im Netz zu installieren und ihn durch ein Programm auf einem anderen Recher zu steuern, also quasi OLE Automation per Fernsteuerung durchzuführen.
"Microsoft Foundation Classes", faßt praktisch alle von MS zur Verfügung gestellten Klassenbibliotheken zusammen.
Unter der Windows API versteht man eine Reihe von Funktionsbibliotheken, die nach Themengebieten gruppiert sind. Beispielsweise gehören die meisten Datei-Funktionen wie Öffnen, Schließen oder auch die Ermittlung des System-TEMP-Verzeichnisses zu den KERNEL-Funktionen. Die USER-Bibliothek enthält z.B. die Standard-Messagebox. Standard-Dialoge wie Datei Öffnen oder Datei Drucken finden sich in der COMDLG-Bibliothek usw. Diese Bibliotheken liegen jeweils als DLL vor (z.B. KERNEL32.DLL) und einigen Hochsprachen wie C auch als .LIB-Datei zum direkten Einbinden in eigene Programme.
Zu den Standard-Bibliotheken gehören unter C die Dateien KERNEL32.LIB, USER32.LIB, GDI32.LIB, WINSPOOL.LIB, COMDLG32.LIB, ADVAPI32.LIB, SHELL32.LIB, OLE32.LIB, OLEAUT32.LIB, UUID.LIB, ODBC32.LIB UND ODBCCP32.LIB. VFP berücksichtigt bei dem Befehl DECLARE DLL und der Bibliotheksangabe Win32API allerdings nur die Dateien KERNEL32.DLL, GDI32.DLL, USER32.DLL, MPR.DLL, and ADVAPI32.DLL; alle weiteren DLLS müssen also explizit angegeben werden.