用C#做WinForm程序時間長了難免會遇到和COM組件打交道的地方用什麼方式創建COM對象也成了我們必須面對的一個問題據我所知道的創建COM對象的方法一共有以下幾種:
使用NET包裝COM組件
這是最簡單的就是導入COM組件所在的DLL讓IDE生成NET一個IL包裝加到項目中這樣原來COM裡面所有實現了IDispatchDual的COM類型及其相關類型就可以直接在NET程序裡面使用比如以前在時代想要寫自己的基於IE的浏覽器就得手動加入與IWebBrowser接口相關的DLL這種方式是大家最常用的也是最傻瓜化的因此也沒什麼可解釋的
但是這種方式有個至命的缺點——不是所有的COM對象都能用這種方式導出正如前面所說的只有實現了IDispatchDual類型的接口才支持被導出而且面對不同版本的COM或許會生成不一樣的導出DLL比如說A機器上寫代碼時導入了一個Jet版本的包裝DLL代碼編譯了拿到B機器上去運行但是B機器上的Jet版本是的就可能會出現運行時錯誤
用反射動態創建
包括使用TypeGetTypeFromCLSID和TypeGetFromProgID兩種方法獲取COM對象的Type再創建這種方式也好理解就是說使用這兩個方法之前必須得知道COM對象的GUID或ProgID好在這也不是什麼難事一般我們要使一個COM對象多多少少都了解一些這個COM對象的GUID或ProgID信息用這種方獲取到了一個Type對象後就可以用NET裡面通用的反射創建對象的方法來做了
這裡給出一個創建JetEngine 的COM對象的代碼實例:
public object GetActiveXObject(Guid clsid)
{
Type t = TypeGetTypeFromCLSID(clsid);
if (t == null) return null;
return ActivatorCreateInstance(t);
}
Guid g = new Guid(DECFFCDBBFCFAEDA); // JetEngine
object jet = GetActiveXObject(g);
是不是覺得最後調用GetActiveXObject(g)的地方和IE裡面Javascript裡面用new ActiveXOjbect創建COM對象的方法很相像?
聲明CoCreateInstance外部函數用這個函數去創建相應的COM實例
M$在裡面包裝的WebBrowser控件內部就是用這個函數去創建的 使用這種方式創建COM就跟在C++裡面不什麼兩樣了有一點需要說明的是一般我們在代碼中引入外部方法的時候方法的參數和返回值的類型不一定是唯一的一種只要在邏輯上相互能轉化一般都可以使用
比如說如下幾種聲明都是正確的:
[return: MarshalAs(UnmanagedTypeInterface)]
[DllImport(oledll ExactSpelling=true PreserveSig=false)]
public static extern object CoCreateInstance([In] ref Guid clsid
[MarshalAs(UnmanagedTypeInterface)] object punkOuter int context [In] ref Guid iid);
[DllImport(oledll ExactSpelling=true PreserveSig=false)]
public static extern IntPtr CoCreateInstance([In] ref Guid clsid
IntPtr punkOuter int context [In] ref Guid iid);
[DllImport(oledll ExactSpelling=true)]
public static extern int CoCreateInstance([In] ref Guid clsid
IntPtr punkOuter int context [In] ref Guid iid [Out] out IntPtr pVoid);
[DllImport(oledll ExactSpelling=true)]
public static extern int CoCreateInstance([In] ref Guid clsid
[MarshalAs(UnmanagedTypeInterface)] object punkOuter int context
[In] ref Guid iid [MarshalAs(UnmanagedTypeInterface) Out] out object pVoid);
甚至於當你有裡面對應的接口類型的聲明的時候完全可以把上面的object或IntPtr換成相應的接口類型前提是你的接口類型的聲明一定要正確讀者中用C++做過COM的一定對這種方式記憶猶新吧只不過這裡不再需要什麼CoInitialize和CoUninitializeNET內部自己幫你搞定了順便提一下上面例子中的object與IntPtr聲明是相通的我們可以用MarshalGetObjectForIUnknown和MarshalGetIUnknownForObject這兩個方法在object和IntPtr之間互轉前題當然是這兩種方式所指向的都是COM對象才行這種方式提供的傳入參數最多創建對象也最靈活
直接聲明空成員的類
可能很多程序員對於這個不太理解這是什麼意思沒關系咱還是用代碼來說話
[ComImport Guid(DECFFCDBBFCFAEDA)]
public class JetEngineClass
{
}
[ComImport CoClass(typeof(JetEngineClass)) Guid(FDFFDBBFCFAEDA)]
public interface IJetEngine
{
void CompactDatabase(
[In MarshalAs(UnmanagedTypeBStr)] string SourceConnection
[In MarshalAs(UnmanagedTypeBStr)] string Destconnection
);
void RefreshCache([In MarshalAs(UnmanagedTypeInterface)] object Connection);
}
JetEngineClass engine = new JetEngineClass();
IJetEngine iengine = engine as IJetEngine;
// iengine即是所要用的接口的引用
大家看到了上面聲明的JetEngineClass類只有一個單單的類聲明但是沒有一個成員聲明但是和一般的類聲明有些不一樣的是這個類多了兩個特性(Attribute)把這個類和COM對象聯系在一起的就是這兩個特性了其中一個是ComImportAttribute這個特性指明了所作用的類是從COM對象中來的GuidAttribute指明了COM對象的GUID也就是說明了創建這個COM需用到的GUID有了這兩個特性以後這個類就不是一個普通的類了當我們使用new去創建實例的時候CLR看到了聲明的這兩特性就知道要創建的是一個COM對象根據提供的GUID也就能創建出指定的COM對象並和new返回的對象實例關聯在一起了
終上種方法我們可以看出來第一種方式只對特定的COM對象有效不具有通用性第二種方式只需要知道COM對象的CLSID或PROGID就可以了是我們在NET裡平時比較常用的創建COM對象的方法第三種方式需要自己聲明一個外部方法而且需要傳入若干的參數還需要知道COM對象模型是單線程呢還是多線程進程內呢還是進程外兩個字麻煩對CoCreateInstance這個方法不是很熟悉的人來說用起來就不那麼順手了第四種方式用起來最像是NET的方式也最簡單省事和其它NET對象的創建方式最為接近四種方法各有各有好處我覺得簡單的COM對象用第二種和第四種是最好的(我個人來說最喜歡第四種)又不生成額外的程序集要是COM對象相關的比較多比如說Excel之類的COM對象我建議還是用導入類型庫包裝吧雖然是有可能出現版本問題但這種應該很容易要求目標機器上運行的COM版和開發的時候一致的更何況版本問題也不是%出現只是很少一部分會出這樣的問題最不推薦的就是第三種方式了這種方式在我看來唯一用到的地方就是使用IntPtr作為COM對象和接口的指針的時候或者是想要在創建COM對象的時候對參數作最靈活的控制的時候 因為其它三種方式既不能返回IntPtr指針(其實也可以通過前面提到的的Marshal類的方法把NET包裝的COM對象轉成指針)也不能提供與直接調用CoCreateInstance函數提供最全面的參數相匹配的方式
最後提個小問題
讀者有興趣的話可以去看看這幾種方式(不包括第三種)生成的COM對象的引用的類型是否是一致的也就是用GetType得到的Type是否是一致的
大家猜猜這段代碼運行後iengine的類型會是什麼(GetType的結果) 會和engine的類型一樣嗎?
JetEngineClass engine = new JetEngineClass();
IJetEngine iengine = engine as IJetEngine;
// iengine即是所要用的接口的引用
IntPtr p = MarshalGetIUnknownForObject(engine);
iengine = MarshalGetObjectForIUnknown(p) as IJetEngine;
我這裡就不給出結果了留給讀者自行去驗證吧另外如果大家還發現NET中有其它的創建COM對象的方式也盡指教一二本人將不甚感激
From:http://tw.wingwit.com/Article/program/net/201311/13971.html