C# 应该如何实现delphi COM类型库中定义的方法,以便它可以返回另一个COM对象?

C# 应该如何实现delphi COM类型库中定义的方法,以便它可以返回另一个COM对象?,c#,delphi,com,C#,Delphi,Com,我正在从现有的Delphi遗留代码开发一个COM Dll,以便在C.NET中使用。我已经向类型库添加了适当的接口,COM服务器已经成功注册,并且可以从C环境中查看和创建对象。 但是,一些现有的类具有定制的构造函数,这是必需的。因此,我还添加了一个Helper类作为COM对象,它包含应该构造和返回其他COM对象的方法,但返回类型与创建的对象不兼容,程序崩溃 我正在使用COM对象向导,代码几乎是由Delphi本身生成的。例如,通过定义ISomeObject接口,将生成其相应的名为SomeObject

我正在从现有的Delphi遗留代码开发一个COM Dll,以便在C.NET中使用。我已经向类型库添加了适当的接口,COM服务器已经成功注册,并且可以从C环境中查看和创建对象。 但是,一些现有的类具有定制的构造函数,这是必需的。因此,我还添加了一个Helper类作为COM对象,它包含应该构造和返回其他COM对象的方法,但返回类型与创建的对象不兼容,程序崩溃

我正在使用COM对象向导,代码几乎是由Delphi本身生成的。例如,通过定义ISomeObject接口,将生成其相应的名为SomeObject的CoClass和Delphi类TSomeObject。TSomeObject实现ISomeObject包括属性和方法的实现。我的意图是使用Helper对象创建一个TSomeObject实例,并在C环境中使用它

创建辅助对象及其方法的C代码如下所示:

    Helper helper = new Helper;
    SomeObject = helper.CreateSomeObject(Param1, Param2);
当我将返回类型设置为SomeObject的方法添加到类型库中的IHelper接口时,将生成以下代码减去正文

函数THelper.CreateSomeObjectParam1,Param2:SomeObject 开始 结果:=TSomeProject.CreateParam1,Param2//此行不是由COM对象向导生成的 终止 由于不兼容,上面的代码崩溃并出现错误

在调试时,我意识到结果的类型是指针作为对象。我尝试将TSomeProject.Create的输出类型转换为使用as操作数的SomeProject,但无效


问题是如何通过返回类型为SomeObject的方法返回TSomeObject的实例。

更改方法签名以返回接口而不是SomeObject类的实例。您可以在类型库编辑器中这样做:

生成的代码将是:

function THelper.CreateSomeObject: ISomeObject;
begin

end;
编辑1 根据评论:尽管你在问题中提供了很多文本,但仍然缺少一些基本信息。您已经提到了一些指针操作,但在开发COM服务器的典型场景中,您不应该处理这些操作

所以我试着自己在我安装的Delphi7最旧的Delphi版本中重新创建您的场景。我在Delphi中创建了一个与上面类似的COM服务器ActiveX库项目。我对CreateSomeObject方法的实现是:

方法TSomeObject.HelloWorld的实现并不重要。然后我通过IDE函数Run>RegisterActiveX服务器注册了服务器。之后,我在Delphi中创建了示例控制台应用程序,导入类型库项目>导入类型库,并向主程序添加了几行代码:

uses
  ActiveX, COMTest_TLB;

var
  _Helper: IHelper;
  _SomeObject: ISomeObject;
begin
  CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  _Helper := CoHelper.Create;
  _SomeObject := _Helper.CreateSomeObject;
  _SomeObject.HelloWorld;
end.
控制台应用程序运行完成,未出现崩溃或意外结果。到现在为止,一直都还不错。然后,我参考COMTest库创建了示例C.NET控制台应用程序.NET 4.5.2:

使用制度; 使用COMTest; 班级计划 { [状态线程] 静态环[]args { var helper=新的helper; var someObject=helper.CreateSomeObject; someObject.HelloWorld; } } 实际上,应用程序因AccessViolationException而崩溃。通过将主机应用程序设置为.NET控制台应用程序并在项目的链接器选项中启用远程调试符号,我可以快速设置COM服务器的调试。我相信您已经了解了这一点。TSomeObject实例的创建进展顺利,但分配给Result的操作失败

在本例中,当为托管类型接口的变量赋值时,有一些编译器的魔力。它首先清除目的地,如果目的地不是nil,它基本上是对_Release的调用。令我惊讶的是,在.NET控制台应用程序客户端的情况下,它不是!因此,我修改了实施方案,以:

function THelper.CreateSomeObject: ISomeObject;
begin
  Pointer(Result) := nil;
  Result := TSomeObject.Create;
end;
在第一次将结果用作接口之前清除结果就完成了这项任务。我还没有深入了解为什么会发生这种情况,但我肯定会这样做。我还将使用更新的Delphi版本进行检查,并在这个答案的另一个编辑中发布我的发现

在这里,您可以在Delphi中找到一些与COM开发相关的优秀资源。

免责声明:我和那个网站没有任何关系,我只是觉得它非常有用

编辑2 COM接口方法应按约定返回HRESULT。这是COM中报告错误的默认机制。从方法返回附加值应该通过带有[Out]修饰符的参数来实现。或者,一个参数可以用[Out,RetVal]修饰符标记,通常是最后一个用来指示方法返回值的修饰符。请注意,[Out]参数是通过引用传递的,您必须在类型库编辑器中将附加星号*符号附加到类型名称后进行指示。因此,ISomeObject*变为[Out]ISomeObject**

Delphi支持safecall调用约定,因此它可以通过在方法中包装任何未捕获的异常并允许[Out,RetVal]参数成为返回值,从而从方法签名中消除HRESULT返回值。但这仅适用于双接口。请参见从IDispatch派生的接口的“标志”选项卡。为了避免实现IDispatch方法,您可以将轻量级COM对象转换为automation对象TAutoObject,如果您还没有这样做的话。在向ActiveX库添加新项时,可以通过选择“自动化对象”选项而不是“COM对象”来实现这一点

这就是转换为safecall时方法定义的样子:

下面是生成的代码,它的实现非常简单:

type
  THelper = class(TAutoObject, IHelper)
  protected
    function CreateSomeObject: ISomeObject; safecall;
  end;

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;

谢谢@Peter Wolf的回复,但我已经尝试过了,它不起作用。不起作用不是一个有用的问题描述。亲爱的@Peter,谢谢你的详尽回答,谢谢你为模拟这个问题所付出的一切努力。我真的很感激。你的解决方案完全有效!我不熟悉自动化对象,但我一定会尝试一下。
type
  THelper = class(TAutoObject, IHelper)
  protected
    function CreateSomeObject: ISomeObject; safecall;
  end;

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;