聊一聊被 .NET程序员 遗忘的 COM 组件
一:背景
1.讲故事
最近遇到了好几起和 COM 相关的Dump,由于对 COM 整体运作不是很了解,所以分析此类dump还是比较头疼的,比如下面这个经典的 COM 调用栈。
0:044> ~~[138c]s
win32u!NtUserMessageCall+0x14:
00007ffc`5c891184 c3 ret
0:061> k
# Child-SP RetAddr Call Site
00 0000008c`00ffec68 00007ffc`5f21bfbe win32u!NtUserMessageCall+0x14
01 0000008c`00ffec70 00007ffc`5f21be38 user32!SendMessageWorker+0x11e
02 0000008c`00ffed10 00007ffc`124fd4af user32!SendMessageW+0xf8
03 0000008c`00ffed70 00007ffc`125e943b xxx!DllUnregisterServer+0x3029f
04 0000008c`00ffeda0 00007ffc`125e9685 xxx!DllUnregisterServer+0x11c22b
05 0000008c`00ffede0 00007ffc`600b50e7 xxx!DllUnregisterServer+0x11c475
06 0000008c`00ffee20 00007ffc`60093ccd ntdll!LdrpCallInitRoutine+0x6f
07 0000008c`00ffee90 00007ffc`60092eef ntdll!LdrpProcessDetachNode+0xf5
08 0000008c`00ffef60 00007ffc`600ae319 ntdll!LdrpUnloadNode+0x3f
09 0000008c`00ffefb0 00007ffc`600ae293 ntdll!LdrpDecrementModuleLoadCountEx+0x71
0a 0000008c`00ffefe0 00007ffc`5cd7c00e ntdll!LdrUnloadDll+0x93
0b 0000008c`00fff010 00007ffc`5d47cf78 KERNELBASE!FreeLibrary+0x1e
0c 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+0xa9
13 0000008c`00fff710 00007ffc`5f167e94 ntdll!TppWorkerThread+0x644
14 0000008c`00fffa00 00007ffc`600d7ad1 kernel32!BaseThreadInitThunk+0x14
15 0000008c`00fffa30 00000000`00000000 ntdll!RtlUserThreadStart+0x21
为了做一个简单的梳理,我们搭建一个简单的多语言 COM 互操作。
二:COM 多语言互操作
1. 背景
可能很多新生代的程序员都不知道 COM ,最多也只听过这个名词,其实在 Windows 上有海量的 COM 组件,这些组件信息都是注册在 HKEY_CLASSES_ROOT\CLSID 节点目录,截图如下:

这个和微服务中的 注册中心 是一个道理,这一篇我们用 C# 写一个COM组件,用 C++ 去调用。
2. C# 写一个 COM 组件
写一个 .NET Framework 4.8 下的 32bit FlyCom 组件,一个接口,一个实现类,具体原理后续再分析,先搭建尝尝鲜, 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}";
}
}
}
这里简单说一下:
- Guid
一个是接口(BaseFly) 的唯一码,即 IID 信息, 一个是 COM组件的 唯一码,叫做 CLSID。
- ProgId
因为 GUID 不方便记忆,所以给这个 COM组件 取一个别名叫 FlyCom.Show 。
- DispId
这个是为了遵循 COM多语言互通下的 vtable调用标准,表示第一个接口方法是 Show,后续再聊。
有了代码,接下来还要做三个配置。
- 对 COM 的可见性
修改 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)]
- 生成签名
一般来说,将 com 放到 注册表,最好都生成一个强签名,否则会有警告提示。

- 注册 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\x64
C:\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 /CodeBase
Microsoft .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 "FlyCom.tlb" named_guids raw_interface_only
using 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();
}
将程序跑起来后,真的很完美。

从 C++ 调用 COM 的流程图可以很清楚的看到,这是面向接口编程的方式,非常完美。
三:COM 多语言互通原理
1. 架构图
千言万语不及一张图。

这就是 COM 能够实现多语言互通的规范,熟悉 C++ 的朋友肯定知道 vtable ,C++ 能够实现多态,全靠这玩意,COM 也是用了 vtable 这套模式,所以诸如 JAVA,C#,VBS 必须在二进制层面将代码组织成上图这种形式,才能实现 COM 的互通。
所以在 C# 中你看到的 DispId 特性就是为了按照 vtable 方式进行组织,对于 ole32 和 combase 这些 COM 运行环境的基石,我们后续用 windbg 来解读一下,这一篇就先到这里,希望对你有帮助。
聊一聊被 .NET程序员 遗忘的 COM 组件的更多相关文章
- 关于APP程序员泡沫经济
这些年,移动互联网非常火,火到掀起学习iOS.安卓以及H5的热潮.有人将这些新技术作为自己的实力补充,增加竞争力:更多的人将它们作为主业,专职做移动开发.但是,即便有移动开发人员不断涌入,对整个行业来 ...
- [转]ThoughtWorks(中国)程序员读书雷达
http://agiledon.github.io/blog/2013/04/17/thoughtworks-developer-reading-radar/#rd?sukey=f64bfa68330 ...
- 【转载、推荐】不要自称是程序员,我十多年的 IT 职场总结
注评:一气读完后,有些和我的观点类似.这篇文章显然是外国老写的,但是不妨碍我们的跨国交流. 如果我可以给每个工程教育增加一门课,它不会涉及编译器.门电路或是时间复杂度,而是一门介绍行业现实的入门课,因 ...
- 【腾讯Bugly干货分享】聊一聊微信“小程序”
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ecdf5ef03abecd43216fd0 Dev Club 是一个交流移动 ...
- [No000033]码农网-如何锻炼出最牛程序员的编码套路
最近,我大量阅读了Steve Yegge的文章.其中有一篇叫"Practicing Programming"(练习编程),写成于2005年,读后令我惊讶不已: 与你所相信的恰恰相反 ...
- 【转载】张逸--ThoughtWorks(中国)程序员读书雷达
原文地址:ThoughtWorks(中国)程序员读书雷达 软件业的特点是变化.若要提高软件开发的技能,就必须跟上技术发展的步伐.埋首醉心于项目开发与实战,固然能够锤炼自己的开发技巧,却难免受限于经验与 ...
- PHP程序员,因该养成 7 个面向对象的好习惯
在 PHP 编程早期,PHP 代码在本质上是限于面向过程的.过程代码 的特征在于使用过程构建应用程序块.过程通过允许过程之间的调用提供某种程度的重用. 但是,没有面向对象的语言构造,程序员仍然可以把 ...
- 追访现代主流程序员的家庭事业观---禅宗派程序员KUROKY
Kuroky,一个被人遗忘的当代主流程序员. 在他的内心深处有着怎样的心路历程 他的快乐与悲伤,都是一个禅 独家专访kuroky: 记者:作为现代主流程序员,你内心的苦楚玉欢乐通过什么来发现? 大师: ...
- 总结一下ERP .NET程序员必须掌握的.NET技术
总结一下ERP .NET程序员必须掌握的.NET技术,掌握了这些技术工作起来才得心应手 从毕业做.NET到现在,有好几年了,自认为只能是达到熟练的水平,谈不上精通.所以,总结一下,自己到底熟练掌握 ...
随机推荐
- mysql 跨库事务XA
前一段时间在工作中遇到了跨库事务问题,后来在网上查询了一下,现在做一下整理和总结. 1.首先要确保mysql开启XA事务支持 SHOW VARIABLES LIKE '%XA%' 如果innodb_s ...
- Python3+Django2集成PayPal(贝宝)跨境支付三方接口以及订单查询和退款业务
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_157 如果您所在的公司涉及外贸或者跨境支付业务,那一定听说过大名鼎鼎的PayPal,总的来说,PayPal在跨国贸易里的优势还是比 ...
- 使用python3.7+Vue.js2.0+Django2.0.4异步前端通过api上传文件到七牛云云端存储
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_130 之前一篇文章是通过普通js+tornado来上传七牛云:使用Tornado配合七牛云存储api来异步切分上传文件,本次使用v ...
- 并发异步编程之争:协程(asyncio)到底需不需要加锁?(线程/协程安全/挂起/主动切换)Python3
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_208 协程与线程向来焦孟不离,但事实上是,线程更被我们所熟知,在Python编程领域,单核同时间内只能有一个线程运行,这并不是什么 ...
- Vue 计算属性 && 监视属性
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 & ...
- java学习第一天.day05
jvm的内存 栈:类方法使用后自动销毁,销毁的好处是释放内存 java方法执行时,在栈区执行 堆: 线程共享的一块内存区域 所有的对象实例以及 数组 都要在堆上分配 每次使用new ...
- z—libirary最新地址获取,zlibirary地址获取方式,zliabary最新地址,zliabary官网登录方式,zliabary最新登陆
Z-Library(缩写为z-lib,以前称为BookFinder)是Library Genesis的镜像,一个影子图书馆项目,用于对学术期刊文章.学术文本和大众感兴趣的书籍(其中一些是盗版的)进行文 ...
- 「JOI 2015 Final」分蛋糕 2
「JOI 2015 Final」分蛋糕 2 题解 这道题让我想起了新年趣事之红包这道DP题,这道题和那道题推出来之后的做法是一样的. 我们可以定义dp[i][len][1] 表示从第i块逆时针数len ...
- 第九十一篇:Vue 具名插槽作用域
好家伙, 1.作用域插槽 插槽在定义的时候,可以定义一些属性,便于在父组件中使用 来看看代码: Article.vue组件中: <template> <div class=" ...
- 【设计模式】Java设计模式 - 反射机制
[设计模式]Java设计模式 - 反射机制 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 目录 [设计模式]Java设计模式 - 反射 ...