在 COM 互用性中包含三个实体:COM 二进制文件、托管客户端、PIA(Primary Interop Assembly,PIA,主互调用步调集)。Tlbimp.exe 可以利用 COM 二进制文件生成一个 PIA,这个 Tlbimp.exe 是 .NET SDK 的一部分。除了这个三个实体,还包含第四个实体,运行时刻调用封装(RCW),这个实体是在运行时被创建的。我们直接来一张图,看看这四个实体之间的关系,如图:
如图所示,首先是托管客户端调用 COM 对象中定义的方法,该对象是在 PIA 中定义的。CLR 通过来自 PIA 的信息创建 RCW 的实例。然后,RCW 截获对这个方法的调用,将参数转换为非托管类型,切换环境,并且调用非托管代码中的方法。
RCW 另外一个功能负责处理底层 COM 对象的生命周期。COM 对象的生命周期是通过一种引用计数模式来管理的,这就意味着每当获取对象的一个接口时,引用计数就会增加。相反,当不在需要一个接口时,引用计数就会递减。当引用计数为 0 时,就可以销毁对象了。RCW 能跟踪引用的数量,并确保相应的递增/递减引用计数。当托管客户端使用完 RCW 并且不存在未释放的引用后, RCW 会被回收,并且相关的 COM 对象都会被释放。
RCW 有两种释放的方式:第一种,当不存在对 RCW 的引用后,RCW 会递减并且清除对底层 COM 对象的任何引用,因而 COM 对象直到垃圾网络器清除时才会被清除。第二种,我们可以使用 Marshal.ReleaseComObject 方法强制释放 COM 对象。
有一些 SOS 命令可以获取 COM 互用性相关的信息。
【!t】或者【!threads】命令获取全部托管线程的信息,此中就包含【套间】类型的信息。【套间】是一种逻辑结构,与 COM 线程模子紧密相关。如果某个 COM 组件的编写不考虑并发调用的情况,就可以使用单线程套间(STA),这种套间会使 COM 子系统对全部这个组件的调用串行化。相反,如果能够处理并发调用的组件就可以使用多线程套间(MTA)模子,在这种情况下,针对组件的访问就不需要串行化。
当任何一个线程使用 COM 组件时,它必须选择合适的套间模子。在默认的情况下,全部的 .NET 线程都在 MTA 模子中。
我们来一张图直观的感受一下【!t】命令的结果,如图:
2 Bp expression 'ExampleCore_7_03!AsyncProcess' could not be resolved, adding deferred bp
复制代码
那我们就通过源码的方式直接给 C++ AsyncProcess 方法下断点。我们点击 Windbg 菜单栏,依次选择【Source】--->【Open Source File】,打开选择我们的 C++ 项目中的 ExampleCore_7_033.cpp 文件。结果如图:
断点设置完成后,我们直接执行【g】命令,继续运行调试器。结果如图:
【Windbg Preview】命令窗口展示如图:
执行结果如下:
1 0:000> bl
2 0 e Disable Clear u 0001 (0001) (@@masm(`E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_7_033\ExampleCore_7_033.cpp:28+`))
7 *** WARNING: Unable to verify checksum for E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_7_03\bin\Debug\net8.0\ExampleCore_7_033.DLL
8 ModLoad: 00007ff8`0c490000 00007ff8`0c4b6000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_7_03\bin\Debug\net8.0\ExampleCore_7_033.DLL