monzoom-PlugIn Programmierung unter Delphi
Tutorial 01: Das Gerüst für ein PlugIn
geschrieben von Dipl.-Ing. Michael Samek
1. Grundlagen
2. Erstellung einer neuen DLL
3. Pfade einstellen
4. Export-Funktion programmieren
5. PlugIn programmieren
6. Beispiele
1. Grundlagen
Auch wenn das monzoom-SDK nur für die Programmiersprache C ausgelegt ist,
kann man problemlos und mit wenig Aufwand tolle PlugIns unter Delphi 2,3,4 oder
5 programmieren. PlugIns sind DLLs, die in einem Unterverzeichnis (z.B. \plugin\startup)
von monzoom liegen. Die PlugIns, die im "startup"-Verzeichnis liegen, werden schon
beim Programmstart geladen und initialisiert wohingegen die PlugIns, die im "User"
Verzeichnis liegen, erst manuell aufgerufen werden (z.B. das Maßband). Für
Animations-PlugIns existiert das "Anim_FX" Verzeichnis und für Mathematische
Texturen das "MTex" Verzeichnis.
Wie muß die DLL aussehen ? Genau das schreibt natürlich monzoom
vor, denn woher sollte monzoom sonst wissen, wie wir die DLL aussehen lassen.
Die DLL muß genau eine einzige Export-Funktion besitzen: RPI_message
Die Deklaration sieht wie folgt aus:
function RPI_message(inp : PPLI_param;outp : PPLI_param) : integer; cdecl;
monzoom ruft zu allen wichtigen Zeitpunkten diese Funktion mit den verschiedensten
Parametern auf. Wir müssen also nur die für uns wichtigen Messages herauspicken
und darauf reagieren. In manchen Fällen erwartet monzoom auch Rückgabeparameter
(über die outp-Parameter). Das ist bereits alles, was man als Grundlagen
benötigt.
2. Erstellung einer neuen DLL
Dazu startet ihr zunächst Delphi (ab Version 2) und wählt den Menüeintrag
"Datei/Neu":
Aus der Objektgalerie wählt ihr dann "DLL" als Projektform. Ihr erhaltet
dann folgenden (sehr übersichtlichen) Quelltext:
library Project1;
uses
SysUtils,
Classes;
{$R *.RES}
begin
end.
Ich glaube, das ich da nichts weiter erklären muß. Ihr solltet jetzt
erstmal euer neues Projekt speichern. (Menüpunkt "Alles Speichern"). Achtung:
Den Namen, den ihr jetzt vergebt, wird dann auch der Name des PlugIns im monzoom-Menü
sein.
3. Pfade einstellen
Da wir komfortabel unter Delphi arbeiten wollen und natürlich auch vom leistungsfähigen
Debugger profitieren möchten, müssen wir einige Pfade einstellen. Da
wäre zunächst der Ausgabepfad der DLL. Diesen legen wir natürlich
gleich ins monzoom-plugin Verzeichnis (und zwar ins startup). Dazu rufen wir den
Menüpunkt "Projekt/Optionen" auf und wechseln auf die Seite "Verzeichnisse/Bedingungen":
Hier tragt ihr einfach in das Feld "Ausgabeverzeichnis" euren Monzoom-Programmpfad
incl plugin/startup ein.
Da man ja bekanntlich DLL´s nicht starten kann, müssen wir als Start-Anwendung
die monzoom.exe eintragen, damit wir hinterher auch debuggen können. Dazu
wählt ihr den Menüpunkt "Start/Parameter" aus und tragt in das Feld
"Host-Anwendung" den kompletten Pfad zu eurer monzoom.exe ein.
Fertig. Das wars bereits mit den Pfaden. Wer mutig ist, kann jetzt schon mit "F9"
das Projekt compilieren (monzoom wird dann automatisch gestartet). Spätestens
wenn dann aber unser neues PlugIn initialisiert werden soll, meldet sich monzoom,
denn wir haben ja noch nicht die Export-Funktion programmiert.
4. Export-Funktion programmieren
Jetzt werden wir die Haupt-Funktion programmieren und zwar die Auswertung der
Messages, mit denen uns monzoom ständig befeuert. Am besten ihr nehmt den
unten stehenden Quelltext und fügt ihn per copy/paste in euren Quelltext
ein:
library tutorial01;
uses
SysUtils,
Classes,
mondefs;
var MyHandle : integer;
{$R *.RES}
function RPI_message(inp : PPLI_param;outp : PPLI_param) : integer; cdecl;
Var Param : PLI_param;
Begin
Param:=inp^;
case Param.ID of
FPLUG_INIT_MESSAGE : Begin
MyHandle:=Param.v_param[0];
Result:=RPI_MESSAGE_PROCESSED;
End;
FPLUG_CLOSE_MESSAGE : Begin
Result:=RPI_MESSAGE_PROCESSED;
End;
PLI_CONNECT : Begin
RPI_add_create_tool(MyHandle,'Tutorial
01',10);
Result:=RPI_MESSAGE_PROCESSED;
End;
else Result:=RPI_MESSAGE_UNKNOWN;
end;
End;
exports
RPI_message;
begin
end.
So, und nun wollen wir den Quelltext mal zerpflücken:
Zunächst ist eine weitere Delphi-Datei in der uses Anweisung hinzugekommen:
Die mondefs.pas enthält die konvertierten Header
der Monzoom-SDK, also die Funktionsaufrufe, mit denen wir Monzoom Befehle erteilen
können. Diese haben mit den Befehlen der Scripte große Ähnlichkeit.
Ich habe aber noch nicht alle Funktionen konvertiert, sondern nur die, dich ich
immer gerade benötige. Eine Übersicht über alle Funktionen gibt´s
in der SDK, und falls die Deklaration in der mondefs.pas fehlt, dann muß
man selber ran und von C nach Delphi umschreiben. Wer das nicht beherrscht, kann
sich aber an mich wenden.
Die nächste Neuerung ist die globale Variable MyHandle vom Typ Integer. Wenn
Monzoom unsere DLL initialisiert, übergibt es uns sein Handle das wir für
alle Funktionsaufrufe benötigen (siehe mondefs.pas). Der Hintergrund ist
hierbei, das auch mehrere Instanzen von Monzoom gestartet sein könnten (wohl
eher unwahrscheinlich, aber sicher ist sicher).
So und jetzt kommt das Wichtigste: Die Funktion RPI_message, die ständig
von Monzoom aufgerufen wird. Hier müssen wir auf die verschiedensten Messages
reagieren:
FPLUG_INIT_MESSAGE wird nur einmal beim Initialisieren der DLL aufgerufen.
Zu diesem Zeitpunkt erhalten wir von Monzoom auch das Handle. Wichtig: Am Ende
jeder Message müssen wir Monzoom mitteilen, ob wir auf seine Message reagiert
haben. Da wir das hier natürlich erledigt haben, schicken wir RPI_MESSAGE_PROCESSED
FPLUG_CLOSE_MESSAGE wird einmal beim Beenden von Monzoom aufgerufen.
Hier habe ich zunächst keinen Quelltext drin - wenn man aber beim späteren
Programmieren Objekte erzeugt, müssen diese hier wieder freigegeben werden.
PLI_CONNECT ist eine weitere Initialisierung von Monzoom, wobei
wir hier jetzt vollen Zugriff auf alle Funktionen haben. Daher werden wir hier
auch sämtliche Menüeinträge für unser PlugIn erzeugen. Der
Aufruf erfolgt hierbei über die Funktion "RPI_add_create_tool". Der erste
Parameter ist hierbei, wie schon angekündigt, das Monzoom-Handle gefolgt
von dem Namen des Menüeintrages. Als letztes kommt noch eine wichtige ID,
also eine willkürliche Nummer für diesen Menüeintrag. Der Hintergrund
ist hierbei: Wenn mehrere Menüeinträge erzeugt werden, müssen wir
später auch verschieden darauf reagieren. Und die Unterscheidung erfolgt
dann anhand dieser ID.
Das wars schon. Am Ende wird nur noch die Funktion RPI_message als Export-Funktion
deklariert. Fertig.
Ihr könnt jetzt mit F9 die DLL compilieren und Monzoom starten (automatisch).
Wenn ihr jetzt auf den Zauberstab klickt, werdet ihr euren Menüeintrag erkennen!
Tataaa! Leider passiert noch nix, wenn ihr diesen Eintrag auswählt. Das ist
also der nächste Punkt.
5. PlugIn programmieren
Damit wir jetzt endlich so richtig loslegen können, müssen wir noch
auf eine Message in RPI_message reagieren:
PLI_DO_CREATE_TOOL wird immer dann aufgerufen, wenn ein PlugIn-Menüeintrag
ausgewählt wurde. Hier werden wir uns wie folgt einklinken: (den Quelltext
bitte in die case-Anweisung einfügen)
PLI_DO_CREATE_TOOL : Begin
If
Param.l_param[0]=10 then Begin
RPI_print(MyHandle,'Mein
PlugIn wurde ausgewählt!');
End;
End;
Die Erklärung ist sicherlich einfach: Wir fragen ab, ob unser Menüeintrag
mit der ID 10 aufgerufen wurde und starten dann unsere Aktionen. Hier gebe ich
nur mal kurz in der Statusleiste einen Hinweistext aus (Funktion RPI_Print).
Compiliert jetzt mal mit F9 und ruft euer PlugIn auf. Tataaa. In der Statusleiste
steht jetzt der Text drin.
Damit habt ihr jetzt ein vollständiges Gerüst für ein PlugIn, das
ausgelegt ist, um Objekte zu manipulieren oder neue Objekte zu erzeugen. Ihr schreibt
alle eure Anweisungen dort hin, wo RPI_Print steht und dann wars das schon. Für
alle Fortgeschrittenen brauche ich sicherlich nicht erwähnen, das man hier
besser nur eine eigene Funktion aufrufen sollte, bzw. sich sein eigenes Objekt
erzeugen sollte.
Eure Arbeit besteht in der Regel daraus, in der SDK nach geeigneten Funktionen
zu suchen, die eure Aufgaben erledigen können. Hier mal ein paar kleine Beispiele:
6. Beispiele
(A)
mit diesem Quellcode wird das gerade selektierte Objekt um 0.5 Grad um die X-Achse
gedreht:
Procedure Beispiel1;
var Obj : PRPI_Object;
Begin
RPI_geo_edit_get_selected_obj(MyHandle,Obj);
If Obj<>nil then RPI_geo_rot(MyHandle,Obj,0.5,0);
End;
Wie schon oben erwähnt sollte man sein PlugIn in eine Prozedur oder
Funktion auszulagern. Nehmt also den obigen Quelltext und kopiert ihn einfach
irgendwo in euren Quelltext rein. Dort wo RPI_Print steht, ruft ihr dann einfach
diese Funktion auf (Nur Beispiel1() schreiben). Wie man sieht, braucht man hier
bereits eine weitere Variable "Obj". Die Funktion RPI_geo_edit_get_selected_obj
liefert euch einen Zeiger auf das gerade selektierte Objekt zurück. Wenn
gerade keins selektiert ist, ist auch der Zeiger nil. Bevor wir einfach blind
rotieren, sollten wir abfragen, ob der Zeiger nicht nil ist. Die X-Achse wird
übrigens im 3ten Paramter durch die 0 festgelegt (Y=1 und Z=2). Aber das
steht alles dann auch im SDK. Der Zeiger vom Typ PRPI_Object wird euch übrigens
sehr oft begegnen.
(B)
Das nächste Beispiel zeigt, wie man ein Objekt mit dem festen Namen "Quader1"
von der Plotkörperliste entfernt:
Procedure Beispiel2;
var Obj : PRPI_Object;
Begin
Obj:=RPI_find_obj(MyHandle,'Quader1');
if Obj<>nil then RPI_pkl_sub(MyHandle,Obj);
End;
Das Beispiel bedarf eigentlich keiner großen Erklärung.
(C)
Das nächste Beispiel ist schon etwas umfangreicher, denn hier wird eine String-Liste
mit den Namen aller Monzoom-Geo-Objekte gefüllt. Als String-Liste dient ja
meistens eine Combo-Box, sofern man noch einen Eingabedialog programmiert:
Procedure Beispiel3;
Var sl : TStringList;
num : Integer;
buffer : TRPI_Buffer;
I : Integer;
Name : String;
X : Integer;
B : Byte;
Begin
sl:=TStringList.Create;
num:=RPI_n_objs(hMonzoom,GEO_OBJEKT_ID,1);
RPI_get_objs(hMonzoom,GEO_OBJEKT_ID,1,num,buffer);
For I:=1 to num do Begin
Name:='';
X:=1;
B:=buffer[I].Buffer[X];
While B<>0 do Begin
Name:=Name + chr(B);
Inc(X);
B:=test[I].Buffer[X];
End;
sl.Add(Name);
End;
End;
Zunächst wird die String-Liste erzeugt (TStringList.Create). Wenn man jedoch
mit fertigen Combo-Boxen arbeitet, entfällt dieser Schritt. Als nächstes
wird die Anzahl der in Monzoom vorhandenen Objekte ermittelt, in "num" gespeichert
und der Buffer mit den Daten der Objekte gefüllt. Die Variable I läuft
nun durch alle Objekte und setzt die einzelnen Namen der Objekte zusammen. (Hinweis:
Die Namen sind nullterminiert, d.h. das Ende des Namens wird durch eine #0 gekennzeichnet).
Die Anweisung sl.add fügt schließlich den Namen in die Liste ein. Achtung:
Die StringListe wurde hier noch nicht wieder freigegeben (sl.free), da man damit
ja noch arbeiten will.
(D)
Bei dem letzten Beispiel wirds wieder interessant, denn jetzt arbeiten wir mit
Dialogen, d.h. Eingabemasken. Dazu müssen wir uns zunächst einen leeren
Dialog (Formular) erstellen (unter Datei|Neues Formular). Um es nicht zu umständlich
zu machen, behalte ich den Namen "Form1" bei. Ihr könnt jetzt alle möglichen
Eingabefelder, Grafiken oder sonstiges einfügen. Um den Dialog innerhalb
des PlugIns zu starten gibt es folgendes Beispiel:
Procedure Beispiel4;
Var Dialog : TForm1;
Begin
Dialog:=TForm1.Create(nil);
Dialog.ShowModal;
Dialog.Free;
End;
Wie man sieht, ist es recht einfach: Die Variable "Dialog" ist vom Typ TForm1
(da ich Form1 als Namen beibehalten habe). Der Dialog wird anschließend
dynamisch erzeugt und mit ShowModal aufgerufen. Der Dialog bleibt solange aktiv,
bis er geschlossen wird! (=> Modaler Dialog). Hinterher wird er mit .free wieder
aus dem Speicher entfernt.
Stand: 10. August 2001