搜索

查看: 3086|回复: 11

[ASP.NET] 老生常谈.NET中的 COM 组件

[复制链接]
发表于 2023-5-4 11:32:22 | 显示全部楼层 |阅读模式
Editor 2023-5-4 11:32:22 3086 11 看全部
目录
  • 什么是COM组件?
  • 使用COM组件需要注意:
  • 一:背景
  • 1.讲故事
  • 二:COM 多语言互操作
  • 1. 背景
  • 2. C# 写一个 COM 组件
  • 3. 注册 COM 到注册表
  • 4. 使用 C++ 调用
  • 三:COM 多语言互通原理
  • 1. 架构图
    什么是COM组件?
            1.COM组件是以WIN32动态链接库(DLL)或可执行文件(EXE)形式发布的可执行代码组成。
            2.COM组件是一些小的二进制可执行文件,必须以二进制的形式发布。
            3.COM组件可以给应用程序、操作系统以及其它组件提供服务。
            4.自定义的COM组件可以在运行时刻同其它组件连接起来构成某个应用程序。
            5.COM组件必须是动态链接的。

    使用COM组件需要注意:
    1.必须要保证升级应用时不破坏与以前版本的向后兼容性;
    2.必须要做到扩展系统服务时不依赖于特定的操作系统。
            COM组件不是一种计算机语言;
            COM组件不是DLL,只是利用DLL来给组件提供动态链接的能力;
            COM组件不是API函数集;
            COM组件不是类;
            COM组件中的接口是一组由组件实现的,提供给客户使用的函数(在COM中的接口是一个包含函数指针数组的内存结构,数组元素是一个由组件实现的函数地址)。DLL的接口就是它所输出的函数。

    一:背景
    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 节点目录,截图如下:

    2022101910215118.png

    2022101910215118.png


    这个和微服务中的 注册中心 是一个道理,这一篇我们用 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}";
            }
        }
    }
    这里简单说一下:
    1.Guid
    一个是接口(BaseFly) 的唯一码,即 IID 信息, 一个是 COM组件的 唯一码,叫做 CLSID。
    2.ProgId
    因为 GUID 不方便记忆,所以给这个 COM组件 取一个别名叫 FlyCom.Show 。
    3.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 放到 注册表,最好都生成一个强签名,否则会有警告提示。

    2022101910215119.png

    2022101910215119.png


  • 注册 com 互操作
    在属性面板中,选择 Build 选项卡,选中 Register for COM interop 选项即可。

    2022101910215120.png

    2022101910215120.png


    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 的注册项。

    2022101910215121.png

    2022101910215121.png


    2022101910215122.png

    2022101910215122.png


    4. 使用 C++ 调用
    要想 C++ 调用 C# 写的 COM 组件,就像 RPC 调用一样,直接自动生成的代理文件即可,将 FlyCom.tlb 复制到 根目录,并且将程序改成 Win32 位,截图如下:

    2022101910215123.png

    2022101910215123.png


    接下来就是完整的 C++ 代码。
    #include
    #include
    #include
    #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();
    }
    将程序跑起来后,真的很完美。

    2022101910215224.png

    2022101910215224.png


    从 C++ 调用 COM 的流程图可以很清楚的看到,这是面向接口编程的方式,非常完美。

    三:COM 多语言互通原理
    1. 架构图
    千言万语不及一张图。

    2022101910215225.png

    2022101910215225.png


    这就是 COM 能够实现多语言互通的规范,熟悉 C++ 的朋友肯定知道 vtable ,C++ 能够实现多态,全靠这玩意,COM 也是用了 vtable 这套模式,所以诸如 JAVA,C#,VBS 必须在二进制层面将代码组织成上图这种形式,才能实现 COM 的互通。
    所以在 C# 中你看到的 DispId 特性就是为了按照 vtable 方式进行组织,对于 ole32 和 combase 这些 COM 运行环境的基石,我们后续用 windbg 来解读一下,这一篇就先到这里,希望对你有帮助。
    到此这篇关于 老生常谈.NET中的 COM 组件的文章就介绍到这了,更多相关.NET COM 组件内容请搜索知鸟论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持知鸟论坛
  • 回复

    使用道具 举报

    发表于 2023-6-29 00:54:57 | 显示全部楼层
    啤酒瓶空了缓 2023-6-29 00:54:57 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-6-29 04:42:15 | 显示全部楼层
    计划你大爷计j 2023-6-29 04:42:15 看全部
    楼主,我太崇拜你了!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-29 22:14:32 | 显示全部楼层
    当当当当裤裆坦 2023-6-29 22:14:32 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    回复

    使用道具 举报

    发表于 2023-6-30 09:17:24 | 显示全部楼层
    伊索谗言 2023-6-30 09:17:24 看全部
    其实我一直觉得楼主的品味不错!呵呵!知鸟论坛太棒了!
    回复

    使用道具 举报

    发表于 2023-6-30 09:52:39 | 显示全部楼层
    冀苍鸾 2023-6-30 09:52:39 看全部
    这东西我收了!谢谢楼主!知鸟论坛真好!
    回复

    使用道具 举报

    发表于 2023-6-30 19:17:21 | 显示全部楼层
    风吹吹蛋蛋疼风w 2023-6-30 19:17:21 看全部
    其实我一直觉得楼主的品味不错!呵呵!知鸟论坛太棒了!
    回复

    使用道具 举报

    发表于 2023-6-30 21:21:55 | 显示全部楼层
    执着等待等wc 2023-6-30 21:21:55 看全部
    论坛不能没有像楼主这样的人才啊!我会一直支持知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-30 22:36:32 | 显示全部楼层
    李志敏 2023-6-30 22:36:32 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    回复

    使用道具 举报

    发表于 2023-7-3 11:45:06 | 显示全部楼层
    尘埃416 2023-7-3 11:45:06 看全部
    我看不错噢 谢谢楼主!知鸟论坛越来越好!
    回复

    使用道具 举报

    • 您可能感兴趣
    点击右侧快捷回复 【请勿灌水】
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则 返回列表

    RSS订阅| SiteMap| 小黑屋| 知鸟论坛
    联系邮箱E-mail:zniao@foxmail.com
    快速回复 返回顶部 返回列表