Announcement

Collapse
No announcement yet.

DLL mit Datenanbindung

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • DLL mit Datenanbindung

    Hallo,

    ich möchte in einer DLL eine Databaseverbindung nutzen, die in meinem Hauptprogramm bereits existiert. Das DataBase Objekt sowie das Session Objekt liegen in einem separaten Datenmodul. Ich übergebe jetzt das Datenmodul als Pointer an die DLL. Innerhalb der DLL habe ich eine Query die ich mit dem Database Objekt und der Session verbinden möchte. Leider erhalte ich innerhalb der DDL immer eine Fehlermeldung über eine Zugriffsverletzung, das die Session ungekannt ist. Anbei ein Codeauszug:

    Aufruf der DLL durch einen Button in Hauptprogramm: <BR>
    <BR>
    procedure TForm1.Button1Click(Sender: TObject);<BR>
    var b,s : Pointer;<BR>
    begin <BR>
    b := @DM_LoginMain.DataBaseEcovivon; <BR>
    s := @DM_LoginMain.SDSessionEcovision; <BR>
    start2_dll(b,s); <BR>
    end; <BR>
    <BR>
    procedure start2.dll(b,s : Pointer); <BR>
    type <BR>
    Tpa = procedure(b,s : Pointer); <BR>
    var <BR>
    pa : Tpa; <BR>
    Handle : THandle; <BR>
    begin <BR>
    Handle := LoadLibrary('Verzeichnis'); <BR>
    if Handle <> 0 then begin <BR>
    if @pa <> nil then begin <BR>
    pa(b,s); <BR>
    end; <BR>
    FreeLibrary(Handle); <BR>
    end; <BR>
    end; <BR>
    <BR>
    Procedure in Dll :<BR>

    procedure start2(b,s : Pointer); <BR>
    var <BR>
    newQuery : TSDQuery; <BR>
    begin <BR>
    newQuery := TSDQuery.Create(nil); <BR>
    newQuery.DataBaseName := TSDDataBase(b^).DataBaseName; <BR>
    newQuery.SessionName := TSDSession(s^).SessionName; <BR>
    newQuery.SQL.Clear; <BR>
    newQuery.SQL.Add('SELECT * FROM TABLENAME'); <BR>
    newQuery.Prepare; <BR>
    newQuery.Open; <BR>
    end; <BR>

    In der Zeile newQuery.Prepare bleibt wird mit einer Fehlermeldung die DLL abgebrochen : Exception der Klasse EDatabaseError. 'Invalid Session name S_Ecovision. <BR>
    <BR>
    Der Sessionname ist der korrekte. Wer kann mir diesbezüglich einen Hinweis geben was ich falsch mache!?

    Gruß Frank

  • #2
    Hallo,

    Borland dokumentiert in der Hilfedatei BDE32.HLP verschiedene Sonderfälle für den Zugriff auf die BDE aus einer DLL heraus. Das folgende Beispiel für eine Umsetzung dieser "Ratschläge" stammt aus meinem Buch <i>Client/Server Datenbankentwicklung mit Delphi</i>:

    a) BDEInit.pas
    <pre>
    { ************************************************** **************
    Typ : Unit
    Autor : Andreas Kosch
    Compiler : Delphi 4.02 CSS
    Betriebssystem : Windows 98
    Beschreibung : BDE in einer DLL verwenden
    ************************************************** ************** }

    unit BDEInit;

    interface

    uses
    Forms, BDE, DBTables;

    // DLL-Schnittstellenprozedur
    procedure InitDLLVCL(App: TApplication;
    Scr: TScreen;
    Ssn: TSession;
    Slt: TSessionList); stdcall;

    // DLL-interne Verwendung
    procedure DLLExitProc;

    implementation

    var
    gSaveApp : TApplication = nil;
    gSaveScr : TScreen = nil;
    gSaveSsn : TSession = nil;
    gSaveSlt : TSessionList = nil;

    procedure InitDLLVCL(App: TApplication;
    Scr: TScreen;
    Ssn: TSession;
    Slt: TSessionList); stdcall;
    begin
    if Assigned(App) and (App <> Application) then
    Application := App;
    if Assigned(Scr) and (Scr <> Screen) then
    Screen := Scr;
    if Assigned(gSaveSsn) and (gSaveSsn <> Session) then
    Session := Ssn;
    if Assigned(gSaveSlt) and (gSaveSlt <> Sessions) then
    Sessions := Slt;
    end;

    procedure DLLInitProc;
    begin
    // Session.Handle initialisieren
    Check(DbiSetCurrSession(Session.Handle));
    gSaveApp := Application;
    gSaveScr := Screen;
    gSaveSsn := Session;
    gSaveSlt := Sessions;
    end;

    procedure DLLExitProc;
    begin
    if Assigned(gSaveApp) and (gSaveApp <> Application) then
    Application := gSaveApp;
    if Assigned(gSaveScr) and (gSaveScr <> Screen) then
    Screen := gSaveScr;
    if Assigned(gSaveSsn) and (gSaveSsn <> Session) then
    Session := gSaveSsn;
    if Assigned(gSaveSlt) and (gSaveSlt <> Sessions) then
    Sessions := gSaveSlt;
    gSaveApp := nil;
    gSaveScr := nil;
    gSaveSsn := nil;
    gSaveSlt := nil;
    // originales Session.Handle wiederherstellen
    Check(DbiSetCurrSession(Session.Handle));
    end;

    initialization
    DLLInitProc;

    finalization
    DLLExitProc;

    end.
    </pre>
    b) BDEDLL.dpr
    <pre>
    { ************************************************** **************
    Typ : DLL-Projektdatei
    Autor : Andreas Kosch
    Compiler : Delphi 4.02 CSS
    Betriebssystem : Windows 98
    Beschreibung : BDE in einer DLL verwenden
    ************************************************** ************** }

    library BDEDLL;

    uses
    ShareMem, Windows, Classes,
    BDEDLL_Frm in 'BDEDLL_Frm.pas' {FormInDLL},
    BDEInit in 'BDEInit.pas';

    {$R *.RES}

    procedure LibraryProc(aReason: Integer);
    begin
    case aReason of
    DLL_Process_Detach : DLLExitProc;
    DLL_Thread_Attach : {NOP};
    DLL_Thread_Detach : {NOP};
    end;
    end;

    exports
    // DLL-Schnittstellenprozeduren für die EXE exportieren
    InitDLLVCL name 'InitDLLVCL',
    ShowDLLForm name 'ShowDLLForm';

    begin
    // Unit-System -> Variable »DLLProc« initialisieren
    DllProc := @LibraryProc;
    end.
    </pre>
    c) Formular in DLL:
    <pre>
    { ************************************************** **************
    Typ : Hauptformular
    Autor : Andreas Kosch
    Compiler : Delphi 4.02 CSS
    Betriebssystem : Windows 98
    Beschreibung : BDE in einer DLL verwenden
    ************************************************** ************** }

    unit BDEDLL_Frm;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls,
    Forms, Dialogs, BDE, DBTables, Grids, DBGrids, Db;

    type
    TFormInDLL = class(TForm)
    DataSourceDLL: TDataSource;
    TableDLL: TTable;
    DBGrid1: TDBGrid;
    DatabaseDLL: TDatabase;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    private
    { Private declarations }
    public
    { Public declarations }
    end;

    var
    FormInDLL: TFormInDLL;

    procedure ShowDLLForm(Sender: TComponent); stdcall;

    implementation

    {$R *.DFM}

    {
    DLL-Schnittstellenprozedur
    }

    procedure ShowDLLForm(Sender: TComponent); stdcall;
    var
    aSaveCursor : TCursor;
    begin
    aSaveCursor := Screen.Cursor;
    Screen.Cursor := crHourGlass;
    try
    Application.Processmessages;
    with TFormInDLL.Create(Sender) do
    Show;
    finally
    Screen.Cursor := aSaveCursor;
    Application.Processmessages;
    end;
    end;

    {
    Die Datenzugriffskomponenten dürfen zur Entwicklungszeit
    nicht aktiv sein!
    }

    procedure TFormInDLL.FormCreate(Sender: TObject);
    begin
    with TableDLL do
    begin
    DataBaseName := Session.DataBases[0].Databasename;
    Open;
    end;
    end;

    procedure TFormInDLL.FormDestroy(Sender: TObject);
    begin
    with TableDLL do
    if Active then
    Close;
    end;

    procedure TFormInDLL.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
    TableDLL.Close;
    Action := caFree;
    end;

    end.
    </pre>
    d) Aufruf im Programm
    <pre>
    { ************************************************** **************
    Typ : Hauptformular
    Autor : Andreas Kosch
    Compiler : Delphi 4.02 CSS
    Betriebssystem : Windows 98
    Beschreibung : Testprogramm für BDEDLL.DLL
    ************************************************** ************** }

    unit BDEDLLTestFrm;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, mmsystem,
    Controls, Forms, Dialogs, BDE, Dbtables, Menus, StdCtrls, ComCtrls;

    type
    TInitDLLVCL = procedure(App: TApplication;
    Scr: TScreen;
    Sesn: TSession;
    Slst: TSessionList); stdcall;

    TShowDLLForm = procedure(Sender: TObject); stdcall;

    TFormMain = class(TForm)
    Database1: TDatabase;
    ButtonShow: TButton;
    StatBar: TStatusBar;
    procedure Exit1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ButtonShowClick(Sender: TObject);
    private
    { Private declarations }
    FDLLHandle : THandle;
    FShowDLLForm : TShowDLLForm;
    procedure LoadDll;
    procedure UnloadDLL;
    protected
    { Protected declarations }
    public
    { Public declarations }
    end;

    var
    FormMain: TFormMain;

    implementation

    {$R *.DFM}

    const
    BDE_DLL = 'BDEDLL.DLL';

    procedure TFormMain.FormCreate(Sender: TObject);
    begin
    // EXE muß als Erster auf die BDE zugreifen -> Session aus EXE öffnen
    Database1.Connected := True;
    // DLL laden + VCL-Objekte in der DLL initialisieren
    LoadDll;
    end;

    procedure TFormMain.FormDestroy(Sender: TObject);
    begin
    // Schritt 1: DLL entladen
    UnloadDLL;
    // Schritt 2: Session-Handle wieder auf die EXE-Verbindung setzen
    Check(DbiSetCurrSession(Session.Handle));
    // die zuerst aufgebaute Verbindung wird als letzte abgebaut
    Database1.Connected := false;
    end;

    {
    Nichtmodalen Dialog aus der DLL aufrufen, dieser zeigt den
    Inhalt einer Tabelle aus der Delphi-Beispieldatenbank DBDEMOS
    an. Vor dem Programmende muß dieses nichtmodales Formular
    wieder geschlossen werden.
    }

    procedure TFormMain.ButtonShowClick(Sender: TObject);
    begin
    FShowDLLForm(Self);
    end;

    procedure TFormMain.Exit1Click(Sender: TObject);
    begin
    Close;
    end;

    {---------------------------------------------------------}
    { Private Methoden }
    {---------------------------------------------------------}

    procedure TFormMain.LoadDll;
    var
    aDLLProc: TInitDLLVCL;
    begin
    if (FDLLHandle = 0) then
    begin
    FDLLHandle := LoadLibrary(BDE_DLL);
    if (FDLLHandle <> 0) then
    begin
    @aDLLProc := GetProcAddress(fDLLHandle,'DLLProcEx');
    if Assigned(aDLLProc) then
    aDLLProc(Application, Screen, Session, Sessions);
    @FShowDLLForm := GetProcAddress(fDLLHandle,'ShowDLLForm');
    if Assigned(FShowDLLForm) then
    ButtonShow.Enabled := True;
    end;
    end;
    end;

    procedure TFormMain.UnloadDLL;
    begin
    if (FDLLHandle <> 0) then
    begin
    FreeLibrary(FDLLHandle);
    Check(DbiSetCurrSession(Session.Handle));
    FDLLHandle := 0;
    end;
    end;

    end.
    </pre&gt

    Comment


    • #3
      Hallo Andreas,

      vielen Dank für Deine Hilfe. Ich habe das Programm testweise nachgebaut. Es läuft auch soweit. Es gibt nur einen Nachteil : Ich erhalte ein zweites Login was ich auf keinen Fall gebrauchen kann. Leider waren meine Informationen nicht ganz vollständig : Ich arbeite nicht mit der BDE sondern mit der Komponente SQLDirect mit der die Möglichkeit besteht auf bis zu 8 Datenbanken zuzugreifen.

      Ein weiteres Problem besteht darin, das ich nicht weiss was passiert, wenn sich jemand bis zu 20 mal mit dem gleichen Usernamen einloggt. Ich benötige die DLL für 'Formulare'. In dem Projekt kann es bis zu 40 'Formuare' geben die über DLL's ausgeführt werden sollen. (Ich habe mich für externe DLL's entschieden, da die Formulare häufiger geändert werden müssen und an den Kunden gehen) Da jedes Formular unabhängig von einem anderen Formular aufgerufen werden kann, würde der Anwender sich bis zu 40 mal (theoretischer Maximalwert) auf die Datenbank einloggen. Ich habe leider keine Erfahrung ob das bei den Datenbanken zu Problemen führen kann. Ich kann das aus Zeitgründen auch nicht bei allen möglichen Datenbanken testen.
      Aus diesem Grund möchte ich gern die im Hauptprogramm bestehende Datenverbindung nutzen. Leider habe ich trotz mehrtägiger Versuche hier eine Lösung zu finden, bisher keinen Erfolg gehabt. Ich habe auch schon überlegt keine DLL zu verwenden, sondern eine eigenständige EXE. Ich denke jedoch das hier das Problem auf einer ähnlichen Ebene liegen wird, da ich in dieser EXE ebenfalls die Datenverbindung aus dem Hauptprogramm nutzen möchte.

      Wenn jemand noch einen Tip hat, würde ich mich freuen.

      Gruß Fran

      Comment


      • #4
        Hallo,

        in diesem Fall ist es üblich, dass die Anwendung die <i>Three-tier-Architektur</i> verwendet, d.h. alle Datenbankzugriffe laufen nur über ein gemein genutztes Datenbankobjekt (COM-Objekt). Angenommen, das Programm würde mit ADO (ADOExpress alias dbGo) arbeiten, dann könnte das so aussehen:

        a) Das eigene mit Delphi geschriebene Datenbankobjekt greift über das Connection- und Command-Objekt (ADO) auf die Datenbank zu und liefert eine Recordset-Objektinstanz (ADO) an den Aufrufer zurück. Dieses Recordset ist "offline", d.h. nicht mehr mit der Datenbank verbunden.

        b) Das Programm arbeitet im Offline-Betrieb mit dem Recordset (Daten einfügen, ändern, löschen), ohne eine Datenbankverbindung zu haben und ohne überhaupt zu wissen, aus welcher Datenbank diese Datenmenge stammt.

        c) Das Programm übergibt das geänderte Recordset-Objekt an das Datenbankobjekt, dieses verbindet das Recordset wieder mit der Datenbank und fordert das Recordset auf, alle Änderungen am Datenbestand über automatisch generierte SQL-Anweisungen einzuarbeiten.

        Da ADO auf einen OLE DB-Provider für den Datenbankzugriff zurückgreift, wirkt im Hintergrund ein automatischer Datenbankverbindungs-Pool, d.h. alle Objektinstanzen nutzen implizit die gleiche Datenbankverbindung, wobei die Poolgröße zur Laufzeit sich dynamisch anpasst.

        Bei ADO steht dieser "Offline-Modus" (Three-tier-Modus) optional zur Verfügung, ab ADO.NET ist das der einzig unterstützte Weg, wenn ein Schreibzugriff auf die Datenbankdaten benötigt wird.

        P.S: Da alle Zugriffe über COM (Component Object Model) laufen, ist der <i>Marshaler</i> des Betriebssystem dafür zuständig, im Hintergrund automatisch alle Anpassungen vorzunehmen, wenn Thread- oder Prozess-Grenzen überwunden werden müssen

        Comment


        • #5
          @Frank Speelmans:

          Gibt es inzwischen eine Lösung für das beschriebene Problem?
          Wenn ja, wie sieht das aus.

          Gruß
          Gunna

          Comment


          • #6
            Zur allgemeinen Information!

            Das Probelem läßt sich wie folgt lösen:
            IBX raus und FIBPlus rein.
            Nach CONNECT zur DB im Hauptprogramm wirk das Handel der Database der DLL übergeben und dort der DB zugewiesen.
            Und schon läuft's. Auch das Entladen der Library funktioniert korrekt.

            Gunna

            Comment

            Working...
            X