技术开发 频道

.NET 组件中的线程辅助


【IT168技术文档】

   在Understanding COM Threading Models and Apartments from a .NET Application's perspective 这一章,你知道在创建经典COM组件之前,.NET 应用程序是怎样宣告调用线程单元辅助 。现在看一下等式的另一方面。尤其是当从非托管 COM感知应用程序创建了.NET 组件 时,.NET组件 的线程辅助被文本定义,而对象就处在这个文本中。本质上来说,一个Context就是AppDomain(轻型过程)拥有的环境 ,而对象就是在AppDomain 中创建的。每一个文本都依次拥有对象,这些对象享用公共的使用要求,例如:线程辅助、对象池化、 交易、 JIT 激活、同步等。当依靠属性的执行时要求时,而且对象也要求中断服务时,就会创建这些文本。如果这里有一个文本,此嗯本与使用规则相匹配,然后执行时就会在那个文本中提供调节。如果它没有找到一个相匹配的文本,就会为对象创建一个新文本。

    前面说到,每一个AppDomain都有一个默认文本。默认文本.依次拥有Context Agnostic (Context Agile)对象。这些对象没有绑定到任何文本。Context Agile 对象不要求任何属性,特殊使用规则以及中断服务。让我们看一下下面的表格,此表格总结了基于它们自己文本敏捷度的跨文本访问方案中.NET 组件是怎样执行的



被非托管COM 感知客户端访问时的线程中立行为

    当一个程序集经过REGASM.EXE,想为COM 感知客户端创建正确的注册途径时,.NET组件是怎样把它的线程模式转到COM的。

    InprocServer32下面的ThreadingModel钥有一个'Both'值。在Classic COM中,把它们的ThreadingModel作为'Both'的对象,期望移到它们的呼叫者单元,此呼叫者单元可以是STA或者MTA。除此以外,'Both'线程对象也集合自由线程封送拆收器,给被它们封送拆收事物提供单元,也集合直接接口指针引用,以此反对代理。Context Agile .NET 组件(不是从ContextBound对象扩展而来的)与中立线程COM对象相似,此中立线程COM对象集合自由线程封送拆收器。当我们通过非托管客户端中的COM 单元将接口引用传输到.NET组件时,.NET组件是怎样运行的。让我们看一下这个简单的C#类型:将向非托管 COM客户端展示这个C#类型:
using System; 
using System.Runtime.InteropServices;

public interface IHelloDotNet {

String GetThreadID();

}/* end interface IHelloDotNet */

[ClassInterface(ClassInterfaceType.None)]
public class HelloDotNet : IHelloDotNet
{
public HelloDotNet() {
}

public String GetThreadID() {

return AppDomain.GetCurrentThreadId().ToString();
}

}/* end class HelloDotNet */
    上面的类型执行来自于IHelloDotNet接口的GetThreadID方法。这个方法返回当前线程的ID,此当前线程正将AppDomain运行到这个对象下载的事物里。为了把上面的类型建成一个程序集,为COM创建正确注册途径,从命令行中执行下列命令。

csc /target:library /out:HelloDotNet.dll HelloDotNet.cs 
regasm HelloDotNet.dll /tlb:HelloDotNet.tlb
   现在继续使用来自于COM 感知客户端的.NET组件。我们将使用一个C++ 控制台应用程序,此C++ 控制台应用程序将在它的主线程 (一个STA)中创建.NET组件,然后通过生成其它两个背景工作执行绪,把它传送到另外两个单元(一个 STA 单元 和一个 MTA 单元)。让我们看一下:当使用显式线程间封送拆收调用(此调用使用CoMarshalInterface/CoUnmarshalInterface API家族)时,什么时候被封送拆收的引用将在单元传输。看一下下面的代码(为了简洁,省略了代码中的错误检查)。
Collapse 
.......

#import "mscorlib.tlb"
// 导入 .NET 组件
#import "HelloDotNet.tlb" no_namespace

// 线程函数
long WINAPI MySTAThreadFunction(long lParam);
long WINAPI MyMTAThreadFunction(long lParam);

IHelloDotNetPtr spHelloNET = NULL;

IStream* g_pStream1 = NULL;
IStream* g_pStream2 = NULL;

int main(int argc, char* argv[])
{
.......

::CoInitialize(NULL);

cout << "The Thread ID of the primary STA thread is : "
<< ::GetCurrentThreadId() << endl;

hr = spHelloNET.CreateInstance(__uuidof(HelloDotNet));

cout << "From .NET when called from the primary STA Thread : "
<< spHelloNET->GetThreadID() << endl;

.......

hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet),
spHelloNET,
&g_pStream1);

hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet),
spHelloNET,
&g_pStream2);

hThreadSTA = CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)MySTAThreadFunction,
NULL,0 ,&dwThreadIDSTA);

cout << "The Thread ID of the STA based Worker thread is : "
<< dwThreadIDSTA << endl;

hThreadMTA = CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)MyMTAThreadFunction,
NULL,0,&dwThreadIDMTA);

cout << "The Thread ID of the MTA based Worker thread is : "
<< dwThreadIDMTA << endl;

::WaitForSingleobject(hThreadSTA,INFINITE);
::WaitForSingleobject (hThreadMTA,INFINITE);

return 0;

}


long WINAPI MySTAThreadFunction(long lParam)
{
::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);

cout << "From .NET when called from the STA Worker Thread (Direct Access) : "
<< spHelloNET->GetThreadID() << endl;

IHelloDotNetPtr spHello = NULL;
HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream1,
__uuidof(IHelloDotNet),
(void **)&spHello);

if(S_OK == hr)
{
cout << "From .NET when called from the STA Worker Thread (Marshaled) : "
<< spHello->GetThreadID() << endl;
}

return 0;

}

long WINAPI MyMTAThreadFunction(long lParam)
{

// 让线程进入MTA
::CoInitializeEx(NULL,COINIT_MULTITHREADED);

cout << "From .NET when called from the MTA Worker Thread (Direct Access) : "
<< spHelloNET->GetThreadID() << endl;

IHelloDotNetPtr spHello = NULL;
HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream2,
__uuidof(IHelloDotNet),
(void **)&spHello);

if(S_OK == hr)
{
cout << "From .NET when called from the MTA Worker Thread (Marshaled) : "
<< spHello->GetThreadID() << endl;
}

// 从线程退出
return 0;

}/* 结束MyMTAThreadFunction */
    当运行控制台应用程序时,这是得到的输出。
The Thread ID of the primary STA thread is : 2220 
From .NET when called from the primary STA Thread : 2220
The Thread ID of the STA based Worker thread is : 2292
The Thread ID of the MTA based Worker thread is : 2296
From .NET when called from the STA Worker Thread (Direct Access) : 2292
From .NET when called from the STA Worker Thread (Marshalled) : 2292
From .NET when called from the MTA Worker Thread (Direct Access) : 2296
From .NET when called from the MTA Worker Thread (Marshalled) : 2296
    注意:对于所有调用,在客户端中的线程呼叫之间,不会产生线程交换,在.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 组件里的中立线程行为,知道Context-agile .NET 组件如何与集合自由线程封送拆收器 (FTM)的线程Classic COM 'Both'组件相似。

    作为COM开发者可能想知道继续编写COM 组件,或者通过键入所有组件和商业逻辑代码(此商业逻辑代码是通过使用其中一种语言,例如:C#, VB.NET或者任何其它你所喜欢的生成通用语言执行层托管代码的语言包装成托管 组件),并且直接转移到.NET世界是非常有意义的。依我看来,如果有大量的COM代码,而不能一夜之间把这些COM代码转化成托管代码,综合利用interop的能力,重用来自于.NET应用程序的COM 组件。但是如果开始从空开始编写新商业逻辑代码,然后,最好的方法就是使用其中一种生成通用语言执行层 托管代码的语言,将代码包装成托管组件。通过这种方法,在托管和非托管边界之间传输时可以免受性能终结之苦。从程序设计方面来看,不管.NET应用程序正在访问一个Classic COM组件 还是访问一个托管组件,由.NET结构提供的工具以及由运行库提供的COM interop机制都能使它无缝。因此本质上,COM和勇敢的新用户.NET世界结合将非常幸福,我们所知道的、喜欢的COM仍然继续我们生活的精髓。
0
相关文章