Programmierung

Remote Procedure Call (Teil 1)

Mittlerweile ist es gang und gäbe in der Informationstechnik, Daten zwischen Applikationen auszutauschen und zu verarbeiten. Auch das Aufrufen von Funktionen aus anderen Applikationen, das sogenannte Remote Procedure Call (RPC), ist nichts Neues mehr. Wie das Ganze innerhalb von CONZEPT 16 realisiert werden kann, möchte ich Ihnen in diesem Artikel vorstellen.


Zielsetzung

Der Zugriff auf die Daten einer anderen CONZEPT 16-Datenbank ist per DbaConnect() zwar leicht zu realisieren, der Aufruf von Prozeduren in der Zieldatenbank – und somit auch die Nutzung von Dialogen – ist über diesen Weg jedoch nicht machbar. Ein Prozeduraufruf von “außen” kann jedoch über die Windows-Programmierschnittstelle (API) erfolgen. Da die API nicht direkt aus einer Prozedur der Quelldatenbank aufrufbar ist, benötigen wir zusätzlich eine kleine DLL als Vermittler, die per DllCall() aufgerufen wird und die dann die API anspricht. Der Umgang mit DllCall() wird übrigens in dem Blogartikel “Hast du Töne…!?” erläutert.

Programmstruktur

Die Programmstruktur ist grob in drei Bereiche aufzuteilen:

  • Quelldatenbank
  • Vermittlungs-DLL
  • Zieldatenbank
Anbindung einer Datenbank per DLL
Die Quelldatenbank (Applikation)

In der Quelldatenbank befindet sich die bestehende Anwendung. Aus dieser Anwendung heraus soll über die Vermittlungs-DLL auf die Zieldatenbank zugegriffen werden. Um mit der DLL kommunizieren zu können, wird eine Prozedur erstellt, welche die Funktionen der DLL-Schnittstelle verwendet. Die Funktionen in dieser Prozedur geben die auszuführenden Befehle per DllCall() an die DLL weiter.

Um einen Zugriff auf eine andere Datenbank und dessen Funktionen zu realisieren, werden folgende Funktionen in der Prozedur benötigt:

  • Initialisieren und Terminieren der Vermittlungs-DLL
  • Öffnen und Schließen der Zieldatenbank
  • Ausführen einer Funktion in der Zieldatenbank
    • Definieren von Argumenten
    • Aufrufen einer Funktion
    • Abholen des Resultats
Initialisieren und Terminieren

Die erste Funktion lädt die Vermittlungs-DLL und ruft dort die globale Initialisierung der DLL auf. Am anderen Ende wird die Terminierungsfunktion der DLL ausgeführt und anschließend die DLL entladen.

sub InterfaceInit ( aPath : alpha(250); ) : handle;
{
  // DLL laden
  tDllHdl # DllLoad(aPath + DllFileName,sDllCallName));
  if (tDllHdl < 0)
    return(tDllHdl);

  // DLL initalisieren
  tError # tDllHdl->DllCall(sDllFuncGlobalInit);
  if (tError < 0)
  {
    DllUnload(tDllHdl);
    return(tError);
  }
  return(tDllHdl);
}

sub InterfaceTerm ( aDllHdl : handle ) : int;
{
  // DLL terminieren
  tError # aDllHdl->DllCall(sDllFuncGlobalTerm);
  if (tError < 0)
    return(tError);

  // DLL entladen
  DllUnload(aDllHdl);
  return(_rOK)
}
Öffnen & Schließen der Zieldatenbank

Beim Öffnen wird zuerst eine Verbindungsinstanz (= Datenbereich) in der DLL angelegt, innerhalb deren dann die Verbindung zur Datenbank hergestellt wird. Beim Schließen wird die Verbindung beendet und die Instanz gelöscht.

global InterfaceData
{
  DllHdl		: handle; // Deskriptor der DLL
  DllData		: int32;  // Zeiger auf die Verbindungsdaten
}

sub InterfaceOpen
( aDllHdl : handle; aDllPath : alpha(250); aServer : alpha ... ) : handle
{
  tHdl # VarAllocate(InterfaceData);
  DllHdl # aDllHdl;
  tError # DllHdl->DllCall(sDllFuncInitInstance,aDllPath,VAR DllData);
  if (tError = _rOK)
  {
    tError # DllHdl->DllCall(sDllFuncOpenDatabase,DllData,aServer, ... );
    if (tError != _rOK)
      DllHdl->DllCall(sDllFuncTermInstance,DllData);
  }
  if (tError != _rOK)
  {
    VarFree(InterfaceData);
    return(tError);
  }
  return(tHdl);
}

sub InterfaceClose ( aDataHdl : handle );
{
  VarInstance(InterfaceData,aDataHdl);
  DllHdl->DllCall(sDllFuncCloseDatabase,DllData);
  DllHdl->DllCall(sDllFuncTermInstance,DllData);
  VarFree(InterfaceData);
}
Hilfsfunktionen

Damit bei einer Erweiterung des Funktionsumfangs die Vermittlungs-DLL nicht geändert werden muss, erfolgt die Übertragung der Funktionsargumente nicht innerhalb eines einzigen DllCall()-Aufrufs. Stattdessen werden die Argumente einzeln an die DLL übertragen und dort zwischengespeichert. Zudem lassen sich mit diesem Verfahren auch Datenobjekte übertragen, die in der DLL selbst unbekannt sind, beispielsweise Memory-Objekte. Zu diesem Zweck gibt es eine Reihe von Hilfsfunktionen, die die Argumentübertragung erledigen.


sub InterfaceProcInit ( aDataHdl : handle )
{
  VarInstance(InterfaceData,aDataHdl);
  DllHdl->DllCall(sDllFuncProcInit,DllData); // Reset der Argumente
}

sub InterfaceAddArgumentInt32 ( aArgData : int32 )
{
  DllHdl->DllCall(sDllFuncProcDefArg,DllData,_TypeInt,aArgData);
}

sub InterfaceAddArgumentMemObj ( aArgData : handle )
{
  DllHdl->DllCall(sDllFuncProcDefArg,DllData,sTypeMemObj,aArgData->spBaseAddress);
}

sub InterfaceProcCall ( aProcName : alpha ) : int;
{
  return(DllHdl->DllCall(sDllFuncProcCall,DllData,aProcName));
}

sub InterfaceGetResultInt32 ( VAR aResData : int32 )
{
  DllHdl->DllCall(sDllFuncProcGetResult,DllData,_TypeInt,VAR aResData);
}
Aufruf einer Funktion

Beim Aufruf einer Zielfunktion werden unter Verwendung der Hilfsfunktionen zunächst die Argumente einzeln an die DLL übertragen. Danach erfolgen der Aufruf der Zielfunktion und gegebenenfalls das Abholen eines Funktionsresultats.

sub InterfaceStorePicture
(
  aDataHdl 	: handle;
  aPicName 	: alpha;
  aPicData 	: handle
  VAR aPicStoreSize : int32;    // Ergebnis
)
: int;   // Fehlercode
{
  // Argumente übergeben
  InterfaceProcInit(aDataHdl);
  InterfaceAddArgumentAlpha(aPicName);
  InterfaceAddArgumentInt32(aPicData->spSize);
  InterfaceAddArgumentMemObj(aPicData);

  // Funktion aufrufen
  tError # InterfaceProcCall(sDllProcNameStorePicture);

  // Resultat abholen
  InterfaceGetResultInt32(VAR aPicStoreSize);
  return(tError);
}

Im nächsten Teil sehen wir uns dann die Vermittlungs-DLL näher an.

Keine Kommentare

Kommentar abgeben