C#中COM操作實例化例子

 用C#做WinForm程序,時間長了難免會遇到和COM組件打交道的地方,用什麼方式創建COM對象也成了我們必須面對的一個問題.據我所知道的創建COM對象的方法一共有以下幾種:

用反射動態創建

    包括使用Type.GetTypeFromCLSID和Type.GetFromProgID兩種方法獲取COM對象的Type再創建.這種方式也好理解,就是說使用這兩個方法之前,必須得知道COM對象的GUID或ProgID,好在這也不是什麼難事,一般我們要使一個COM對象,多多少少都瞭解一些這個COM對象的GUID或ProgID信息.用這種方獲取到了一個Type對象後,就可以用.NET裏面通用的反射創建對象的方法來做了.

 

這裏給出一個創建JetEngine 的COM對象的代碼實例:

 

 1 public object GetActiveXObject(Guid clsid)
 2 {
 3     Type t = Type.GetTypeFromCLSID(clsid);
 4     if (t == nullreturn null;
 5 
 6     return Activator.CreateInstance(t);
 7 }
 8 
 9 Guid g = new Guid("DE88C160-FF2C-11D1-BB6F-00C04FAE22DA"); // JetEngine
10 object jet = GetActiveXObject(g);

是不是覺得最後調用GetActiveXObject(g)的地方和IE裏面Javascript裏面用new ActiveXOjbect創建COM對象的方法很相像?

 

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript> </SCRIPT>

聲明CoCreateInstance外部函數,用這個函數去創建相應的COM實例

    M$在2005裏面包裝的WebBrowser控件內部就是用這個函數去創建的, 使用這種方式創建COM,就跟在C++裏面不什麼兩樣了.有一點需要說明的是,一般我們在代碼中引入外部方法的時候,方法的參數和返回值的類型不一定是唯一的一種,只要在邏輯上相互能轉化,一般都可以使用.

比如說如下幾種聲明都是正確的:  

 

 

 1 [return: MarshalAs(UnmanagedType.Interface)]
 2 [DllImport("ole32.dll", ExactSpelling=true, PreserveSig=false)]
 3 public static extern object CoCreateInstance([In] ref Guid clsid, 
 4     [MarshalAs(UnmanagedType.Interface)] object punkOuter, int context, [In] ref Guid iid);
 5   
 6 [DllImport("ole32.dll", ExactSpelling=true, PreserveSig=false)]
 7 public static extern IntPtr CoCreateInstance([In] ref Guid clsid, 
 8     IntPtr punkOuter, int context, [In] ref Guid iid); 
 9 
10 [DllImport("ole32.dll", ExactSpelling=true)]
11 public static extern int CoCreateInstance([In] ref Guid clsid, 
12     IntPtr punkOuter, int context, [In] ref Guid iid, [Out] out IntPtr pVoid); 
13 
14 [DllImport("ole32.dll", ExactSpelling=true)]
15 public static extern int CoCreateInstance([In] ref Guid clsid, 
16     [MarshalAs(UnmanagedType.Interface)] object punkOuter, int context, 
17     [In] ref Guid iid, [MarshalAs(UnmanagedType.Interface), Out] out object pVoid); 

 甚至於當你有裏面對應的接口類型的聲明的時候,完全可以把上面的object或IntPtr換成相應的接口類型,前提是你的接口類型的聲明一定要正確.讀者中用C++做過COM的一定對這種方式記憶猶新吧,只不過這裏不再需要什麼CoInitialize和CoUninitialize,.NET內部自己幫你搞定了.順便提一下,上面例子中的object與IntPtr聲明是相通的,我們可以用Marshal.GetObjectForIUnknown和Marshal.GetIUnknownForObject這兩個方法在object和IntPtr之間互轉,前題當然是這兩種方式所指向的都是COM對象纔行.這種方式提供的傳入參數最多,創建對象也最靈活.

 

直接聲明空成員的類

    可能很多程序員對於這個不太理解這是什麼意思,沒關係咱還是"用代碼來說話".

 

 1 [ComImport, Guid("DE88C160-FF2C-11D1-BB6F-00C04FAE22DA")]
 2 public class JetEngineClass
 3 {
 4 }
 5 
 6 [ComImport, CoClass(typeof(JetEngineClass)), Guid("9F63D980-FF25-11D1-BB6F-00C04FAE22DA")]
 7 public interface IJetEngine
 8 {
 9     void CompactDatabase(
10         [In, MarshalAs(UnmanagedType.BStr)] string SourceConnection, 
11         [In, MarshalAs(UnmanagedType.BStr)] string Destconnection
12         );
13     void RefreshCache([In, MarshalAs(UnmanagedType.Interface)] object Connection);
14 }
15 
16 JetEngineClass engine = new JetEngineClass();
17 IJetEngine iengine = engine as IJetEngine;
18 // iengine即是所要用的接口的引用

 

 大家看到了上面聲明的JetEngineClass類只有一個單單的類聲明,但是沒有一個成員聲明,但是和一般的類聲明有些不一樣的是這個類多了兩個特性(Attribute),把這個類和COM對象聯繫在一起的就是這兩個特性了,其中一個是ComImportAttribute,這個特性指明瞭所作用的類是從COM對象中來的,GuidAttribute指明瞭COM對象的GUID,也就是說明了創建這個COM需用到的GUID。有了這兩個特性以後,這個類就不是一個普通的類了,當我們使用new去創建實例的時候,CLR看到了聲明的這兩特性就知道要創建的是一個COM對象,根據提供的GUID也就能創建出指定的COM對象,並和new返回的對象實例關聯在一起了。

使用.NET包裝COM組件

    這是最簡單的就是導入COM組件所在的DLL,讓IDE生成.NET一個IL包裝加到項目中,這樣原來COM裏面所有實現了IDispatch,Dual的COM類型及其相關類型就可以直接在.NET程序裏面使用,比如以前在2003時代,想要寫自己的基於IE的瀏覽器,就得手動加入與IWebBrowser2接口相關的DLL,這種方式是大家最常用的,也是最傻瓜化的,因此也沒什麼可解釋的.

    但是這種方式有個至命的缺點---不是所有的COM對象都能用這種方式導出.正如前面所說的,只有實現了IDispatch,Dual類型的接口才支持被導出,而且面對不同版本的COM或許會生成不一樣的導出DLL,比如說A機器上寫代碼時導入了一個Jet2.6版本的包裝DLL,代碼編譯了拿到B機器上去運行,但是B機器上的Jet版本是2.8的,就可能會出現運行時錯誤.

 

    終上4種方法我們可以看出來,第一種方式只對特定的COM對象有效,不具有通用性;第二種方式只需要知道COM對象的CLSID或PROGID就可以了,是我們在.NET裏平時比較常用的創建COM對象的方法;第三種方式需要自己聲明一個外部方法,而且需要傳入若干的參數,還需要知道COM對象模型,是單線程呢還是多線程,進程內呢還是進程外,兩個字"麻煩"。對CoCreateInstance這個方法不是很熟悉的人來說,用起來就不那麼順手了;第四種方式用起來最像是.NET的方式,也最簡單省事,和其它.NET對象的創建方式最爲接近。四種方法各有各有好處,我覺得簡單的COM對象,用第二種和第四種是最好的(我個人來說最喜歡第四種)又不生成額外的程序集;要是COM對象相關的比較多,比如說Excel之類的COM對象,我建議還是用導入類型庫包裝吧,雖然是有可能出現版本問題,但這種應該很容易要求目標機器上運行的COM版和開發的時候一致的,更何況版本問題也不是100%出現,只是很少一部分會出這樣的問題。最不推薦的就是第三種方式了,這種方式在我看來唯一用到的地方就是使用IntPtr作爲COM對象和接口的指針的時候,或者是想要在創建COM對象的時候,對參數作最靈活的控制的時候. 因爲其它三種方式既不能返回IntPtr指針(其實也可以通過前面提到的的Marshal類的方法把.NET包裝的COM對象轉成指針),也不能提供與直接調用CoCreateInstance函數提供最全面的參數相匹配的方式。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章