在Understanding COM Threading Models and Apartments from a NET Applications perspective 这一章你知道在创建经典COM组件之前NET 应用程序是怎样宣告调用线程单元辅助现在看一下等式的另一方面尤其是当从非托管 COM感知应用程序创建了NET组件时NET组件的线程辅助被文本定义而对象就处在这个文本中本质上来说一个Context就是AppDomain(轻型过程)拥有的环境 而对象就是在AppDomain 中创建的每一个文本都依次拥有对象这些对象享用公共的使用要求例如线程辅助对象池化交易JIT 激活同步等当依靠属性的执行时要求时而且对象也要求中断服务时就会创建这些文本如果这里有一个文本此嗯本与使用规则相匹配然后执行时就会在那个文本中提供调节如果它没有找到一个相匹配的文本就会为对象创建一个新文本
前面说到每一个AppDomain都有一个默认文本默认文本依次拥有Context Agnostic (Context Agile)对象这些对象没有绑定到任何文本Context Agile 对象不要求任何属性特殊使用规则以及中断服务让我们看一下下面的表格此表格总结了基于它们自己文本敏捷度的跨文本访问方案中NET 组件是怎样执行的
图
被非托管COM感知客户端访问时的线程中立行为
当一个程序集经过REGASMEXE想为COM 感知客户端创建正确的注册途径时NET组件是怎样把它的线程模式转到COM的
InprocServer下面的ThreadingModel钥有一个Both值在Classic COM中把它们的ThreadingModel作为Both的对象期望移到它们的呼叫者单元此呼叫者单元可以是STA或者MTA除此以外Both线程对象也集合自由线程封送拆收器给被它们封送拆收事物提供单元也集合直接接口指针引用以此反对代理Context Agile NET 组件(不是从ContextBound对象扩展而来的)与中立线程COM对象相似此中立线程COM对象集合自由线程封送拆收器当我们通过非托管客户端中的COM 单元将接口引用传输到NET组件时NET组件是怎样运行的让我们看一下这个简单的C#类型将向非托管 COM客户端展示这个C#类型
using System;using SystemRuntimeInteropServices;public interface IHelloDotNet {String GetThreadID();}/* end interface IHelloDotNet */[ClassInterface(ClassInterfaceTypeNone)]public class HelloDotNet : IHelloDotNet{public HelloDotNet() {}public String GetThreadID() {return AppDomainGetCurrentThreadId()ToString();}}/* end class HelloDotNet */
上面的类型执行来自于IHelloDotNet接口的GetThreadID方法这个方法返回当前线程的ID此当前线程正将AppDomain运行到这个对象下载的事物里为了把上面的类型建成一个程序集为COM创建正确注册途径从命令行中执行下列命令
csc /target:library /out:HelloDotNetdll HelloDotNetcs
regasm HelloDotNetdll /tlb:HelloDotNettlb
现在继续使用来自于COM感知客户端的NET组件我们将使用一个C++ 控制台应用程序此C++ 控制台应用程序将在它的主线程 (一个STA)中创建NET组件然后通过生成其它两个背景工作执行绪把它传送到另外两个单元(一个STA单元和一个MTA单元)
让我们看一下当使用显式线程间封送拆收调用(此调用使用CoMarshalInterface/CoUnmarshalInterface API家族)时什么时候被封送拆收的引用将在单元传输看一下下面的代码(为了简洁省略了代码中的错误检查)
Collapse #import mscorlibtlb // 导入 NET 组件 #import HelloDotNettlb no_namespace // 线程函数 long WINAPI MySTAThreadFunction(long lParam); long WINAPI MyMTAThreadFunction(long lParam); IHelloDotNetPtr spHelloNET = NULL; IStream* g_pStream = NULL; IStream* g_pStream = NULL; int main(int argc char* argv[]) { ::CoInitialize(NULL); cout << The Thread ID of the primary STA thread is : << ::GetCurrentThreadId() << endl; hr = spHelloNETCreateInstance(__uuidof(HelloDotNet)); cout << From NET when called from the primary STA Thread : << spHelloNET>GetThreadID() << endl; hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet) spHelloNET &g_pStream); hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet) spHelloNET &g_pStream); hThreadSTA = CreateThread(NULL (LPTHREAD_START_ROUTINE)MySTAThreadFunction NULL &dwThreadIDSTA); cout << The Thread ID of the STA based Worker thread is : << dwThreadIDSTA << endl; hThreadMTA = CreateThread(NULL (LPTHREAD_START_ROUTINE)MyMTAThreadFunction NULL&dwThreadIDMTA); cout << The Thread ID of the MTA based Worker thread is : << dwThreadIDMTA << endl;
::WaitForSingleobject(hThreadSTAINFINITE); ::WaitForSingleobject (hThreadMTAINFINITE); return ; } long WINAPI MySTAThreadFunction(long lParam) { ::CoInitializeEx(NULLCOINIT_APARTMENTTHREADED); cout << From NET when called from the STA Worker Thread (Direct Access) : << spHelloNET>GetThreadID() << endl; IHelloDotNetPtr spHello = NULL;HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream __uuidof(IHelloDotNet) (void **)&spHello); if(S_OK == hr) { cout << From NET when called from the STA Worker Thread (Marshaled) : << spHello>GetThreadID() << endl; } return ; } long WINAPI MyMTAThreadFunction(long lParam) { // 让线程进入MTA ::CoInitializeEx(NULLCOINIT_MULTITHREADED); cout << From NET when called from the MTA Worker Thread (Direct Access) : << spHelloNET>GetThreadID() << endl; IHelloDotNetPtr spHello = NULL; HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream __uuidof(IHelloDotNet) (void **)&spHello); if(S_OK == hr) { cout << From NET when called from the MTA Worker Thread (Marshaled) : << spHello>GetThreadID() << endl; } // 从线程退出 return ; }/* 结束MyMTAThreadFunction */
当运行控制台应用程序时这是得到的输出
The Thread ID of the primary STA thread is : From NET when called from the primary STA Thread : The Thread ID of the STA based Worker thread is : The Thread ID of the MTA based Worker thread is : From NET when called from the STA Worker Thread (Direct Access) : From NET when called from the STA Worker Thread (Marshalled) : From NET when called from the MTA Worker Thread (Direct Access) : From NET when called from the MTA Worker Thread (Marshalled) :
注意对于所有调用在客户端中的线程呼叫之间不会产生线程交换在NET组件中也不会产生线程引发实际方法还句话说NET组件是一个Context agile总是在呼叫者线程里执行任务从上面的代码片段中观察到对象引用(例如CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream)的封送拆收效果与在单元间传递直接对象引用的效果是一样的最后接受单元得到一个中立单元接口指针此中立单元l接口指针能用来调用NET组件NET组件展示Both 线程Classic COM 组件回忆的所有行为而Both线程Classic COM 组件则集合the自由线程封送拆收器
结论NET世界中COM组件的地位
在这系列文章的第一部分我们可以怎样把Classic COM组件展示到在公共语言运行库 (通用语言执行层)范围内执行的NET应用程序通过使用前期绑定和晚期绑定以及检查执行时类型和创建动态类型发现的方法COM interop怎样无缝地允许你来调用你的COM组件我们已经理解了在NET 中委托是怎样工作的以及它们在NET时间处理模式中所扮演的角色以及作为适配器COM Interop是怎样把classic COM中的连接点事件处理模式连接到NET中的基于委托的事件处理模式我们讨论了怎样把COM集合展示到NET应用程序中以及怎样使用C#s foreach句法简单地通过集合元素来迭代然后我们研究了IDL文件中的方向属性是怎样被映射到C#中相应的方向参数类型我们也学习了通过使用继承和内含包容Classic COM组件可以利用的来自于NET 应用程序的重用选项最后知道当调用COM组件时托管线程是怎样宣告它们的单元辅助
在文章的后半部分我们尝试探索来自于NET时代以前的COM感知客户端是怎样像classic COM组件一样使用NET 组件从程序编辑方面来看COM可调用的包装 以及通用语言执行层怎样无缝地推动产生此程序我们简单地探索了使用属性将元数据发送到NET类型的可能性因此可以根据你的要求来修改被生成的类型库我们也学习了两个世界中异常处理机制是怎样互相联系的我们还讨论了怎样从处在非托管事件接受里的NET 组件中接受异步事件通知然后把注意力转向可以利用的安装选择以及怎样把NET 组件安装成共享程序集最后讨论了NET 组件里的中立线程行为知道Contextagile NET 组件如何与集合自由线程封送拆收器 (FTM)的线程Classic COM Both组件相似
作为COM开发者可能想知道继续编写COM组件或者通过键入所有组件和商业逻辑代码(此商业逻辑代码是通过使用其中一种语言例如C# VBNET或者任何其它你所喜欢的生成通用语言执行层托管代码的语言包装成托管 组件)并且直接转移到NET世界是非常有意义的依我看来如果有大量的COM代码而不能一夜之间把这些COM代码转化成托管代码综合利用interop的能力重用来自于NET应用程序的COM 组件但是如果开始从空开始编写新商业逻辑代码然后最好的方法就是使用其中一种生成通用语言执行层托管代码的语言将代码包装成托管组件通过这种方法在托管和非托管边界之间传输时可以免受性能终结之苦从程序设计方面来看不管NET应用程序正在访问一个Classic COM组件还是访问一个托管组件由NET结构提供的工具以及由运行库提供的COM interop机制都能使它无缝因此本质上COM和勇敢的新用户NET世界结合将非常幸福我们所知道的喜欢的COM仍然继续我们生活的精髓