TLDR: Ich versuche Async-Callbacks von einer .Net-COM-DLL zu Delphi-Client .exe aufzurufen, aber diese scheint in registrierungsfreiem COM nicht ordnungsgemäß zu funktionieren, während synchrone Rückrufe funktionieren und auch async Rückrufe funktionieren, wenn es kein reg-free COM ist.Rückruf von .Net COM-DLL zu Delphi-Client in Registrierung-frei (nebeneinander) COM
Mein globaler Fall ist, dass ich eine fremde Closed-Source-.Net-DLL habe, die einige öffentliche Ereignisse aufdeckt. Ich muss diese Ereignisse an die Delphi App weitergeben. Also entschied ich mich, eine Zwischen-DLL zu erstellen, die als COM-Brücke zwischen meiner App und dieser anderen DLL funktionieren würde. Es funktionierte gut, wenn meine DLL über regasm registriert wurde, aber die Dinge werden schlimmer, wenn ich auf reg-free COM umschalte. Ich habe meinen Fall auf ein kleines reproduzierbares Beispiel verkürzt, das nicht von der anderen DLL abhängt, also werde ich es unten veröffentlichen.
Basierend auf this answer machte ich eine öffentliche Schnittstelle ICallbackHandler
, die ich erwarte von Delphi Client-Anwendung zu bekommen:
namespace ComDllNet
{
[ComVisible(true)]
[Guid("B6597243-2CC4-475B-BF78-427BEFE77346")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICallbackHandler
{
void Callback(int value);
}
[ComVisible(true)]
[Guid("E218BA19-C11A-4303-9788-5A124EAAB750")]
public interface IComServer
{
void SetHandler(ICallbackHandler handler);
void SyncCall();
void AsyncCall();
}
[ComVisible(true)]
[Guid("F25C66E7-E9EF-4214-90A6-3653304606D2")]
[ClassInterface(ClassInterfaceType.None)]
public sealed class ComServer : IComServer
{
private ICallbackHandler handler;
public void SetHandler(ICallbackHandler handler) { this.handler = handler; }
private int GetThreadInfo()
{
return Thread.CurrentThread.ManagedThreadId;
}
public void SyncCall()
{
this.handler.Callback(GetThreadInfo());
}
public void AsyncCall()
{
this.handler.Callback(GetThreadInfo());
Task.Run(() => {
for (int i = 0; i < 5; ++i)
{
Thread.Sleep(500);
this.handler.Callback(GetThreadInfo());
}
});
}
}
}
Dann habe ich einen starken Namen zu dll gab, und registriert sie über Regasm.exe.
Jetzt wandte ich mich an Delphi-Client. Ich den TLB-Wrapper-Code erstellen Component > Import Component > Import a Type Library
verwendet, die mir
ICallbackHandler = interface(IUnknown)
['{B6597243-2CC4-475B-BF78-427BEFE77346}']
function Callback(value: Integer): HResult; stdcall;
end;
IComServer = interface(IDispatch)
['{E218BA19-C11A-4303-9788-5A124EAAB750}']
procedure SetHandler(const handler: ICallbackHandler); safecall;
procedure SyncCall; safecall;
procedure AsyncCall; safecall;
end;
IComServerDisp = dispinterface
['{E218BA19-C11A-4303-9788-5A124EAAB750}']
procedure SetHandler(const handler: ICallbackHandler); dispid 1610743808;
procedure SyncCall; dispid 1610743809;
procedure AsyncCall; dispid 1610743810;
end;
und erstellt einen Handler und eine gewisse Form mit zwei Tasten und Memo Dinge zu testen:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComDllNet_TLB, StdCtrls;
type
THandler = class(TObject, IUnknown, ICallbackHandler)
private
FRefCount: Integer;
protected
function Callback(value: Integer): HResult; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
property RefCount: Integer read FRefCount;
end;
type
TForm1 = class(TForm)
Memo1: TMemo;
syncButton: TButton;
asyncButton: TButton;
procedure FormCreate(Sender: TObject);
procedure syncButtonClick(Sender: TObject);
procedure asyncButtonClick(Sender: TObject);
private
{ Private declarations }
handler : THandler;
server : IComServer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function THandler._AddRef: Integer;
begin
Inc(FRefCount);
Result := FRefCount;
end;
function THandler._Release: Integer;
begin
Dec(FRefCount);
if FRefCount = 0 then
begin
Destroy;
Result := 0;
Exit;
end;
Result := FRefCount;
end;
function THandler.QueryInterface(const IID: TGUID; out Obj): HRESULT;
const
E_NOINTERFACE = HRESULT($80004002);
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function THandler.Callback(value: Integer): HRESULT;
begin
Form1.Memo1.Lines.Add(IntToStr(value));
Result := 0;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
handler := THandler.Create();
server := CoComServer.Create();
server.SetHandler(handler);
end;
procedure TForm1.syncButtonClick(Sender: TObject);
begin
Form1.Memo1.Lines.Add('Begin sync call');
server.SyncCall();
Form1.Memo1.Lines.Add('End sync call');
end;
procedure TForm1.asyncButtonClick(Sender: TObject);
begin
Form1.Memo1.Lines.Add('Begin async call');
server.AsyncCall();
Form1.Memo1.Lines.Add('End async call');
end;
end.
Also, ich laufe es, gedrückt ‚sync 'und' async 'Tasten und alles hat wie erwartet funktioniert. Beachten Sie, wie der Thread-IDs einer Aufgabe kommt nach ‚End asynchronem Aufruf‘ Leitung (auch mit einiger Verzögerung wegen Thread.Sleep
):
Ende des ersten Teils. Jetzt habe ich auf die Verwendung von R Registration-free (Side-by-Side) COM umgestellt. Basierend auf this answer fügte ich dependentAssembly
Teil meiner Delphi App-Manifest:
<dependency>
<dependentAssembly>
<assemblyIdentity name="ComDllNet" version="1.0.0.0" publicKeyToken="f31be709fd58b5ba" processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
die mt.exe tool ich mit einem Manifest für meine dll generiert:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="ComDllNet" version="1.0.0.0" publicKeyToken="f31be709fd58b5ba" processorArchitecture="x86"/>
<clrClass clsid="{F25C66E7-E9EF-4214-90A6-3653304606D2}" progid="ComDllNet.ComServer" threadingModel="Both" name="ComDllNet.ComServer" runtimeVersion="v4.0.30319"/>
<file name="ComDllNet.dll" hashalg="SHA1"/>
</assembly>
Dann unregistriert ich die dll und die App laufen. Und ich fand, dass nur synchrone Teile der Rückrufe arbeiten:
Edit: Beachten Sie, dass Sie mit /tlb
Option abzumelden müssen, sonst wird es weiterhin auf dem lokalen Rechner arbeiten, als ob dll war immer noch registriert (see).
Ich habe eine Reihe von Dingen schon müde, und ich bin mir nicht sicher, was ich als nächstes tun soll. Ich habe den Verdacht, dass der ursprüngliche Ansatz überhaupt nicht funktionieren sollte und ich ein paar Threads auf der Delphi-App-Seite implementieren muss. Aber ich bin mir nicht sicher was und wie. Jede Hilfe wäre willkommen!
Die Registrierung des Proxy ist das Richtige, aber @Mikhail sollte auch 'this.handler.Callback (GetThreadInfo())' nicht direkt aus einem Pool-Thread aufrufen, ohne COM-Marshalling. Es sei denn, seine Delphi-Seite erwartet den Rückruf in einem zufälligen Thread. – Noseratio
Ich habe versucht, 'comInterfaceExternalProxyStub' -Element hinzuzufügen, aber auf das .dll-Element selbst zu verweisen. Jetzt habe ich Ihren Vorschlag mit seperate 'Datei' Element für .tlb versucht und es hat nichts geändert, aber scheint angemessener, so werde ich es behalten. – Mikhail
@Noseratio: das scheint tatsächlich der Fall zu sein. Könnten Sie mir bitte eine Anleitung für die richtige Umsetzung des Marshalling geben? – Mikhail