c# - delphi COM 类型库中定义的方法应该如何实现,才能返回另一个 COM 对象?
问题描述
我正在COM Dll
从现有的Delphi
遗留代码开发一个用于C#.NET
. 我已经添加了适当的接口,Type Library
并且 COM 服务器已成功注册,并且可以从C#
环境中查看和创建对象。但是,一些现有的类具有定制的构造函数,这是必要的。因此,我添加了一个Helper
类(也作为 a COM object
),其中包含应该构造和返回其他 COM 对象的方法,但返回类型与创建的对象不兼容并且程序崩溃。
我正在使用COM 对象向导,代码几乎是Delphi
自己生成的。例如,通过定义一个ISomeObject
Interface,生成其对应的命名CoClassSomeObject
和一个Delphi类TSomeObject
。TSomeObject
implementsISomeObject
包括属性和方法的实现。我的意图是创建一个实例TSomeObject
,使用该Helper
对象并在C#
环境中使用它。
C#
创建对象及其方法的代码Helper
如下:
Helper helper = new Helper;
SomeObject = helper.CreateSomeObject(Param1, Param2);
当我将返回类型设置为的方法添加到inSomeObject
的IHelper
接口时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
转换为SomeProject
使用as
操作数,但没有成功。
问题是如何TSomeObject
通过返回类型为 的方法返回一个实例SomeObject
。
解决方案
更改您的方法签名以返回接口而不是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 对象”来实现。
这是生成的代码,实现简单:
type
THelper = class(TAutoObject, IHelper)
protected
function CreateSomeObject: ISomeObject; safecall;
end;
function THelper.CreateSomeObject: ISomeObject;
begin
Result := TSomeObject.Create;
end;
推荐阅读
- swift - 将私钥从安全飞地链接到配置文件
- jupyterhub - 使用 curl 访问时 JupyterHub 连接被拒绝
- python - 无法替换值以更正此 mplDeprecation
- ios - 如何将数据从 UIPageViewController 传递到它的“子视图”之一?
- haskell - Haskell中函数的理论定义是什么
- php - mysqli 数据库连接有时有效
- java - 接口示例似乎有不必要的扩展
- twilio - Azure 函数中未命中断点
- python - 使用 isin 函数过滤 Pandas 中的行时出现 TypeError 错误
- javascript - React 服务器不断遇到与 socket.io 的连接错误(重新打开项目时)