.NET進階篇07-.NET和COM

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

內容目錄

1、COM和.NET元數據內存管理接口註冊線程編組2、.NET客戶端調用COM組件3、COM客戶端調用.NET組件4、嵌入互操做類型5、平臺調用DllImport6、等等編程

1、COM和.NET

COM組件對象模型是在.NET以前的一種編程規範,它容許不一樣的語言之間能夠互相操做。因爲COM規範比較複雜,註冊表,內存對象管理,錯誤處理機制都和.NET不一樣,.NET作爲其後秀,應用起來更簡單,但通常不會由於新技術可用就重寫已有的代碼,因此就引來COM的互操做性api

咱們可能沒必要編寫COM組件,但瞭解是有用的。常常會遇到嵌入互操做類型,爲COM設置互操做問題多線程

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

元數據

COM的元數據信息存儲在tlb類型庫中,包含接口、方法和參數名稱等,在.NET程序集中元數據都存儲在程序集中的。函數

內存管理

咱們知道.NET託管對象的內存釋放都有垃圾回收器GC完成,不一樣於COM,COM依賴引用計數,工具

接口

COM三個基本接口,IClassFactory、IUnknown、Idispatch
IClassFactory,每一個組件都有一個相關的類廠用於建立COM組件對象。非託管對象,客戶端是沒法直接New對象的,因此只能經過交給類廠來建立實例而後把實例的指針交給客戶端ui

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

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

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

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

3、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互操做註冊,因此須要以管理員運行的才能註冊成功

4、嵌入互操做類型

引用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的沒法嵌入

5、平臺調用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)); 
    }
}

6、等等

關於COM也只是知曉一二,日常主要寫業務,COM用的很少,充其量就是調用。作底層嵌入式開發應該用的比較多,好比設備打印機驅動等。瞭解總沒壞處,拜了個拜

相關文章
相關標籤/搜索