从接口获取子接口

Eri*_*nge 13 delphi interface

这是接口的一个特例,其中一个类实现了同一接口的多个版本,即.类似以下内容

IBase = interface
   procedure Foo;
end;

ISub = interface (IBase)
   procedure Bar;
end;

ISpecialBase = interface (IBase) end;

ISpecialSub = interface (ISub) end;

TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)

   procedure SpecialFoo1;
   procedure SpecialFoo2;
   procedure SpecialBar;

   procedure ISpecialBase.Foo = SpecialFoo1;

   procedure ISpecialSub.Foo = SpecialFoo2;
   procedure ISpecialSub.Bar = SpecialBar;

   function GetTheRightOne(parameters) : IBase;

end;

...

function TMyClass.GetTheRightOne(parameters) : IBase;
begin
   if (something complex depending on parameters) then
      Result := ISpecialBase(Self)
   else Result := ISpecialSub(Self)
end;
Run Code Online (Sandbox Code Playgroud)

当然,在实际案例中大约有十几个ISpecialXxxx.

非常重要的是只需要一个实例,即.我想避免创建适配器或虚拟实例只是为了推迟ISpecialXxxx实现,因为之前设计的唯一目的正是让单个实例处理许多出色的接口(即TMyClass的RefCount可以达到千分之一秒) ).

现在的问题是GetTheRightOne()返回一个IBase,但在某些时候我想检查是否可以将IBase强制转换为ISub.

有没有办法用上面的申报表格来做?

一种方法是添加一个

function GetSub : ISub;
Run Code Online (Sandbox Code Playgroud)

到IBase,但这确实使设计变得更重,因为它必须为每个ISpecialXxxx实现,并且对于ISpecialXxxx"继承"是多余的,所以我正在寻找更优雅的解决方案(假设它存在) .

(我有其他"膨胀"解决方案,所以我真的想强调我正在寻找一个非膨胀的解决方案)

编辑:更多细节

  • GUID存在于原始代码中(但它们缺乏的不是造成困难的原因)
  • Supports&QueryInterface不起作用,因为ISpecialXxx需要每个类有多个版本的接口,ISub没有列出explitly,因此找不到.然而,当使用适配器/虚拟类来推迟接口时,两者都有效(因为可以明确列出ISub)

edit2:如果你想要血淋淋的细节

检查https://code.google.com/p/dwscript/source/browse/trunk/Source/dwsJSONConnector.pas(r2492),TdwsJSONConnectorType类和IJSONLow界面,目标是从中检测到IConnectorFastCall它作为IConnectorCall传递,因此能够调用LowFastCall而不是LowCall.

检测必须在TConnectorCallExpr.AssignConnectorSym第294行进行,其中当前有一个QueryInterface.

请注意,QueryInterface适用于TdwsJSONIndexReadCall和TdwsJSONIndexWriteCall,因为它们从不同的类和实例实现IConnectorCall和IConnectorFastCall.但这就是我想要避免的.

当然,理想情况下,目标是将所有内容折回到ConnectorType类(单个类,单个实例)中,并且对于每个接口,特定的ConnectorType类应该可以自由地实现IConnectorCall或IConnectorFastCall.

Ond*_*lle 7

要查看接口的实现者是否实现了您可以使用的另一个接口Supports或QueryInterface,如下面的伪代码:

var
  Base: IBase;
  Sub: ISub;
begin
  Base := X.GetTheRightOne(Params);  
  if Supports(Base, ISub, Sub) then
    Sub.Bar;
end;
Run Code Online (Sandbox Code Playgroud)

编辑:要使上述工作,您需要将IID添加到接口的声明中.


小智 6

一种hackish方式依赖于编译器如何存储接口VTable数据.编译器为对象实现的每个接口存储单独的VTable.在每个VTable之后,它存储对象实现的接口数.

因此,我们可以使用它来确定我们是否获得了祖先接口的VTable,或者是后代的VTable.

至少这是它在XE3和XE5中的工作原理,我必须承认,在涉及接口的实现时,我有点像n00b.

除了依赖于实现细节之外,如果向IBase接口添加方法,则必须保持GetSub函数同步.此外,如果你有两个不同的,无关的,ISub,那么这段代码无法检测到你得到的.你可能会破解它,但我宁愿不去那里......

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  IBase = interface
    procedure Foo;
  end;

  ISub = interface (IBase)
    procedure Bar;
  end;

  ISpecialBase = interface (IBase)
  end;

  ISpecialSub = interface (ISub)
  end;

  TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)

    procedure SpecialFoo1;
    procedure SpecialFoo2;
    procedure SpecialBar;

    procedure ISpecialBase.Foo = SpecialFoo1;

    procedure ISpecialSub.Foo = SpecialFoo2;
    procedure ISpecialSub.Bar = SpecialBar;

    function GetTheRightOne(const Param: boolean) : IBase;
  end;


{ TMyClass }

function TMyClass.GetTheRightOne(const Param: boolean): IBase;
begin
  if Param then
    Result := ISpecialBase(Self)
  else
    Result := ISpecialSub(Self);
end;

procedure TMyClass.SpecialBar;
begin
  WriteLn('SubBar');
end;

procedure TMyClass.SpecialFoo1;
begin
  WriteLn('BaseFoo');
end;

procedure TMyClass.SpecialFoo2;
begin
  WriteLn('SubFoo');
end;

function GetSub(const Intf: IInterface): ISub;
type
  PPVtable = ^PVtable;
  PVtable = ^TVtable;
  TVtable = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer;
var
  intfVTable: PPVtable;
  caddr: NativeUInt;
begin
  result := nil;
  intfVTable := PPVTable(Intf);
  // 3 is offset to user methods
  // +0 = first user method, +1 = second user method etc
  // get the "address" of the first method in ISub
  caddr := NativeUInt(intfVTable^[3+1]);
  // compiler stores number of interface entries the
  // implementing object implements right after the interface vtable
  // so if we get a low number here, it means Intf is the IBase interface
  // and not the ISub
  if caddr > $100 then
    result := ISub(Intf);
end;

procedure CallIt(const b: IBase);
var
  s: ISub;
begin
  b.Foo;

  s := GetSub(b);
  if Assigned(s) then
    s.Bar;
end;

var
  c: TMyClass;
  b: IBase;
begin
  try
    c := TMyClass.Create;

    b := c.GetTheRightOne(True);
    CallIt(b);

    WriteLn('---');

    b := c.GetTheRightOne(False);
    CallIt(b);

    WriteLn('...');

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.
Run Code Online (Sandbox Code Playgroud)

这输出

BaseFoo
---
SubFoo
SubBar
...
Run Code Online (Sandbox Code Playgroud)

我们想要的.


Eri*_*nge 5

这是我目前的"最佳"解决方案:

我放弃了方法解析子句,并转移到绑定到主类的虚拟类,并且只实例化一次.

这样,可以使用GetInterface和Supports,因为ISub再次显式.

但是,这引发了循环引用的问题:主类需要引用特殊内容(如果只是在GetTheRightOne()中返回它们),并且特殊内容需要引用主类(访问存储在那里的参数或重定向到方法)主要课程).

主类和特殊区都是引用计数接口,当然,使用上下文是多线程的,因此通常的弱引用方案将引入对全局锁的需求.

但是鉴于特殊内容是仅用于主类的接口解析的虚拟类,我们可以覆盖它们的_AddRef和_Release以使引用计数集中在主类上(即_AddRef和_Release只是重定向到主类的_AddRef&_Release ,并且不再保持自己的引用计数).