知識需要不斷積累、總結和沉淀,思考和寫作是成長的催化劑
內容目錄
一、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用的不多,充其量就是調用。做底層嵌入式開發應該用的比較多,比如設備打印機驅動等。了解總沒壞處,拜了個拜