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