最近遇到了好几起和 COM 相关的Dump,由于对 COM 整体运作不是很了解,所以分析此类dump还是比较头疼的,比如下面这个经典的 COM 调用栈。
0:044>~~[138c]swin32u!NtUserMessageCall+0x14:00007ffc`5c891184c3ret0:061>k#Child-SPRetAddrCallSite000000008c`00ffec6800007ffc`5f21bfbewin32u!NtUserMessageCall+0x14010000008c`00ffec7000007ffc`5f21be38user32!SendMessageWorker+0x11e020000008c`00ffed1000007ffc`124fd4afuser32!SendMessageW+0xf8030000008c`00ffed7000007ffc`125e943bxxx!DllUnregisterServer+0x3029f040000008c`00ffeda000007ffc`125e9685xxx!DllUnregisterServer+0x11c22b050000008c`00ffede000007ffc`600b50e7xxx!DllUnregisterServer+0x11c475060000008c`00ffee2000007ffc`60093ccdntdll!LdrpCallInitRoutine+0x6f070000008c`00ffee9000007ffc`60092eefntdll!LdrpProcessDetachNode+0xf5080000008c`00ffef6000007ffc`600ae319ntdll!LdrpUnloadNode+0x3f090000008c`00ffefb000007ffc`600ae293ntdll!LdrpDecrementModuleLoadCountEx+0x710a0000008c`00ffefe000007ffc`5cd7c00entdll!LdrUnloadDll+0x930b0000008c`00fff01000007ffc`5d47cf78KERNELBASE!FreeLibrary+0x1e0c0000008c`00fff04000007ffc`5d447aa3combase!CClassCache::CDllPathEntry::CFinishObject::Finish+0x28[onecore\com\combase\objact\dllcache.cxx@3420]0d0000008c`00fff07000007ffc`5d4471a9combase!CClassCache::CFinishComposite::Finish+0x4b[onecore\com\combase\objact\dllcache.cxx@3530]0e0000008c`00fff0a000007ffc`5d3f1499combase!CClassCache::FreeUnused+0xdd[onecore\com\combase\objact\dllcache.cxx@6547]0f0000008c`00fff65000007ffc`5d3f13c7combase!CoFreeUnusedLibrariesEx+0x89[onecore\com\combase\objact\dllapi.cxx@117]10(InlineFunction)--------`--------combase!CoFreeUnusedLibraries+0xa[onecore\com\combase\objact\dllapi.cxx@74]110000008c`00fff69000007ffc`6008a019combase!CDllHost::MTADllUnloadCallback+0x17[onecore\com\combase\objact\dllhost.cxx@929]120000008c`00fff6c000007ffc`6008bec4ntdll!TppTimerpExecuteCallback+0xa9130000008c`00fff71000007ffc`5f167e94ntdll!TppWorkerThread+0x644140000008c`00fffa0000007ffc`600d7ad1kernel32!BaseThreadInitThunk+0x14150000008c`00fffa3000000000`00000000ntdll!RtlUserThreadStart+0x21为了做一个简单的梳理,我们搭建一个简单的多语言 COM 互操作。
二:COM 多语言互操作
1. 背景
可能很多新生代的程序员都不知道 COM ,最多也只听过这个名词,其实在 Windows 上有海量的 COM 组件,这些组件信息都是注册在 HKEY_CLASSES_ROOT\CLSID 节点目录,截图如下:
这个和微服务中的 注册中心 是一个道理,这一篇我们用 C# 写一个COM组件,用 C++ 去调用。
2. C# 写一个 COM 组件
在属性面板中,选择 Build 选项卡,选中 Register for COM interop 选项即可。
3. 注册 COM 到注册表
要将 com组件 放到注册表,需要使用注册表编辑工具 regasm。
Microsoft Windows [版本 10.0.19042.746](c) 2020 Microsoft Corporation. 保留所有权利。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 程序集注册实用工具版本 4.8.4084.0(适用于 Microsoft .NET Framework 版本 4.8.4084.0)版权所有 (C) Microsoft Corporation。保留所有权利。成功注册了类型成功注册了导出到“D:\net6\ConsoleApp1\FlyCom\bin\Debug\FlyCom.tlb”的程序集和类型库C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64>从输出中可以看到已成功注册,并且生成了一个 FlyCom.tlb 代理文件,接下来可以到注册表中验证一下 GUID=270C3ED3-053D-4324-9176-9C3FA2BE58A7 注册项以及别名为 FlyCom.Show 的注册项。
4. 使用 C++ 调用
要想 C++ 调用 C# 写的 COM 组件,就像 RPC 调用一样,直接自动生成的代理文件即可,将 FlyCom.tlb 复制到 根目录,并且将程序改成 Win32 位,截图如下:
接下来就是完整的 C++ 代码。
#include<Windows.h>#include<string.h>#include<iostream>#import&#34;FlyCom.tlb&#34;named_guidsraw_interface_onlyusingnamespacestd;intmain(){CoInitialize(NULL);FlyCom::BaseFlyPtrptr;ptr.CreateInstance(&#34;FlyCom.Show&#34;);wchar_t*c=ptr->Show(L&#34;helloworld&#34;);wprintf(L&#34;%s&#34;,c);getchar();}将程序跑起来后,真的很完美。
从 C++ 调用 COM 的流程图可以很清楚的看到,这是面向接口编程的方式,非常完美。
三:COM 多语言互通原理
1. 架构图
千言万语不及一张图。
这就是 COM 能够实现多语言互通的规范,熟悉 C++ 的朋友肯定知道 vtable ,C++ 能够实现多态,全靠这玩意,COM 也是用了 vtable 这套模式,所以诸如 JAVA,C#,VBS 必须在二进制层面将代码组织成上图这种形式,才能实现 COM 的互通。
所以在 C# 中你看到的 DispId 特性就是为了按照 vtable 方式进行组织,对于 ole32 和 combase 这些 COM 运行环境的基石,我们后续用 windbg 来解读一下,这一篇就先到这里,希望对你有帮助。