如何通過COM接口得到實現(xiàn)該接口的對象實例
問題由來
我的程序為一個基于COM的插件結(jié)構(gòu),框架需要向插件傳遞一個IResource接口。IResource
需要根據(jù)不同的插件傳遞不同的內(nèi)容。
接口定義
IResource = Interface(IDispatch)
  Function GetPath: String; safecall;
End;
實現(xiàn)類
TResource = TClass(TAutoObject, IResource)
PRotected
  Function GetPath: String; SafeCall;
Public
  Path: String;
End;
Function GetPath: String; 
Begin
  Result:= Path;
End;
調(diào)用部分:
Var
  Resource: IResource;
  ResourceObj: TResource;
Begin
  Resource:= CreateComObject(CLASS_Resource) As IResource;
  //想通過強制轉(zhuǎn)換得到TResource;結(jié)果失敗了:(
  ResourceObj:= TResource(Resource);
  ResourceObj.Path:= '這里設置不同的值';
End;
請問:
    如何通過IResource得到TResource,從而達到設置PATH值的目的? 
目前我采用的方案是再定義一個ISetValue的接口修改里面的PATH屬性,感覺用起來比較
麻煩。 
問題的延伸 
如果從解決問題出發(fā),通過定義配置接口,如:
IObjRef = Interface
  function GetObjRef: TObject; safecall;
end;
這樣得到對象,再對PATH賦值,這樣做在沒有破壞COM的封裝,實現(xiàn)起來也比較清晰。問題至此基本解決。
但本著從分析DELPHI對象與接口之間的關(guān)系的出發(fā)點,我們還是繼續(xù)標題中提出的問題:
如何通過COM接口得到實現(xiàn)該接口的對象實例 ? 
SAVETIME的線索 
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2433841  
SAVETIME的這篇文章中提到了關(guān)于DELPHI中對象與接口之間在編譯器實現(xiàn)的內(nèi)存空間情況:
----------------|-----------------|----------|--------------|-----------------
 對象/接口指針   | 對象內(nèi)存空間    |          | 虛方法表     |
 ----------------|-----------------|----------|--------------|-----------------
 MyObject    ->  | VMTptr        00|--------->| VirtA      00|
                 | FRefCount     04|          | VirtB      04|
 MyIntf      ->  | IInterface    08|----|           
                 | FFieldA       0C|    |           | IInterface    跳轉(zhuǎn)表   |
                 | FFieldB       10|    |---------> | addr of QueryInterface |
 MyIntfB     ->  | IIntfB        14|---------|      | addr of _AddRef        |
 MyIntfA     ->  | IIntfA        18|--|      |      | addr of _Release       |
                                      |      |
                                      |      |      | IIntfB        跳轉(zhuǎn)表   |
                                      |      |----> | addr of ProcB          |
                                      |             | addr of VirtB          |
                                      |
                                      |             | IIntfA        跳轉(zhuǎn)表   |
                                      |-----------> | addr of ProcA          |
                                                    | addr of VirtA          |
 ------------------------------------------------------------------------------
一個對象在調(diào)用類的成員函數(shù)的時候,比如執(zhí)行 MyObject.ProcA,會隱含傳遞一個 Self 指針給這個成員函數(shù):MyObject.ProcA(Self)。Self 就是對象數(shù)據(jù)空間的地址。那么編譯器如何知道 Self 指針?原來對象指針 MyObject 指向的地址就是 Self,編譯器直接取出 MyObject^ 就可以作為 Self。
在以接口的方式調(diào)用成員函數(shù)的時候,比如 MyIntfA.ProcA,這時編譯器不知道 MyIntfA 到底指向哪種類型(class)的對象,無法知道 MyIntfA 與 Self 之間的距離(實際上,在上面的例子中 Delphi 編譯器知道 MyIntfA 與 Self 之間的距離,只是為了與 COM 的二進制格式兼容,使其它語言也能夠使用接口指針調(diào)用接口成員函數(shù),必須使用后期的 Self 指針修正),編譯器直接把 MyIntfA 指向的地址設置為 Self。從上圖可以看到,MyIntfA 指向 MyObject 對象空間中 $18 偏移地址。這時的 Self 指針當然是錯誤的,編譯器不能直接調(diào)用 TMyObject.ProcA,而是調(diào)用 IIntfA 的“接口跳轉(zhuǎn)表”中的 ProcA。“接口跳轉(zhuǎn)表”中的 ProcA 的內(nèi)容就是對 Self 指針進行修正(Self - $18),然后再調(diào)用 TMyObject.ProcA,這時就是正確調(diào)用對象的成員函數(shù)了。由于每個類實現(xiàn)接口的順序不一定相同,因此對于相同的接口在不同的類中實現(xiàn),就有不同的接口跳轉(zhuǎn)表(當然,可能編輯器能夠聰明地檢查到一些類的“接口跳轉(zhuǎn)表”偏移量相同,也可以共享使用)。
通過這里得到了解決問題的關(guān)鍵,如果能得到接口的偏移地址,那么就可以得到對象實例
呵呵~~看到曙光了,加油! 
 尋找偏移地址 
眾所周知,所有的DELPHI對象都是從TObject繼承下來的,而創(chuàng)建對象也是通過
class function TObject.InitInstance(Instance: Pointer): TObject;
來分配內(nèi)存空間的,仔細分析這段代碼。
class function TObject.InitInstance(Instance: Pointer): TObject;
{$IFDEF PUREPASCAL}
var
  IntfTable: PInterfaceTable;
  ClassPtr: TClass;
  I: Integer;
begin
  FillChar(Instance^, InstanceSize, 0);
  PInteger(Instance)^ := Integer(Self);
  ClassPtr := Self;
  while ClassPtr <> nil do
  begin
    IntfTable := ClassPtr.GetInterfaceTable;
    if IntfTable <> nil then
      for I := 0 to IntfTable.EntryCount-1 do
  with IntfTable.Entries[I] do
  begin
    if VTable <> nil then
      //就是它了IOffset,它就是接口的偏移地址
      PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);
  end;
    ClassPtr := ClassPtr.ClassParent;
  end;
  Result := Instance;
end;
找到了IOffset,在跟蹤發(fā)現(xiàn)它屬于 接口標識的接口項(PInterfaceEntry)
  PInterfaceEntry = ^TInterfaceEntry;
  TInterfaceEntry = packed record
    IID: TGUID;
    VTable: Pointer;
    IOffset: Integer;
    ImplGetter: Integer;
  end;
問題出來了,得到PInterfaceEntry 就得到了一切 
輕松得到PInterfaceEntry 
Var
  eResourceObj: TResource;
  eEntry: PInterfaceEntry;
  eAutoObjFactory: TAutoObjectFactory;
Begin
  eResource:= CreateComObject(CLASS_Resource) as IResource;
  //得到類工廠
  eAutoObjFactory:= TAutoObjectFactory(ComClassManager.GetFactoryFromClassID(CLASS_Resource));
  //得到接口標識的接口項
  eEntry:= eAutoObjFactory.DispIntfEntry;
  //IOffset為接口的偏移地址,eResource減去IOffset所得到的地址就是對象實例
  eResourceObj:= TResource(Integer(eResource)-eEntry.IOffset);
  eResourceObj.Path:= '這里設置不同的值'';
End;  
 
結(jié)論 
費勁周折得來的結(jié)果,可能對整個問題并沒有太多的意義
但是,過程確實非常有意義,通過這個過程讓我對DELPHI對象和接口的實質(zhì)有了更深層次的了解。