老生常談.NET中的 COM 組件
目錄
- 什么是COM組件?
- 使用COM組件需要注意:
- 一:背景
- 1.講故事
- 二:COM 多語(yǔ)言互操作
- 1. 背景
- 2. C# 寫(xiě)一個(gè) COM 組件
- 3. 注冊(cè) COM 到注冊(cè)表
- 4. 使用 C++ 調(diào)用
- 三:COM 多語(yǔ)言互通原理
- 1. 架構(gòu)圖
什么是COM組件?
1.COM組件是以WIN32動(dòng)態(tài)鏈接庫(kù)(DLL)或可執(zhí)行文件(EXE)形式發(fā)布的可執(zhí)行代碼組成。
2.COM組件是一些小的二進(jìn)制可執(zhí)行文件,必須以二進(jìn)制的形式發(fā)布。
3.COM組件可以給應(yīng)用程序、操作系統(tǒng)以及其它組件提供服務(wù)。
4.自定義的COM組件可以在運(yùn)行時(shí)刻同其它組件連接起來(lái)構(gòu)成某個(gè)應(yīng)用程序。
5.COM組件必須是動(dòng)態(tài)鏈接的。
使用COM組件需要注意:
1.必須要保證升級(jí)應(yīng)用時(shí)不破壞與以前版本的向后兼容性;
2.必須要做到擴(kuò)展系統(tǒng)服務(wù)時(shí)不依賴于特定的操作系統(tǒng)。
COM組件不是一種計(jì)算機(jī)語(yǔ)言;
COM組件不是DLL,只是利用DLL來(lái)給組件提供動(dòng)態(tài)鏈接的能力;
COM組件不是API函數(shù)集;
COM組件不是類;
COM組件中的接口是一組由組件實(shí)現(xiàn)的,提供給客戶使用的函數(shù)(在COM中的接口是一個(gè)包含函數(shù)指針數(shù)組的內(nèi)存結(jié)構(gòu),數(shù)組元素是一個(gè)由組件實(shí)現(xiàn)的函數(shù)地址)。DLL的接口就是它所輸出的函數(shù)。
一:背景
1.講故事
最近遇到了好幾起和 COM
相關(guān)的Dump,由于對(duì) COM 整體運(yùn)作不是很了解,所以分析此類dump還是比較頭疼的,比如下面這個(gè)經(jīng)典的 COM 調(diào)用棧。
0:044> ~~[138c]swin32u!NtUserMessageCall+0x14:00007ffc`5c891184 c3 ret0:061> k # Child-SP RetAddr Call Site00 0000008c`00ffec68 00007ffc`5f21bfbe win32u!NtUserMessageCall+0x1401 0000008c`00ffec70 00007ffc`5f21be38 user32!SendMessageWorker+0x11e02 0000008c`00ffed10 00007ffc`124fd4af user32!SendMessageW+0xf803 0000008c`00ffed70 00007ffc`125e943b xxx!DllUnregisterServer+0x3029f04 0000008c`00ffeda0 00007ffc`125e9685 xxx!DllUnregisterServer+0x11c22b05 0000008c`00ffede0 00007ffc`600b50e7 xxx!DllUnregisterServer+0x11c47506 0000008c`00ffee20 00007ffc`60093ccd ntdll!LdrpCallInitRoutine+0x6f07 0000008c`00ffee90 00007ffc`60092eef ntdll!LdrpProcessDetachNode+0xf508 0000008c`00ffef60 00007ffc`600ae319 ntdll!LdrpUnloadNode+0x3f09 0000008c`00ffefb0 00007ffc`600ae293 ntdll!LdrpDecrementModuleLoadCountEx+0x710a 0000008c`00ffefe0 00007ffc`5cd7c00e ntdll!LdrUnloadDll+0x930b 0000008c`00fff010 00007ffc`5d47cf78 KERNELBASE!FreeLibrary+0x1e0c 0000008c`00fff040 00007ffc`5d447aa3 combase!CClassCache::CDllPathEntry::CFinishObject::Finish+0x28 [onecore\com\combase\objact\dllcache.cxx @ 3420] 0d 0000008c`00fff070 00007ffc`5d4471a9 combase!CClassCache::CFinishComposite::Finish+0x4b [onecore\com\combase\objact\dllcache.cxx @ 3530] 0e 0000008c`00fff0a0 00007ffc`5d3f1499 combase!CClassCache::FreeUnused+0xdd [onecore\com\combase\objact\dllcache.cxx @ 6547] 0f 0000008c`00fff650 00007ffc`5d3f13c7 combase!CoFreeUnusedLibrariesEx+0x89 [onecore\com\combase\objact\dllapi.cxx @ 117] 10 (Inline Function) --------`-------- combase!CoFreeUnusedLibraries+0xa [onecore\com\combase\objact\dllapi.cxx @ 74] 11 0000008c`00fff690 00007ffc`6008a019 combase!CDllHost::MTADllUnloadCallback+0x17 [onecore\com\combase\objact\dllhost.cxx @ 929] 12 0000008c`00fff6c0 00007ffc`6008bec4 ntdll!TppTimerpExecuteCallback+0xa913 0000008c`00fff710 00007ffc`5f167e94 ntdll!TppWorkerThread+0x64414 0000008c`00fffa00 00007ffc`600d7ad1 kernel32!BaseThreadInitThunk+0x1415 0000008c`00fffa30 00000000`00000000 ntdll!RtlUserThreadStart+0x21
為了做一個(gè)簡(jiǎn)單的梳理,我們搭建一個(gè)簡(jiǎn)單的多語(yǔ)言 COM 互操作。
二:COM 多語(yǔ)言互操作
1. 背景
可能很多新生代的程序員都不知道 COM ,最多也只聽(tīng)過(guò)這個(gè)名詞,其實(shí)在 Windows 上有海量的 COM 組件,這些組件信息都是注冊(cè)在 HKEY_CLASSES_ROOT\CLSID
節(jié)點(diǎn)目錄,截圖如下:
這個(gè)和微服務(wù)中的 注冊(cè)中心
是一個(gè)道理,這一篇我們用 C# 寫(xiě)一個(gè)COM組件,用 C++ 去調(diào)用。
2. C# 寫(xiě)一個(gè) COM 組件
寫(xiě)一個(gè) .NET Framework 4.8 下的 32bit FlyCom
組件,一個(gè)接口,一個(gè)實(shí)現(xiàn)類,具體原理后續(xù)再分析,先搭建嘗嘗鮮, C# 代碼如下:
namespace FlyCom{ [Guid("31A3CED7-B4F1-4D59-881A-EA1D7ABCC4CF")] public interface BaseFly {[DispId(1)]string Show(string str); } [Guid("270C3ED3-053D-4324-9176-9C3FA2BE58A7")] [ProgId("FlyCom.Show")] public class Fly : BaseFly {public string Show(string str){ return $"str={str}, length={str.Length}";} }}
這里簡(jiǎn)單說(shuō)一下:
1.Guid
一個(gè)是接口(BaseFly) 的唯一碼,即 IID 信息, 一個(gè)是 COM組件的 唯一碼,叫做 CLSID。
2.ProgId
因?yàn)?GUID 不方便記憶,所以給這個(gè) COM組件 取一個(gè)別名叫 FlyCom.Show
。
3.DispId
這個(gè)是為了遵循 COM多語(yǔ)言互通下的 vtable調(diào)用標(biāo)準(zhǔn),表示第一個(gè)接口方法是 Show
,后續(xù)再聊。
有了代碼,接下來(lái)還要做三個(gè)配置。
- 對(duì) COM 的可見(jiàn)性
修改 AssemblyInfo.cs 中的 ComVisible = true,參考如下:
// Setting ComVisible to false makes the types in this assembly not visible// to COM components. If you need to access a type in this assembly from// COM, set the ComVisible attribute to true on that type.[assembly: ComVisible(true)]
- 生成簽名
一般來(lái)說(shuō),將 com 放到 注冊(cè)表,最好都生成一個(gè)強(qiáng)簽名,否則會(huì)有警告提示。
- 注冊(cè) com 互操作
在屬性面板中,選擇 Build 選項(xiàng)卡,選中 Register for COM interop
選項(xiàng)即可。
3. 注冊(cè) COM 到注冊(cè)表
要將 com組件 放到注冊(cè)表,需要使用注冊(cè)表編輯工具 regasm
。
Microsoft Windows [版本 10.0.19042.746](c) 2020 Microsoft Corporation. 保留所有權(quán)利。C:\Users\Administrator>cd /d C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64>C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe D:\net6\ConsoleApp1\FlyCom\bin\Debug\FlyCom.dll /tlb:FlyCom.tlb /CodeBaseMicrosoft .NET Framework 程序集注冊(cè)實(shí)用工具版本 4.8.4084.0(適用于 Microsoft .NET Framework 版本 4.8.4084.0)版權(quán)所有 (C) Microsoft Corporation。保留所有權(quán)利。成功注冊(cè)了類型成功注冊(cè)了導(dǎo)出到“D:\net6\ConsoleApp1\FlyCom\bin\Debug\FlyCom.tlb”的程序集和類型庫(kù)C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64>
從輸出中可以看到已成功注冊(cè),并且生成了一個(gè) FlyCom.tlb
代理文件,接下來(lái)可以到注冊(cè)表中驗(yàn)證一下 GUID=270C3ED3-053D-4324-9176-9C3FA2BE58A7
注冊(cè)項(xiàng)以及別名為 FlyCom.Show
的注冊(cè)項(xiàng)。
4. 使用 C++ 調(diào)用
要想 C++ 調(diào)用 C# 寫(xiě)的 COM 組件,就像 RPC 調(diào)用一樣,直接自動(dòng)生成的代理文件即可,將 FlyCom.tlb
復(fù)制到 根目錄,并且將程序改成 Win32
位,截圖如下:
接下來(lái)就是完整的 C++ 代碼。
#include <Windows.h>#include <string.h>#include <iostream>#import "FlyCom.tlb" named_guids raw_interface_onlyusing namespace std;int main(){ CoInitialize(NULL); FlyCom::BaseFlyPtr ptr; ptr.CreateInstance("FlyCom.Show"); wchar_t* c = ptr->Show(L"hello world"); wprintf(L"%s", c); getchar();}
將程序跑起來(lái)后,真的很完美。
從 C++ 調(diào)用 COM 的流程圖可以很清楚的看到,這是面向接口編程的方式,非常完美。
三:COM 多語(yǔ)言互通原理
1. 架構(gòu)圖
千言萬(wàn)語(yǔ)不及一張圖。
這就是 COM 能夠?qū)崿F(xiàn)多語(yǔ)言互通的規(guī)范,熟悉 C++ 的朋友肯定知道 vtable
,C++ 能夠?qū)崿F(xiàn)多態(tài),全靠這玩意,COM 也是用了 vtable 這套模式,所以諸如 JAVA,C#,VBS 必須在二進(jìn)制層面將代碼組織成上圖這種形式,才能實(shí)現(xiàn) COM 的互通。
所以在 C# 中你看到的 DispId 特性就是為了按照 vtable 方式進(jìn)行組織,對(duì)于 ole32 和 combase 這些 COM 運(yùn)行環(huán)境的基石,我們后續(xù)用 windbg 來(lái)解讀一下,這一篇就先到這里,希望對(duì)你有幫助。
到此這篇關(guān)于 老生常談.NET中的 COM 組件的文章就介紹到這了,更多相關(guān).NET COM 組件內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
