作者以親身實習掙扎為引,系統講解 COM 套間——這個「既非進程也非線程,卻擁有兩者特性」的核心抽象。文章從多線程基礎切入,層層剖析套間解決的同步問題、四種套間模型差異、消息機制實現原理、預設套間陷阱,並示範如何用 TLS 阻止跨套間非法調用。
用汇编视角还原进程结构:代码段(CS)、数据段(DS)、堆栈段(SS)加载到内存形成进程;线程拥有 EIP/EAX 等寄存器上下文,通过 TSS (Task-State Segment) 与 TR 寄存器实现上下文切换。Windows 内核用 _CONTEXT 结构保存线程上下文,包含段寄存器、通用寄存器、EIP/EFLAGS/ESP 等。
全域变量 g_i 经两线程并发 +=10 导致竞态条件,输出乱序。临界区 (Enter/LeaveCriticalSection) 虽能保护,但粒度过粗失去并发优势,且组件开发者无法强制使用者加锁。
核心思想:利用 Windows 消息队列串行化调用。CoInitializeEx(COINIT_APARTMENTTHREADED) 创建隐藏窗口,跨线程调用时通过消息转发至对象所在线程,保证同一对象方法按序执行。
| 模型 | 註冊表 ThreadingModel | 調用線程 | 同步責任 |
|---|---|---|---|
| Single | Single/無 | 進程首個 STA | COM 自動串行化 |
| Apartment (STA) | Apartment | 建立對象的 STA | COM 自動串行化 |
| Free (MTA) | Free | 調用者線程 (MTA) | 組件自行實現同步 |
| Both | Both | 視調用者套間而定 | 組件自行同步,可跨 STA/MTA 直呼 |
實測驗證:修改註冊表切換模型,觀察 TestFunc1 輸出的線程 ID 變化。STA 模式下跨線程調用需用 CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream 封送接口指標,並以 MsgWaitForMultipleObjects 替代 WaitForSingleObject 保持消息泵。
主線程用 COINIT_MULTITHREADED (MTA) 初始化,創建 STA 組件時 COM 將其放入「預設 STA」——一個非當前線程的隱藏套間,導致調用額外跨套間開銷,性能下降。正確做法:線程初始化模式與組件模型匹配。
在對象構造函數 TlsAlloc/SetValue 標記所屬線程,方法入口 TlsGetValue 校驗,失配返回 RPC_E_WRONG_THREAD。實測可攔截直接傳指標跨線程調用,不影響合法封送調用。
套間本質:線程 TLS 中的數據結構 + 隱藏窗口,建立線程與串行化調用上下文的綁定。跨套間調用靠代理/存根/通道機制,最終落地於消息分發。看不懂 COM,多半是 C++ 虛函數表、多線程、CPU 架構基礎未夯實——夯實基礎,萬法歸一。