經典 COM 使用「單元」 管理執行緒親和性:STA(單執行緒單元)、MTA(多執行緒單元)。.NET 引入 AppDomain(輕量級進程)與 Context(內含同步、交易、JIT 啟動等需求的執行環境)。物件依其屬性(如 [Synchronization])被置於特定 Context,執行期自動處理跨 Context 呼叫的封送。
Context-Agile 物件:不繫結特定 Context,可在任何執行緒直接執行,無封送開銷。預設所有非 ContextBoundObject 衍生類別皆為 Agile。
經 RegAsm.exe 註冊後,登錄 InprocServer32 下 ThreadingModel 為 Both。表示:
.NET Context-Agile 組件行為等同 COM "Both" + FTM:永遠在呼叫者執行緒上執行,無執行緒切換。
using System;
using System.Runtime.InteropServices;
[ComVisible(true)]
public interface IHelloDotNet
{
string GetThreadId();
}
[ClassInterface(ClassInterfaceType.None)]
public class HelloDotNet : IHelloDotNet
{
public string GetThreadId() => AppDomain.GetCurrentThreadId().ToString();
}
編譯註冊:
csc /target:library /out:HelloDotNet.dll HelloDotNet.cs
regasm HelloDotNet.dll /tlb:HelloDotNet.tlb
#import "mscorlib.tlb"
#import "HelloDotNet.tlb" no_namespace
IHelloDotNetPtr spHelloNET;
IStream* g_pStream1 = NULL;
IStream* g_pStream2 = NULL;
long WINAPI MySTAThreadFunction(long lParam);
long WINAPI MyMTAThreadFunction(long lParam);
int main()
{
CoInitialize(NULL); // 主執行緒進入 STA
cout << "Main STA Thread ID: " << GetCurrentThreadId() << endl;
spHelloNET.CreateInstance(__uuidof(HelloDotNet));
cout << "From .NET (Main STA): " << spHelloNET->GetThreadId() << endl;
// 封送介面至兩個背景執行緒
CoMarshalInterThreadInterfaceInStream(__uuidof(IHelloDotNet), spHelloNET, &g_pStream1);
CoMarshalInterThreadInterfaceInStream(__uuidof(IHelloDotNet), spHelloNET, &g_pStream2);
HANDLE hSTA = CreateThread(NULL, 0, MySTAThreadFunction, NULL, 0, NULL);
HANDLE hMTA = CreateThread(NULL, 0, MyMTAThreadFunction, NULL, 0, NULL);
WaitForSingleObject(hSTA, INFINITE);
WaitForSingleObject(hMTA, INFINITE);
return 0;
}
long WINAPI MySTAThreadFunction(long lParam)
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // 進入 STA
cout << "STA Worker Direct: " << spHelloNET->GetThreadId() << endl;
IHelloDotNetPtr spHello;
CoGetInterfaceAndReleaseStream(g_pStream1, __uuidof(IHelloDotNet), (void**)&spHello);
cout << "STA Worker Marshaled: " << spHello->GetThreadId() << endl;
return 0;
}
long WINAPI MyMTAThreadFunction(long lParam)
{
CoInitializeEx(NULL, COINIT_MULTITHREADED); // 進入 MTA
cout << "MTA Worker Direct: " << spHelloNET->GetThreadId() << endl;
IHelloDotNetPtr spHello;
CoGetInterfaceAndReleaseStream(g_pStream2, __uuidof(IHelloDotNet), (void**)&spHello);
cout << "MTA Worker Marshaled: " << spHello->GetThreadId() << endl;
return 0;
}
Main STA Thread ID: 2220
From .NET (Main STA): 2220
STA Worker Thread ID: 2292
MTA Worker Thread ID: 2296
From .NET (STA Worker Direct): 2292
From .NET (STA Worker Marshaled): 2292
From .NET (MTA Worker Direct): 2296
From .NET (MTA Worker Marshaled): 2296
結論:所有呼叫均在呼叫者執行緒上執行,無執行緒切換。直接呼叫與封送呼叫結果相同,證明 .NET 組件提供中立執行緒介面指標,行為等同 COM "Both" + FTM。
RegAsm、TlbImp、ComVisible、ClassInterface 等屬性提供精細控制。COM 與 .NET 的融合並非對立,而是互補。理解雙方執行緒模型映射關係,是構建穩健混合系統的關鍵。