Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Interface zu dynamischer DLL mit Callback (https://www.delphipraxis.net/208253-interface-zu-dynamischer-dll-mit-callback.html)

norwegen60 4. Jul 2021 14:48

Interface zu dynamischer DLL mit Callback
 
Hallo,

ich möchte ein Callback zusammen mit einer dynamisch geladenen DLL behandeln und habe folgenden Test-Code
Delphi-Quellcode:
unit u_IntDll;

interface

uses
  Winapi.Windows;

type
  TCallbackProcedure = procedure(Msg: string); stdcall;
  TSetCallbackProc = procedure(ACallbackProc: TCallbackProcedure); stdcall;
  TEnableDllTimer = procedure(AEnabled: boolean); stdcall;

  TMyMsgEvent = procedure(Msg: string) of object;

  TintDll = class
  private
    FDLLHandle: THandle;
    FOnMyMsg: TMyMsgEvent;

    MyCallbackProc: TSetCallbackProc;
    MyEnableDllTimer: TEnableDllTimer;

    procedure ShowDLLMessage(sMsg:String); stdcall;
  public
    constructor Create;
    destructor Destroy; override;

    property OnMyMsg: TMyMsgEvent read FOnMyMsg write FOnMyMsg;
    procedure Init;
    procedure EnableDllTimer(bValue: boolean);
  end;

procedure ShowDLLMessageExt(sMsg: string); stdcall;

var
  intDll: TintDll;


implementation

procedure ShowDLLMessageExt(sMsg: string); stdcall;
begin
  if assigned(intDll.OnMyMsg) then
    intDll.OnMyMsg(sMsg);
end;

{ TintDll }

constructor TintDll.Create;
begin

end;

destructor TintDll.Destroy;
begin
  FreeLibrary(FDLLHandle);

  inherited;
end;

procedure TintDll.EnableDllTimer(bValue: boolean);
begin
  MyEnableDllTimer(bValue);
end;

procedure TintDll.Init;
begin
  FDLLHandle := LoadLibrary('Callback.dll');
  if FDLLHandle <> 0 then
  begin
    @MyEnableDllTimer := GetProcAddress(FDLLHandle, 'EnableDllTimer');

    @MyCallbackProc := GetProcAddress(FDLLHandle, 'CallbackProc');
    MyCallbackProc(ShowDLLMessageExt);
  end;
end;

procedure TintDll.ShowDLLMessage(sMsg: String);
begin
  if assigned(intDll.OnMyMsg) then
    intDll.OnMyMsg(sMsg);
end;

end.
Weise ich im Init-Part
Delphi-Quellcode:
MyCallbackProc(ShowDLLMessageExt);
zu, funktioniert das Ganze einwandfrei.
Möchte ich aber alles innerhalb der Klasse behandeln und weise
Delphi-Quellcode:
MyCallbackProc(ShowDLLMessage);
zu, bekomme ich die Meldung "Inkompatible Typen: Reguläre Procedure und Methodenzeiger"
Mir ist nicht ganz klar, was der Unterschied zwischen dem Handling von ShowDLLMessageExt und ShowDLLMessage ist. Was muss ich machen, dass ich auch den Callback innnerhalb der Klasse behandeln kann?

Grüße
Gerd

TurboMagic 4. Jul 2021 17:00

AW: Interface zu dynamischer DLL mit Callback
 
Dein Problem ist, dass eine Prozedur und eine Methode trotz gleichem Schlüsselwort
und ähnlicher Semantik doch technisch unterschiedlich sind.

Warum? Bei einer normalen Prozedur Referenz gibt es einen Zeiger. Den auf den Startpunkt
des Codes der Prozedur im Speicher.

Bei einer Methode gibt es zwei Zeiger:
Den auf den Startpunkt des Codes der Methode im Speicher und einen auf die Objektinstanz
zu der die Methode gehört, damit man von dort aus auch an die Daten des Objektes ran kommt.

Grüße
TurboMagic

brechi 4. Jul 2021 17:22

AW: Interface zu dynamischer DLL mit Callback
 
Du musst das Objekt auch übergeben (vereinfacht) und nimm keinen string:


Delphi-Quellcode:
TCallbackProcedure = procedure(obj: Tobject; Msg: widestring); stdcall;

procedure ShowDLLMessageExt(obj: TObject; sMsg: widestring); stdcall;
begin
  TintDll(obj).OnMyMsg(sMsg);
end;

...

MyCallbackProc(self, @ShowDLLMessageExt)

norwegen60 4. Jul 2021 19:37

AW: Interface zu dynamischer DLL mit Callback
 
Das grundsätzliche Problem habe ich verstanden. Ich weiß aber immer noch nicht wie ich es lösen kann. Die Zuweisung auf ShowDLLMessageExt funktioniert jetzt schon korrekt. Ich suche, wie ich auf das klasseneigene ShowDLLMessage verweisen kann.

Delphi-Quellcode:
procedure TintDll.Init;
begin
  FDLLHandle := LoadLibrary('Callback.dll');
  if FDLLHandle <> 0 then
  begin
    @MyEnableDllTimer := GetProcAddress(FDLLHandle, 'EnableDllTimer');

    @MyCallbackProc := GetProcAddress(FDLLHandle, 'CallbackProc');
    MyCallbackProc(ShowDLLMessage);
  end;
end;

procedure TintDll.ShowDLLMessage(sMsg: String);
begin
  if assigned(intDll.OnMyMsg) then
    intDll.OnMyMsg(sMsg);
end;
Hätte jetzt erhofft, dass es mit
Delphi-Quellcode:
MyCallbackProc(self, ShowDLLMessage);
aber dem ist nicht so.

Standardmässig verwende ich eigentlich Widestring

jaenicke 4. Jul 2021 22:20

AW: Interface zu dynamischer DLL mit Callback
 
Wenn du beide Seiten frei gestalten kannst, kannst du auch schlicht mit Interfaces arbeiten. Wenn du an die DLL z.B. ein Interface IUserDialog übergibst, kann dieses die Prozedur ShowDLLMessage enthalten. Dann kannst du die einfach und ohne Tricks aus der DLL aufrufen.

norwegen60 5. Jul 2021 13:12

AW: Interface zu dynamischer DLL mit Callback
 
Zitat:

Zitat von jaenicke (Beitrag 1491849)
Wenn du beide Seiten frei gestalten kannst, kannst du auch schlicht mit Interfaces arbeiten. Wenn du an die DLL z.B. ein Interface IUserDialog übergibst, kann dieses die Prozedur ShowDLLMessage enthalten. Dann kannst du die einfach und ohne Tricks aus der DLL aufrufen.

Ich habe schon mal eine Klasse an eine DLL übergeben, auch wenn hier im Forum davor vielfach gewarnt wird. Läuft aber zuverlässig so lange man sicher stellt, dass bei einer Änderung der Klasse beide Seiten kompiliert werden.

Mit Interfaces habe ich noch nie gearbeitet. Müsste schauen wie groß der Umstellungsaufwand ist. Wenn ich an mein Projekt mit der Klasse denke, da waren die Vereinfachungen enorm.

Trotzdem die Frage: Gibt es eine Möglichkeit MyCallbackProc mit der klasseneigenen Procedure zu verbinden?

Incocnito 5. Jul 2021 15:39

AW: Interface zu dynamischer DLL mit Callback
 
Nur, wenn du "CallbackProc" in der "Callback.dll" ändern kannst,
da wie die anderen versucht haben zu erklären
Delphi-Quellcode:
procedure ShowDLLMessageExt(sMsg: string); stdcall;

etwas anderes ist als
Delphi-Quellcode:
procedure TintDll.ShowDLLMessage(sMsg: String);

sowie (um das mal zu verdeutlichen)
Delphi-Quellcode:
procedure TintDll.ShowDLLMessage(sMsg: String);

etwas anderes ist als
Delphi-Quellcode:
procedure TintDll.ShowDLLMessage2(iWert: Integer);

Hier kommt es nicht auf den Namen an, sondern auf die Parameter und auf den Aufruf-Typ.
Es wird definiert, dass CallbackProc vom Typ
Delphi-Quellcode:
TCallbackProcedure = procedure(Msg: string); stdcall;

ist.
TintDll.ShowDLLMessage ist aber vom Typ
Delphi-Quellcode:
TCallbackProcedure2 = procedure(Msg: string) of object; stdcall;

(ich weiß nur nicht, ob "of object" und "stdcall" zusammen funktionieren und
überhaubt bei DLL-Proceduren als Parameter verwendet werden können)
Es geht mir nur darum zu zeigen, wie sich der Typ unterscheidet.

Ich hoffe das hilft beim Verständnis.

LG Incocnito

brechi 5. Jul 2021 17:53

AW: Interface zu dynamischer DLL mit Callback
 
Delphi-Quellcode:
  TCallbackProcedure = procedure(Msg: string); stdcall;
 
  TMyMsgEvent = procedure(Msg: string) of object;

  // TCallbackProcedure != TMyMsgEvent
  // ein "of object" fuegt einen (nicht sichtbaren) 1. neuen Parameter ein
 
  // Technisch ist demnach das TMyMsgEvent ein
  // TMyMsgEvent2 = procedure(Sender: TObject; Msg: string)

  // diese sind nicht kompatibel (daher die Fehlermeldung)
Entweder du nimmst ein "TMyMsgEvent2" und übergibst zusätzlich das "self" als Parameter an die DLL. Die Dll muss das Objekt zwischenspeichern und beim Aufruf ebenfalls übergeben. Du benoetigst dann eine Funktion ShowDLLMessageExt (keine Methode). Innerhalb dieser kannst du das Objekt dann casten und die Methode aufrufen. Dann sparst du dir die globale Variable:

Delphi-Quellcode:
procedure ShowDLLMessageExt(obj: TObject; sMsg: widestring); stdcall;
begin
  TintDll(obj).OnMyMsg(sMsg);
end;
oder du nimmst ein TMyMsgEvent und übergibst beim Callback dieses (sollte hoffentlich funktionieren).

norwegen60 5. Jul 2021 20:11

AW: Interface zu dynamischer DLL mit Callback
 
Danke für die genaue Erklärung. Vor allem der Hinweis
Zitat:

// TCallbackProcedure != TMyMsgEvent
// ein "of object" fuegt einen (nicht sichtbaren) 1. neuen Parameter ein
hat mir sehr geholfe.
In dem Fall erscheint mir die bestehende Lösung als am Einfachsten da ich nur auf der Exe-Seite handeln muss.
Ich probier aber um des Verständnis Willen auch mal die angedachten Ansätze


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:25 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz