2011-03-27 56 views
6

对于服务器端插件框架,我想实现公开一个返回类引用(TInterfacedClass)的RegisterPlugin方法的DLL。基于TInterfacedClass的Delphi插件框架的内存管理

主机应用程序然后创建此类的实例,并且实例将在主机线程的上下文中运行。 (这与例如Jedi VCL插件框架不同,后者实例化DLL或BPL中的插件并将实例返回给主机。)

第一个测试显示迄今为止没有问题。但是,我应该知道存储管理存在隐藏的问题吗?正如我在此项目中使用Delphi 2009,FastMM4是默认的内存管理器。

此处插件DLL项目的草图:

library ExamplePlugin; 
uses 
    ... 
type 
    TPluginOne = class(TInterfacedObject, ...) 
    ... 
    end; 

function RegisterPlugin: TInterfacedClass; stdcall; 
begin 
    Result := TPluginOne; 
end; 

exports 
    RegisterPlugin; 

{ TPluginOne } 
// ... plugin class implementation 

begin 
end. 

回答

7

没有问题与内存管理器,因为FastMM作为EXE和DLL之间的共享内存管理器。但我真的不熟悉在DLL和EXE之间传递纯对象或(最差)元类的概念。问题是,来自EXE的TInterfacedObject与DLL中的TInterfacedObject不一样!当然,他们可能看起来完全一样,但他们不是!如果您曾经为EXE或任何DLL升级Delphi的版本,则需要重新构建所有内容(因此失去了从实现插件框架中获得的任何优势)。

一个更便携的解决方案将是返回一个“工厂接口”,沿着线的东西:

IFactoryInterface = interface 
[GUId-goes-here] 
    function MakeWhateverInterfaceYouNeed: IUnknownDerivate 
end; 

然后导出函数与此签名:

function RegisterPlugin: IFactoryInterface; 
+0

工厂接口可以与所有版本的Delphi一起工作(只要与GUID相关的接口在主机和DLL中相同? – mjn 2011-03-27 10:21:58

+0

这是COM不是吗? – 2011-03-27 10:38:32

+0

@mjin,是的,接口有一个明确定义二进制接口与给定GUID的接口预期(并且由编译器信任)具有特定的VMT *练习:*在单元A中定义接口“ITestIntf”。将单元B中相同的定义复制粘贴。如果尝试执行'var i1:A.ITestIntf:= B.ITestIntf'编译器会抱怨,因为'A.ITestIntf'与'B.ITestIntf'不兼容。如果你做了'var i1:A.ITestIntf:= B.ITestIntf as A.ITestIntf'赋值按预期工作,因为这两个声明都有相同的GUID – 2011-03-27 16:53:04

2

你的代码是不完整的,但是从已包含什么有一个明显的缺陷。

您似乎是从DLL中导出类(TInterfacedClass)。当客户端尝试使用不同版本的Delphi来使用您的类时,这会导致问题。更重要的是,如果他们想用不同的语言编写插件,就会让他们感到无助。

就我个人而言,我会选择一个基于COM的界面,这将允许插件作者使用任何语言创建插件。这实际上是COM发明要解决的问题。

如果您很高兴被限制使用相同的编译器插件和宿主应用程序,并且您更愿意向COM接口公开类,那么您需要确保所有的释放都使用相同的内存管理器作为分配的内存。最简单的方法是使用ShareMem,然后你就会安全。

UPDATE

科斯明在评论所指出的跨越模块边界导出类另一个缺陷。这基本上是你不应该做的事情。 COM是为这个目的而设计的,它仍然应该是你的第一选择。德尔福接口这是COM兼容,所以你可以得到二进制的互操作性同样的好处,而无需创建服务器,注册CLSID的等

我觉得你的插件应该是这样的:

library ExamplePlugin; 

type 
    TPluginOne = class(TInterfacedObject, IPlugin) 
    [GUID] 
    public 
    constructor Create(const Host: THostApp); 
    end; 

function RegisterPlugin(const Host: IHostApp): IPlugin; stdcall; 
begin 
    Result := TPluginOne.Create(Host); 
end; 

exports 
    RegisterPlugin; 

begin 
end. 
+0

即使DLL和EXE是用相同版本的Delphi构建的,如果你有一个来自DLL的'TObjectDerivate'并且试图在EXE中执行'O是TObjectDerivate',你将会得到FALSE,因为'是'和'as '运算符依赖于'Class'的实际值:DLL和EXE将具有不同的'Class' meta-c用于'TObjectDerivate'! – 2011-03-27 10:23:13

+0

在当前版本的代码中,服务器使用'Inst:= AComponentClass.Create;'实例化一个插件,并且从此仅使用'if支持(Inst,ISomeInterface,X)'来访问接口方法。 COM兼容接口,是否有从DLL返回类引用的风险? – mjn 2011-03-27 10:45:13

+1

@mjn对于它的价值,我的答案和Cosmin的基本相同。你的代码'Inst:= AComponentClass.Create;'已经是个问题了。相反,您的插件应该公开一个返回接口的全局函数。在插件中,它将通过调用构造函数来实现。但是如果你调用在不同模块中声明的类的构造函数和方法,那么你将进入一个痛苦的世界。这听起来像你几乎在那里。你只需要你的插件来导出这个工厂方法。 – 2011-03-27 10:52:51