在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仍然繼續我們生活的精髓
From:http://tw.wingwit.com/Article/program/net/201311/13088.html