.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 Object

二、.NET客戶端調用COM組件

由於COM對象和.NET對象在生命週期、內存管理、接口服務上的差異,運行時提供了包裝類來使其互相調用。託管客戶端調用 COM 對象方法時,運行時就會創建一個運行時可調用包裝器 (RCW)來封送引用機制之間的差異。 也會創建了一個 COM 可調用包裝器 (CCW) 來逆轉此過程

三、COM客戶端調用.NET組件

沒寫過COM,也不是很瞭解,但一些約定規範必須遵守,原理和.NET客戶端調用COM組件類似
比如在C#類庫的AssemblyInfo.cs中修改

// 將 ComVisible 設置爲 false 使此程序集中的類型
// 對 COM 組件不可見。如果需要從 COM 訪問此程序集中的類型,
// 則將該類型上的 ComVisible 特性設置爲 true。
[assembly: ComVisible(false)]

在程序集屬性中勾選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工具,它支持託管代碼和非託管代碼之間的方法簽名的轉換,可以直接生成調用代碼

一般會在一些特殊場合來調用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用的不多,充其量就是調用。做底層嵌入式開發應該用的比較多,比如設備打印機驅動等。瞭解總沒壞處,拜了個拜

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