1. 程式人生 > >.NET進階篇07-.NET和COM

.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用的不多,充其量就是呼叫。做底層嵌入式開發應該用的比較多,比如裝置印表機驅動等。瞭解總沒壞處,拜了個拜