返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >老生常谈.NET中的 COM 组件
  • 258
分享到

老生常谈.NET中的 COM 组件

.NET  COM 组件.NET组件 2022-11-13 18:11:10 258人浏览 安东尼
摘要

目录什么是COM组件?使用COM组件需要注意:一:背景1.讲故事二:COM 多语言互操作1. 背景2. C# 写一个 COM 组件3. 注册 COM 到注册表4. 使用 c++ 调用

什么是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 节点目录,截图如下:

这个和微服务中的 注册中心 是一个道理,这一篇我们用 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 放到 注册表,最好都生成一个强签名,否则会有警告提示。

  • 注册 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 组件的文章就介绍到这了,更多相关.NET COM 组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 老生常谈.NET中的 COM 组件

本文链接: https://lsjlt.com/news/169668.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 老生常谈.NET中的 COM 组件
    目录什么是COM组件?使用COM组件需要注意:一:背景1.讲故事二:COM 多语言互操作1. 背景2. C# 写一个 COM 组件3. 注册 COM 到注册表4. 使用 C++ 调用...
    99+
    2022-11-13
    .NET  COM 组件 .NET组件
  • 老生常谈java数组中的常见异常
    数组的定义 1:单个变量能存储信息 2:用来存储具有相同数据类型的数据集合,可以使用共同的名字来引用数组中存储的数据。 特点 数组可以存储任何类型的数据,包括原始数据类型和引用数据类...
    99+
    2024-04-02
  • 老生常谈java中的数组初始化
    数组的初始化可以分为两种:静态初始化动态初始化静态初始化:例:String[] str = new String[]{"A","B","C"};String str[] = new String[]{"A","B","C"};String ...
    99+
    2023-05-31
    java 数组 初始化
  • 老生常谈C++ 中的继承
    目录继承1.1 继承的概念1.2 继承的定义1.2.1 定义格式2 基类(父类)对象和派生类(子类)对象之间的赋值转换 3 继承中的作用域4 派生类(子类)的默认成员函数5...
    99+
    2024-04-02
  • 老生常谈Python中的Pickle库
    目录简介pickle与json比较函数dumpsloadsdumpload简介 Python 中有个序列化过程叫作 pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任...
    99+
    2024-04-02
  • 老生常谈java中的Future模式
    jdk1.7.0_79 本文实际上是对上文《简单谈谈ThreadPoolExecutor线程池之submit方法》的一个延续或者一个补充。在上文中提到的submit方法里出现了FutureTask,这不得不停止脚步将方向转向Java的Fut...
    99+
    2023-05-31
    java future模式 ava
  • 老生常谈vue的生命周期
    目录一、什么是生命周期二、生命周期函数三、生命周期的流程四、简单的生命周期代码总结一、什么是生命周期 每一个组件都可能经历从创建,挂载,更新,卸载的过程。 在这个过程中的某一个阶段,...
    99+
    2024-04-02
  • 老生常谈c++中的静态成员
    引言 有时候需要类的一些成员与类本身相关联,而不是与类的每个对象相关联。比如类的所有对象都要共享的变量,这个时候我们就要用到类的静态成员。 声明类的静态成员 声明静态成员的方法是使用...
    99+
    2024-04-02
  • 老生常谈Vue中的侦听器watch
    目录一、侦听器watch1.1.初识侦听器watch1.2.Vue的data的watch1.3.Vue的watch侦听选项一、侦听器watch (思维导图不太完善,因为是按照自己看...
    99+
    2022-11-13
    Vue侦听器watch Vue侦听器 Vue watch
  • 老生常谈java中的fail-fast机制
    在JDK的Collection中我们时常会看到类似于这样的话:例如,ArrayList:注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 Concurren...
    99+
    2023-05-31
    java fail fast
  • 老生常谈Java中List与ArrayList的区别
    目录1 概念方面2 初始化方面2.1 List2.1.1 错误写法2.1.2 正确写法2.1.2.1 不指定存取数据类型2.1.2.2 指定存取数据类型2.2 ArrayList2....
    99+
    2024-04-02
  • Android老生常谈LayoutInflater的新认知
    现在看我文章的多数是一些老Android了,相信每个人使用起LayoutInflater都是家常便饭,信手拈来。 但即使是这样,我仍然觉得这个知识点有可以分析的地方,看完之后或许你对...
    99+
    2024-04-02
  • 老生常谈Scanner的基本用法
    需求:键盘录入一个月份,输出该月份对应的季节。一年有四季3,4,5   春季6,7,8   夏季9,10,11 秋季12,1,2  冬季分析:A:键盘录入一个月份,用Scanner实现B:判...
    99+
    2023-05-31
    scanner 基本用法
  • 老生常谈android中的事件传递和处理机制
    一直以来,都被android中的事件传递和处理机制深深的困扰!今天特意来好好的探讨一下。现在的感觉是,只要你理解到位,其实事件的 传递和处理机制并没有想象中的那么难。总之,不要...
    99+
    2022-06-06
    事件 Android
  • 老生常谈C语言中指针的使用
    目录前提一.指针基础1.1 变量指针1.2 数据指针1.3 指针的本质1.4 指针数组1.5 指针的移动1.5 Scanf函数的解释二.指针的进阶玩法2.1 二维指针2.2 结构体指...
    99+
    2024-04-02
  • 老生常谈Javascript的防抖和节流
    目录1. 什么是防抖2、什么是节流3、节流阀总结1. 什么是防抖 【解释】: 防抖策略(debounce)是当事件被触发后,延迟 n 秒后再执行回调,如果在这 n 秒内事件...
    99+
    2024-04-02
  • 老生常谈spring的事务传播机制
    目录spring的事务传播机制1、why为什么会有事务传播机制?2、传播机制生效的条件解决方案3、传播机制类型PROPAGATION_REQUIRED (默认)REQUIRES_NE...
    99+
    2024-04-02
  • C语言函数指针的老生常谈
    目录函数指针函数指针的应用函数指针作为参数实例(qsort函数)总结 函数指针 本质上是一个指针,只不过指向函数而已。 编译器在编译期间对函数开辟了一块空间,而这快空间的开始地址,就...
    99+
    2024-04-02
  • 面试中老生常谈的MySQL问答集锦夯实基础
    目录1、数据库架构1.1、MySQL的基础架构图1.2、一条SQL查询语句在MySQL中如何执行的?2、SQL优化2.1、日常工作中你是怎么优化SQL的?2.1.1、优化表结构2.1...
    99+
    2024-04-02
  • 老生常谈java路径中的反斜杠和斜杠的区别
    JAVA中的斜杠有正斜杠与反斜杠之分,正斜杠,一般就叫做斜杠,符号为“/”;反斜杠的符号为“\”。斜杠(/)在JAVA中没有什么特别的意义,就是代表一个字符‘/';反斜杠(\)则不然,它和紧跟着它的那个字符构成转义字符,如“\n”(换行)、...
    99+
    2023-05-31
    java 路径 斜杠
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作