首页 > 解决方案 > delphi COM 类型库中定义的方法应该如何实现,才能返回另一个 COM 对象?

问题描述

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

我正在使用COM 对象向导,代码几乎是Delphi自己生成的。例如,通过定义一个ISomeObjectInterface,生成其对应的命名CoClassSomeObject和一个Delphi类TSomeObjectTSomeObjectimplementsISomeObject包括属性和方法的实现。我的意图是创建一个实例TSomeObject,使用该Helper对象并在C#环境中使用它。

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

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

当我将返回类型设置为的方法添加到inSomeObjectIHelper接口时Type Library,会生成以下代码(减去正文)。

function THelper.CreateSomeObject(Param1, Param2): SomeObject
begin
    Result := TSomeProject.Create(Param1, Param2); //This line is not generated by COM Object Wizard
end;

上面的代码由于不兼容而崩溃并出现错误。

在调试时,我意识到类型ResultPointer as ISomeObject. 我试图将输出类型TSomeProject.Create转换为SomeProject使用as操作数,但没有成功。

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

标签: c#delphicom

解决方案


更改您的方法签名以返回接口而不是SomeObject类的实例。您可以在类型库编辑器中执行此操作,如下所示: 在此处输入图像描述

生成的代码将是:

function THelper.CreateSomeObject: ISomeObject;
begin

end;

编辑 1

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

因此,我尝试在 Delphi 7(我安装的最旧的 delphi 版本)中自己重新创建您的场景。我创建了一个ActiveX Library与上面类似的 COM 服务器(Delphi 中的项目)。我的方法实现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调用约定,因此它可以HRESULT通过在方法中包装任何未捕获的异常并将其传递回 EAX 寄存器,从而允许 [Out, RetVal] 参数成为返回值,从而消除方法签名中的返回值。但这仅支持从IDispatch. 为避免实现IDispatch方法,您可以将轻量级 COM 对象转换为自动化对象 ( TAutoObject),如果您还没有这样做的话。这可以通过在将新项目添加到 ActiveX 库时选择“自动化对象”选项而不是“COM 对象”来实现。

所以这就是方法定义转换为时的样子safecall类型库编辑器

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

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

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

推荐阅读