Dynamic Interface (or Strong "Duck Typing")

http://d.hatena.ne.jp/lethevert/20050922/p1
ダウンキャストを、後付けinterface→元々のオブジェクトが持っているinterface、に限定すれば、TAggregateObjectで可能ですね。Objectから任意のinterfaceを生成する元々のVB9のサンプルとは全然違うという話は置いておいて。

program Duck;

{$APPTYPE CONSOLE}

uses
  SysUtils, Crt;

type
  IAnObject = interface
    ['{BE464EC2-D871-4BA6-B4F2-4DB0A4C983C5}']
    function CalcSomething(X: Integer): Integer;
    procedure DoSomething;
  end;

  TAnObject = class(TInterfacedObject, IAnObject)
    function CalcSomething(X: Integer): Integer;
    procedure DoSomething;
  end;

procedure TAnObject.DoSomething;
begin
  WriteLn('DoSomething');
end;

function TAnObject.CalcSomething(X: Integer): Integer;
begin
  Result := X * 2
end;

type
  IDuck = interface
    procedure DoSomething;
  end;

procedure AnProcedureThatNeedsInterface(const intf: IDuck);
var
  obj: IAnObject;
begin
  intf.DoSomething;
  obj := intf as IAnObject;
  WriteLn(obj.CalcSomething(100));
end;

type
  TDuckAdapter = class(TAggregatedObject, IDuck)
    FAnObject: TAnObject;
    constructor Create(const AnObject: TAnObject);
    property AsDuck: TAnObject read FAnObject implements IDuck;
  end;

constructor TDuckAdapter.Create(const AnObject: TAnObject);
begin
  inherited Create(AnObject);
  FAnObject := AnObject;
end;

var
  obj: TAnObject;
  intf: IDuck;
begin
  obj := TAnObject.Create;
  intf := TDuckAdapter.Create(obj);
  AnProcedureThatNeedsInterface(intf);
end.

逆をやるには、TAnObjectがIDuckのことを知らない以上はQueryInterfaceに細工が必要で、これは例えTAnObjectに手を入れられないとしても、VirtualProtectでVMT上の関数ポインタ差し替えてやるだけで(以下略)
元々のサンプルのようにやるには、公開部をpublishedにするかこの間見つけたIInvokableを使うかぐらいでしょうか。
こうやってネタを小出しにしているせいで、ObjectPascal Magic Programmingのところが増えていかないという話。
双方向のダウンキャストを可能にする(以下略)版。

program Duck;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, Crt;

type
  IAnObject = interface
    ['{BE464EC2-D871-4BA6-B4F2-4DB0A4C983C5}']
    function CalcSomething(X: Integer): Integer;
    procedure DoSomething;
  end;

  TAnObject = class(TInterfacedObject, IAnObject)
    function CalcSomething(X: Integer): Integer;
    procedure DoSomething;
  end;

procedure TAnObject.DoSomething;
begin
  WriteLn('DoSomething');
end;

function TAnObject.CalcSomething(X: Integer): Integer;
begin
  Result := X * 2
end;

type
  IDuck = interface
    ['{FDB7A78C-E3FC-4205-B773-D15C79DC12F5}']
    procedure DoSomething;
  end;

procedure AnProcedureThatNeedsInterface(const intf: IDuck);
var
  obj: IAnObject;
begin
  intf.DoSomething;
  obj := intf as IAnObject;
  WriteLn(obj.CalcSomething(100));
end;

type
  TDuckAdapter = class(TAggregatedObject, IDuck)
    FAnObject: TAnObject;
    constructor Create(const AnObject: TAnObject);
    property AsDuck: TAnObject read FAnObject implements IDuck;
  end;

constructor TDuckAdapter.Create(const AnObject: TAnObject);
begin
  inherited Create(AnObject);
  FAnObject := AnObject;
end;

procedure SampleMain(obj: IInterface);
var
  intf: IDuck;
begin
 intf := obj as IDuck;
 AnProcedureThatNeedsInterface(intf);
end;

var
  OldQueryInterface: function(const Self: IInterface; const IID: TGUID; out Obj): HResult; stdcall;
  Offset: Cardinal;

function NewQueryInterface(const Self: IInterface; const IID: TGUID; out Obj): HResult; stdcall;
begin
  if IsEqualGUID(IID, IDuck) then begin
    IDuck(Obj) := TDuckAdapter.Create(TAnObject(Cardinal(Self) - Offset));
    Result := 0
  end else begin
    Result := OldQueryInterface(Self, IID, Obj)
  end;
end;

procedure InstallContraryIsAlsoTruth;
var
  Entry: PInterfaceEntry;
  OldAccess: DWORD;
begin
  {手を抜いているが実際は全部のentryに対して行う}
  Entry := TAnObject.GetInterfaceEntry(IInterface);
  OldQueryInterface := PPointer(Entry.VTable)^;
  Offset := Entry.IOffset;
  VirtualProtect(Entry.VTable, 4, PAGE_READWRITE, OldAccess);
  PPointer(Entry.VTable)^ := @NewQueryInterface;
  VirtualProtect(Entry.VTable, 4, OldAccess, nil);
end;

var
  obj: TAnObject;
  intf: IDuck;
begin
  obj := TAnObject.Create;
  intf := TDuckAdapter.Create(obj);
  AnProcedureThatNeedsInterface(intf);
  InstallContraryIsAlsoTruth;
  SampleMain(obj);
end.