天天看點

.NET進階篇07-.NET和COM

知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑

内容目錄

一、COM和.NET中繼資料記憶體管理接口注冊線程編組二、.NET用戶端調用COM元件三、COM用戶端調用.NET元件四、嵌入互操作類型五、平台調用DllImport六、等等

一、COM和.NET

COM元件對象模型是在.NET之前的一種程式設計規範,它允許不同的語言之間可以互相操作。由于COM規範比較複雜,系統資料庫,記憶體對象管理,錯誤處理機制都和.NET不同,.NET做為其後秀,應用起來更簡單,但一般不會因為新技術可用就重寫已有的代碼,是以就引來COM的互操作性

我們可能不必編寫COM元件,但了解是有用的。經常會遇到嵌入互操作類型,為COM設定互操作問題

先看一下COM的一些基本概念,挑了幾個重要的也是比較好了解的

中繼資料

COM的中繼資料資訊存儲在tlb類型庫中,包含接口、方法和參數名稱等,在.NET程式集中中繼資料都存儲在程式集中的。

記憶體管理

我們知道.NET托管對象的記憶體釋放都有垃圾回收器GC完成,不同于COM,COM依賴引用計數,

接口

COM三個基本接口,

IClassFactory、IUnknown、Idispatch

IClassFactory,每個元件都有一個相關的類廠用于建立COM元件對象。非托管對象,用戶端是無法直接New對象的,是以隻能通過交給類廠來建立執行個體然後把執行個體的指針交給用戶端

每個COM對象必須實作IUnknown接口,QueryInterface用于查詢元件實作的其它接口,說白了也就是看看這個元件的父類中還有哪些接口類,AddRef()遞增引用計數,Release()遞減引用計數,為0後就銷毀對象

IDispatch排程接口派生自IUnknown接口,在其基礎上又增加了GetIDsOfNames()和Invoke(),調用接口會建立方法或屬性對應的調用ID映射表,這樣調用時先擷取根據名字擷取排程ID然後Invoke調用。因為并不是所有的語言(用戶端)(像一些js腳本語言)都支援指針,也就不能通過虛函數表來調用,是以用排程接口增加函數ID映射。

注冊

.NET中區分私有程式集和共享程式集。在COM中,通過系統資料庫配置的所有元件都是全局可用的。所有COM對象都有一個唯一辨別符CLSID類ID,建立COM對象時,COM API調用CoCreateInstacne()方法,在系統資料庫中查找CLSID的dll或exe路徑,然後加載,執行個體化元件

線程

COM使用單元模型,單元模型有

單線程單元模型STA和多線程單元模型MTA

STA單線程單元模型,在Winfrom程式中經常看到Main入口函數上面标記STAThread特性。在STA中隻允許建立執行個體的線程通路元件。一個程序中也可以包含多個STA

MTA多線程單元模型,在MTA中,多個線程可以同時通路元件

編組

.NET和COM之間的資料傳遞必須經過轉換,這種機制就是編組(marshaling)。轉換過程取決于資料類型。簡單的資料類型如byte、short、int和long屬性blittable類型,在com和net中是一樣的表示方法,其他nonblittable類型的則需要進行轉換,當然會有些開銷

COM資料類型 .Net資料類型
SAFEARRAY Array
VARIANT Object
BSTR String
Iunknown,Idispatch

二、.NET用戶端調用COM元件

由于COM對象和.NET對象在生命周期、記憶體管理、接口服務上的差異,運作時提供了包裝類來使其互相調用。托管用戶端調用 COM 對象方法時,運作時就會建立一個

運作時可調用包裝器 (RCW)

來封送引用機制之間的差異。 也會建立了一個

COM 可調用包裝器 (CCW)

來逆轉此過程

.NET進階篇07-.NET和COM

三、COM用戶端調用.NET元件

沒寫過COM,也不是很了解,但一些約定規範必須遵守,原理和.NET用戶端調用COM元件類似

比如在C#類庫的AssemblyInfo.cs中修改

// 将 ComVisible 設定為 false 使此程式集中的類型
// 對 COM 元件不可見。如果需要從 COM 通路此程式集中的類型,
// 則将該類型上的 ComVisible 特性設定為 true。
[assembly: ComVisible(false)]
           

在程式集屬性中勾選COM互操作注冊

.NET進階篇07-.NET和COM

然後任何一個需要暴露給COM用戶端的都需要有接口

[ComVisible(true)]  
[Guid("35A5CE1E-551C-41EC-81D4-005318550119")]  
public interface IMyClass  
{  
    void Initialize();  
    void Dispose();  
    int Add(int x, int y);
}  
           

編譯時候因為勾選的為COM互操作注冊,是以需要以管理者運作的才能注冊成功

四、嵌入互操作類型

引用

PIA

(主互操作程式集,COM元件生成)時,可以設定是否嵌入互操作類型。嵌入互操作類型時(True)則PIA不随着程式一起部署,程式隻是引用COM中的類型資訊,這樣的好處就是可以部署到不同COM版本的環境中。比如常用的Office開發Microsoft.Office.Interop.Excel,設定嵌入互操作類型,就可以不依賴office版本。改為互操作false後也就将PIA複制到本地

有時候會無法嵌入互操作類型請改為适當的接口,單純一點就修改嵌入互操作設為false,OK編譯通過。不太單純的,可以修改建立對象的方式,像下面這樣,直接執行個體化的普通類,無法嵌入互操作類型

Application excelApp = new ApplicationClass();

Application excelApp = new Application()

這樣是可以的,Application雖然是一個接口,理論上應該不能執行個體化的,當它上面标記了

[CoClass(typeof (ApplicationClass))

告訴運作時CLR,當有人要建立類型為Application的執行個體時,它實際上應該繼續建立ApplicationClass的執行個體。

用COM接口的可以嵌入,直接使用coclass的無法嵌入

五、平台調用DllImport

還有一些非托管庫不包含COM對象,隻包含倒出的函數,這時候需要使用

平台調用服務(P-Invoke)

,CLR會加載包含所需調用函數的dll,并編組參數。在C++的非托管庫中使用dllexport暴露函數,在C#中使用dllimport導入。基本文法如下

[DLLImport(“DLL檔案”)]

修飾符 extern 傳回變量類型 方法名稱 (參數清單)

dllimport在命名空間System.Runtime.InteropServices下,該特性用于對照非托管庫中導出的函數

[AttributeUsage(AttributeTargets.Method)]
public class DllImportAttribute: System.Attribute
{
   public DllImportAttribute(string dllName) {…}    //定位參數為dllName
   public CallingConvention CallingConvention;      //入口點調用約定
   public CharSet CharSet;                              //入口點采用的字元接
   public string EntryPoint;                //入口點名稱
   public bool ExactSpelling;               //是否必須與訓示的入口點拼寫完全一緻,預設false
   public bool PreserveSig;                 //方法的簽名是被保留還是被轉換
   public bool SetLastError;                //FindLastError方法的傳回值儲存在這裡
   public string Value { get {…} }
}
           

需要注意的就是資料類型的映射,必須映射到.NET資料類型上。可以使用

P/Invoke Interop Assistant

工具,它支援托管代碼和非托管代碼之間的方法簽名的轉換,可以直接生成調用代碼

.NET進階篇07-.NET和COM

一般會在一些特殊場合來調用win 32的api,比如像輸入法程式設定永遠不擷取焦點,一些任務處理時不希望使用者點選别的操作(當然窗體也不能崩了),這時候可以使用下面設定窗體控件不可用

[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int wndproc);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

public const int GWL_STYLE = -16;
public const int WS_DISABLED = 0x8000000;
public static void SetControlEnabled(Control c, bool enabled)
{
     if (enabled)
     { 
        SetWindowLong(c.Handle, GWL_STYLE, (~WS_DISABLED) & GetWindowLong(c.Handle, GWL_STYLE));
    }
else
{ 
        SetWindowLong(c.Handle, GWL_STYLE, WS_DISABLED + GetWindowLong(c.Handle, GWL_STYLE)); 
    }
}
           

六、等等

關于COM也隻是知曉一二,平常主要寫業務,COM用的不多,充其量就是調用。做底層嵌入式開發應該用的比較多,比如裝置列印機驅動等。了解總沒壞處,拜了個拜

繼續閱讀