我正在从现有COM Dll
的旧版代码中开发Delphi
,以在C#.NET
中使用。我向Type Library
添加了适当的接口,并且成功注册了COM服务器,并且可以从C#
环境中看到和创建对象。
但是,某些现有的类具有必需的自定义构造函数。因此,我添加了一个Helper
类(也作为COM object
),该类包含应该构造和返回其他COM对象的方法,但是返回类型与创建的对象不兼容,并且程序崩溃。
我正在使用 COM对象向导,并且该代码几乎是由Delphi
本身生成的。例如,通过定义ISomeObject
接口,将生成其对应的名为SomeObject
的CoClass和Delphi类TSomeObject
。 TSomeObject
实现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
的实例。
答案 0 :(得分:1)
更改方法签名以返回接口,而不是SomeObject
类的实例。您可以在类型库编辑器中执行以下操作:
生成的代码将是:
function THelper.CreateSomeObject: ISomeObject;
begin
end;
根据评论:尽管您在问题中提供了很多文本,但仍然缺少一些基本信息。您已经提到了一些指针操作,但是在开发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/
免责声明:我与该网站没有任何关系,我只是发现它非常有用。
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对象”来实现。
这是带有简单实现的生成代码:
type
THelper = class(TAutoObject, IHelper)
protected
function CreateSomeObject: ISomeObject; safecall;
end;
function THelper.CreateSomeObject: ISomeObject;
begin
Result := TSomeObject.Create;
end;