Im zweiten Teil unseres Beitrags zur Kommunikation zwischen zwei unterschiedlichen CONZEPT 16-Anwendungen beschäftigen wir uns mit der Vermittlungs-DLL, welche die Schnittstelle zwischen den Prozeduren der Quell- und der Zieldatenbank bildet. Dabei ist diese DLL vollkommen unabhängig von der Struktur der beteiligten Applikationen und kann somit universell verwendet werden.
Funktionsweise
Die Vermittlungs-DLL wird von der Quelldatenbank (Applikation) per DllCall() angesprochen. Die DLL selbst ruft dann Prozeduren in der Zieldatenbank auf und benutzt dabei die Programmierschnittstelle C16_API (c16_pgxw.dll bzw. c16_pgxe.dll), in der alle dazu notwendigen Funktionen vorhanden sind. Die DLL ist in C++ geschrieben und besteht aus recht wenig Quelltext. Bei späteren Erweiterungen des Funktionsumfangs bleibt die DLL unverändert, die notwendigen Modifikationen betreffen nur die Prozeduren in der Quell- und Zieldatenbank. Außerdem soll die DLL auch den parallelen Zugriff auf mehrere Zieldatenbanken erlauben.
Wie schon im ersten Teil dargestellt, beinhaltet die Ausführung einer Remote-Prozedur den mehrfachen Aufruf der DLL. Dabei werden zunächst die Argumente definiert, dann die eigentliche Funktion gestartet und am Ende die Resultate abgeholt. Für die Implementation verwenden wir eine Klasse, von der pro Instanz (Datenbankverbindung) ein Objekt generiert wird. Dieses Objekt übernimmt dann auch die Übergabe von Argumenten und Resultaten.
Eine Besonderheit der DLL ist die Verwendung einer Callback-Funktion, über die die Prozeduren der Zieldatenbank Zugriff auf die Argumente und Resultate erhalten. Mit dieser Methode kann die DLL unabhängig von den Funktionen der Zieldatenbank bleiben, da es keine direkte Übergabe von Argumenten beim Prozeduraufruf in der Zieldatenbank gibt. Statt dessen holt sich die Prozedur die Daten per DllCall()
auf die Callback-Funktion der DLL.
Ein Beispiel für den Vorteil des Callback-Verfahrens ist die Übergabe eines Memory-Objekts. In diesem Fall wird die Größe und die Basisadresse an die DLL übergeben und im Instanz-Objekt zwischengespeichert. Die Zielprozedur holt sich dann per Callback-Aufruf zunächst die Größe und legt selbst ein entsprechendes Memory-Objekt an. Über einen zweiten Callback-Aufruf kann dann die DLL den Inhalt des Memory-Objekts der Quelldatenbank in das Memory-Objekt der Zieldatenbank kopieren.
Hier nochmals das Aufrufschema:
Einstiegsfunktion
Analog zur Quelldatenbank muss die DLL folgende Funktionen (via DllCall) zur Verfügung stellen:
- Initialisieren und Terminieren
- Öffnen und Schließen der Zieldatenbank
- Ausführen einer Funktion in der Zieldatenbank
- Definieren von Argumenten
- Aufrufen einer Funktion
- Abholen des Resultats
Hier ein Beispiel für die Einstiegsfunktion der DLL:
vERROR WINAPI C16_APP_MF ( vC16_CCB* aCCB )
{
// Argumente prüfen
aCCB->C16_ArgCount(aCCB->InstHdl,&tArgCount);
if (tArgCount < 1)
return(sErrArguments);
aCCB->C16_ArgInfo(aCCB->InstHdl,1,&tCmdType,NULL,NULL,NULL,NULL);
if (tCmdType != _TypeInt)
return(sErrArguments);
// Kommandowert lesen
aCCB->C16_ArgRead(aCCB->InstHdl,1,0,&tCmdNumber);
switch (tCmdNumber)
{
// globale Initialisierung
case sDllFuncInitGlobal:
{
tError # C16_InitPgif(MEM_256M,&gPgxModule);
break;
}
// Initialisierung einer Instanz
case sDllFuncInitInstance:
{
cAppInstance* tInst = new cAppInstance;
aCCB->C16_ArgRead(aCCB->InstHdl,2,250,tInst->DllPath);
aCCB->C16_ArgWrite(aCCB->InstHdl,3,0,(vLONG)tInst); // 32-Bit
break;
}
Das Öffnen der Zieldatenbank:
// mit der Zieldatenbank verbinden
case sDllFuncOpenDatabase:
{
aCCB->C16_ArgRead(aCCB->InstHdl,2,0,&tInst);
// Instanz der PGX anlegen
tError = C16_InitInstance(gPgxModule,&tInst->PgxInst);
if (tError == ERR_OK)
{
// Datenbank öffnen
tError = C16_OpenArea(tInst->PgxInst, . . . );
if (tError == ERR_OK)
{
// Prozedurumgebung initialisieren
C16_ProcArgument(tInst->PgxInst,_TypeAlpha,tInst->DllPath,false,0);
C16_ProcArgument(tInst->PgxInst,_TypeInt,(vLONG*)&tInst,false,0);
tError = C16_ProcCall(tInst->PgxInst,‘ImplementInit‘);
if (tError == ERR_OK)
{
// Deskriptor des Datenbereichs ermitteln
tError = C16_ProcResult(tInst->PgxInst,&tTypeInfo,&tValPtr);
if (tError == ERR_OK && tTypeInfo == _TypeInt)
tInst->ProcData = *tValPtr;
}
}
}
break;
}
Definition der Argumente und Aufruf der Zielprozedur:
// Funktionsargument definieren
case sDllFuncProcDefArg:
{
aCCB->C16_ArgRead(aCCB->InstHdl,2,0,(vLONG*)&tInst);
tError = tInst->ProcDefArg(aCCB);
break;
}
// Prozedur starten
case sDllFuncProcCall:
{
aCCB->C16_ArgRead(aCCB->InstHdl,2,0,(vLONG*)&tInst);
tError = tInst->ProcCall(aCCB);
break;
}
}
return(tError);
}
Im nächsten Teil betrachten wir die Prozeduren in der Zieldatenbank.