在.Net 中枚舉COM對象的方法和屬性名稱

來源:互聯網
上載者:User
對象 在.Net 中枚舉COM對象的方法和屬性名稱

Author:Zee

恩,以前滿世界問過這個問題,沒有人理偶的說,還是自己動手搞定比較好。

一般來說,一個COM對象在提供的時候,通常還會提供一個類型庫,在其中定義了COM對象的所有方法名稱、參數名稱、屬性名稱等等資訊。我們要做的就是從類型庫中取出這些資訊。
當然,某些只供C++程式員使用的COM對象沒有類型庫,而代之以C++的標頭檔和/或idl檔案,對這種情況,一般沒有辦法在程式中枚舉出對象的方法屬性:畢竟去找C++標頭檔不太現實,何況在非開發環境下,根本就沒有標頭檔的說。
因此,我們將討論當COM對象存在TypeLib的情況下,枚舉方法/屬性名稱的問題。
從COM對象定位到TypeLib
在一般情況下,COM對象的TypeLib資訊儲存在註冊表中:在HK_CLASSROOT\CLSID\{ClassID}\的登錄機碼下,有一個名為TypeLib的子項,其中定義了這個COM物件類型庫的ID;而在HK_CLASSROOT\TypeLib 登錄機碼下,列舉了系統中所有TypeLib。
看看我們首先要做什麼:從ProgID 取得 ClassID,這個工作可以通過調用COM 基礎庫的 CLSIDFromProgID 函數來完成,在Platform SDK中,該函數的定義如下:
HRESULT CLSIDFromProgID(
  LPCOLESTR lpszProgID,
  LPCLSID pclsid

);
為了在.Net中使用這個函數,我們用DllImport Attribute 把這個函數引入.Net 中:
class UnsafeNativeMethods{
[DllImport("ole32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
public static extern void CLSIDFromProgID([In,MarshalAs(UnmanagedType.BStr)] string lpszProgID,[Out]out Guid pclsid);
………
然後,我們可以在.Net 中調用這個函數取得ClassID了:
Guid clsid;
UnsafeNativeMethods.CLSIDFromProgID(progID,out clsid);
OK, 升級寶物Class ID 入手,Level Up!Strength + 3, Life + 5,必殺技 dll import 習得。 :) 下一個任務:取得TypeLib。
l         取得TypeLib。
為訪問TypeLib,COM 提供了二個介面:ITypeLib 和 ITypeInfo,其中ITypeLib 提供對 TypeLib 的訪問,而ITypeInfo 則表示TypeLib中定義的某一項ITypeInfo。
要獲得ITypeInfo,COM有二個函數可以做這件事情:LoadTypeLib 和 LoadRegTypeLib。其中 LoadTypeLib 需要 TypeLib 檔案的路徑作為參數,而LoadRegTypeLib 則根據TypeLib的TypeLib ID和TypeLib的版本號碼取得 ITypeLib。在這裡,我們用LoadRegTypeLib來取得ITypeLib 介面。
先來準備需要的參數:TypeLibID和TypeLib的版本號碼,這些資訊需要從註冊表裡得到:
RegistryKey regKey = Registry.ClassesRoot;
regKey = regKey.OpenSubKey("CLSID\\{" + clsid.ToString() + "}\\TypeLib");
Guid typeLibID = new Guid(regKey.GetValue("").ToString());
//Get TypeLib Versions
short iMajorVer,iMinusVer;
regKey = Microsoft.Win32.Registry.ClassesRoot;
regKey = regKey.OpenSubKey("TypeLib\\{" + typeLibID.ToString() + "}");
string[] aryTemp = regKey.GetSubKeyNames();
string sVersion = aryTemp[0];
aryTemp = sVersion.Split('.');
iMajorVer = short.Parse(aryTemp[0],System.Globalization.NumberStyles.AllowHexSpecifier);
iMinusVer = short.Parse(aryTemp[1] ,System.Globalization.NumberStyles.AllowHexSpecifier);
這裡要注意一點:在註冊表裡記錄的TypeLib版本號碼是以十六進位格式表示的,運氣好的話,你會發現類似”1.a”之類的版本號碼,所以我們最好把它們看成16進位來轉換。
現在可以調用LoadRegTypeLib 了,和CLSIDFromProgID一樣,先import進來:
這是LoadRegTypeLib 的函數原型:
HRESULT LoadRegTypeLib( 
  REFGUID  rguid,             
  unsigned short  wVerMajor,  
  unsigned short  wVerMinor,  
  LCID  lcid,                 
  ITypeLib FAR* FAR*  pptlib  

);
恩,在.Net裡,這個函數是這個樣子的:
[DllImport("oleaut32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
[LCIDConversion(3)]
public static extern UCOMITypeLib LoadRegTypeLib(ref Guid rguid, [In,MarshalAs(UnmanagedType.U2)]short wVerMajor, [In,MarshalAs(UnmanagedType.U2)]short wVerMinor);
哈,一個小小的技巧:在.Net 的定義裡,偶沒有定義原型裡 lcid 這個參數,這是因為偶應用了LCIDConversionAttribute,這個Attribute 意思就是說這個方法需要一個LCID做參數,參數的位置嘛:[LCIDConversion(3)]——第三個參數。這樣在調用這個方法的時候,.Net 的封送拆收器將自動提供 LCID 參數。不錯把:)
另外,在.Net Framwork的System.Runtime.InteropServices 命名空間裡,定義了一些常用COM Interface的.Net託管定義,雖然不多,但幸運的是我們要用到的ITypeLib和 ITypeInfo這二個介面都有,也就是 System.Runtime.InteropService. UCOMITypeLibSystem.Runtime.InteropService. UCOMITypeInfo。這就省下了我們自己定義介面的工作。
好了,接下來的事情狠簡單了:
UCOMITypeLib typeLib;
typeLib = UnsafeNativeMethods.LoadRegTypeLib(ref typeLibID,iMajorVer,iMinusVer);
Bingo!Mission Complete!只剩下最後一個任務:定位到對應我們的COM對象的ITypeInfo,並從中取出我們需要的資訊。
ITypeInfo介面的GetITypeInfo方法和GetITypeInfoCount方法一起提供了遍曆TypeLib中所有ITypeInfo的能力,不過既然我們手上有COM對象的ClassID,利用GetITypeInfoOfGuid 方法就可以獲得COM對象的ITypeInfo了。
UCOMITypeInfo ITypeInfo;
typeLib.GetITypeInfoOfGuid(ref clsid,out ITypeInfo);
拿到ITypeInfo之後,首先我們需要看看這個ITypeInfo裡有多少方法/屬性,這需要我們調用它的GetTypeAttr 方法獲得TYPEATTR結構。
TYPEATTR typeattr;
IntPtr p_typeattr = IntPtr.Zero;
ITypeInfo.GetTypeAttr(out p_typeattr);
typeattr = (TYPEATTR)Marshal.PtrToStructure(p_typeattr,typeof(TYPEATTR));
獲得TYPEATTR結構有那麼一點點麻煩,因為 .Net的不支援非託管簽名的 TYPEATTR ** 參數,所以只有使用引用 IntPtr 參數定義 GetTypeAttr。然後我們需要用Marshal.PtrToStructure將資料從非託管記憶體塊封送到託管對象。在TYPEATTR結構中,cFuns欄位表示當前TrpeInfo描述的函數數目,而每個函數的描述則是通過ITypeInfo的GetFuncDesc方法取得的FUNCDESC結構描述的。
if(typeattr.cFuncs > 0)
{
for(int i=0;i<typeattr.cFuncs;++i)
{
//Get FUNCDESC struct
FUNCDESC funcdesc;
IntPtr p_funcDesc;
ITypeInfo.GetFuncDesc(i,out p_funcDesc);
funcdesc = (FUNCDESC)Marshal.PtrToStructure(p_funcDesc,typeof(FUNCDESC));
……
和TYPEATTR一樣,FUNCDESC結構也需要Marshal.PtrToStructure處理一下,偶就不多說了。
討厭的是:FUNCDESC結構裡並沒有函數的名稱,我們只能通過它的memid欄位和invkind欄位知道這個函數的成員ID和函數的類型。函數的類型是我們需要的:它告訴我們這個函數是一個方法或者是一個屬性的Get/Set方法,而名稱這個東西,我們還得求助於ITypeInfo:GetNames 方法擷取具有指定成員ID的成員名稱,它的返回一個string數組,對方法而言,這個數組第一個元素是方法名稱,後面的元素則是方法的參數名,而對屬性而言,屬性名稱也出現在數組的第一個元素。
好了,現在除了一件事情,該說的偶已經都說了,我們已經知道了如何從ITypeInfo獲得方法/屬性的名稱,至於如何如何先建立二個空的Collection分別用於存放方法和屬性名稱;如何如何遍曆ITypeInfo的所有FuncDesc,根據每個不同的函數類型向對應的Collection中插入元素,這些簡單操作偶就不想多說了。我們剩下的唯一的問題是:對所有VB產生的COM對象,按照上面的步驟走下來,我們什麼方法屬性也看不到。
原因嘛,用OleView看一下這些dll的TypeLib就明白了:VB會產生一個名為”_”+類名的類介面,這個介面繼承自IDispatch,所有的方法屬性都在這個介面上定義,而實作類別只是簡單的實現這個介面,在它的TypeLib裡,真正對應ClassID的實作類別裡沒有任何成員。
既然知道了原因,辦法也就有了:我們在枚舉一個COM對象的所有方法/屬性時,不應該只枚舉僅對應它自己ClassID的TypeInfo,這個TypeInfo繼承的所有其他介面中定義的方法/屬性也要照樣拿出來,而要定位到它繼承的其他介面,我們要做的事情其實和遍曆這個ITypeInfo的所有FUNCDESC差不多:
if(typeattr.cImplTypes > 0)
{
for(int i=0;i<typeattr.cImplTypes;++i)
{
       int href;
       UCOMITypeInfo imptypeinfo;
       typeinfo.GetRefTypeOfImplType(i,out href);
       typeinfo.GetRefTypeInfo(href,out imptypeinfo);
       //Now we can do the same thing to the imptypeinfo like typeinfo
……
}
}

TYPEATTR的cImplTypes欄位表示這個ItypeInfo實現的介面數目,ITypeInfo的GetRefTypeOfImplType 方法擷取對某個已實現介面的控制代碼的引用,而GetRefTypeInfo 方法從這個控制代碼的引用擷取該介面的ITypeInfo。很明顯,我們可以寫一個遞迴函式來走遍所有COM對象實現的介面,而且我們可以確信這個遞迴是有出口的:因為COM裡所有的介面歸根到底都派生自“我不知道”介面 。^-^
最後,我想在大多數情況下,你不會希望在COM對象的方法列表裡看到QueryInterface或者AddRef這類IUnknown介面的方法,而IDispatch介面那些類似Invoke之類的方法想來有興趣的人也不多,不過反正這種底層方法就那麼幾個,在你遍曆的時候盡可以判斷一下過濾掉這些方法名稱。

免責聲明:
在本文中,為了清晰起見,所有給出的代碼中都沒有錯誤處理。如果你在你的代碼中使用本文中的部分代碼,由此造成的諸如程式出錯、系統宕機、走路撞樹、手機爆炸、洪水毀堤、地球毀滅等等一切後果,本人概不負責。

相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。