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

时间:2019-05-27 10:33:36

标签: c# delphi com

我正在从现有COM Dll的旧版代码中开发Delphi,以在C#.NET中使用。我向Type Library添加了适当的接口,并且成功注册了COM服务器,并且可以从C#环境中看到和创建对象。 但是,某些现有的类具有必需的自定义构造函数。因此,我添加了一个Helper类(也作为COM object),该类包含应该构造和返回其他COM对象的方法,但是返回类型与创建的对象不兼容,并且程序崩溃。

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

创建C#对象及其方法的Helper代码如下:

    Helper helper = new Helper;
    SomeObject = helper.CreateSomeObject(Param1, Param2);

当我将返回类型设置为SomeObject的方法添加到IHelper的{​​{1}}接口中时,将生成以下代码(减去主体)。

Type Library

以上代码由于不兼容而崩溃,并显示错误。

在调试时,我意识到function THelper.CreateSomeObject(Param1, Param2): SomeObject begin Result := TSomeProject.Create(Param1, Param2); //This line is not generated by COM Object Wizard end; 的类型为Result。我试图使用Pointer as ISomeObject操作数将TSomeProject.Create的输出类型{em {em} 转换为SomeProject

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

1 个答案:

答案 0 :(得分:1)

更改方法签名以返回接口,而不是SomeObject类的实例。您可以在类型库编辑器中执行以下操作: enter image description here

生成的代码将是:

function THelper.CreateSomeObject: ISomeObject;
begin

end;

编辑1

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

因此,我尝试自己在Delphi 7(我安装的最旧的delphi版本)中重新创建您的方案。我创建了一个类似于上面的COM服务器(在Delphi中为ActiveX Library项目)。我对方法CreateSomeObject的实现是:

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

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

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):

using System;
using COMTest;

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        var helper = new Helper();
        var someObject = helper.CreateSomeObject();
        someObject.HelloWorld();
    }
}

确实,该应用程序因AccessViolationException而崩溃。我通过将主机应用程序设置为.NET控制台应用程序并在项目的链接器选项中启用了远程调试符号来快速设置COM服务器的调试(我确定您已经弄清楚了)。创建TSomeObject实例的过程很顺利,但是分配给Result的操作失败。

将值赋给托管类型的变量(在这种情况下为接口)时,有一些编译器魔术。如果目的地不是_Release,则从清除目的地开始,该目的地基本上是对nil的调用。令我惊讶的是,.NET控制台应用程序客户端不是这样!因此,我将实现修改为:

function THelper.CreateSomeObject: ISomeObject;
begin
  Pointer(Result) := nil;
  Result := TSomeObject.Create;
end;

在第一次将结果用作接口之前清除结果就可以了。我还没有详细解释为什么会发生这种情况,但是我一定会做。我还将使用较新的Delphi版本进行检查,并将我的发现发布在此答案的另一次编辑中。

在这里您可以找到一些与Delphi中COM开发相关的重要资源。 http://www.techvanguards.com/com/

免责声明:我与该网站没有任何关系,我只是发现它非常有用。

编辑2

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

Delphi支持safecall调用约定,因此它可以通过在方法中包装任何未捕获的异常并允许{Out,RetVal]参数的passing it back in EAX register来消除方法签名中的HRESULT返回值成为返回值。但这仅支持从IDispatch派生的双接口(请参见接口的“标记”选项卡)。为了避免实现IDispatch方法,可以将轻量级COM对象转换为自动化对象(TAutoObject)(如果尚未实现的话)。将新项目添加到ActiveX库时,可以通过选择“自动化对象”选项而不是“ COM对象”来实现。

因此,这是转换为safecall时方法定义的样子: Type library editor

这是带有简单实现的生成代码:

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

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