TLDR: 我正试图从一个 .Net COM dll 到 Delphi client .exe 调用异步回调函数,但在注册-free COM 中似乎无法正常工作,而同步回调可以正常工作,当不是 reg-free COM 时也可以正常工作。
我的情况是有一个外部闭源的 .Net dll,它公开了一些公共事件。我需要将这些事件传递给 Delphi 应用程序。因此,我决定创建一个中间 .dll,它将作为我的应用程序和另一个 dll 之间的 COM 桥梁。当我的 dll 通过 regasm 注册时,它能够正常工作,但是当我切换到注册-free COM 时,情况就变得更糟了。我缩小了问题范围,以便在下面发布一个与其他 dll 无关的简单可重现示例。
根据这个答案,我制作了一个公共接口ICallbackHandler
,我希望从 Delphi 客户端应用程序中获得:
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());
}
});
}
}
}
然后,我给dll赋予了强名称,并通过Regasm.exe进行了注册。
现在我转到Delphi客户端。我使用组件 > 导入组件 > 导入类型库
创建了tlb包装代码,它给了我:
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;
创建了一个处理程序,以及一些带有两个按钮和备忘录的表单,用于测试:
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.
所以,我运行它,按下“同步”和“异步”按钮,一切都按预期工作。请注意,任务的线程ID出现在“End async call”行之后(还有一些延迟,因为使用了Thread.Sleep
):
第一部分结束。现在我转而使用Registration-free(并行)COM。根据这个答案,我在我的Delphi应用程序清单中添加了dependentAssembly
部分:
<dependency>
<dependentAssembly>
<assemblyIdentity name="ComDllNet" version="1.0.0.0" publicKeyToken="f31be709fd58b5ba" processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
我使用mt.exe工具为我的dll生成了一个清单:
<?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>
然后我注销了dll并运行应用程序。我发现只有回调函数中同步的部分起作用了:
编辑:请注意,您必须使用/tlb
选项注销,否则它将继续在本地计算机上工作,就像dll仍然注册一样(请参见)。
我已经尝试了很多方法,但不确定接下来该怎么做。我开始怀疑最初的方法根本不会起作用,需要在Delphi应用程序端实现一些线程控制。但我不确定该如何操作。如果有任何帮助,将不胜感激!
this.handler.Callback(GetThreadInfo())
,而没有进行COM封送。除非他的Delphi端期望在随机线程上接收回调。 - noseratio - open to workCoMarshalInterThreadInterfaceInStream
/CoGetInterfaceAndReleaseStream
(示例在这里)和全局接口表(GIT,示例在这里)。 - noseratio - open to work