首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

MFC 多線程操作大漠插件發送字符串:STA 模型下跨窗口調用失效的解決方案

     編輯:程式設計綜合問答

在 VS2013 MFC 專案中,繼承 CWinThread 封裝大漠插件 (dm.dmsoft) 向多個目標窗口發送字符串。現象:創建兩個線程對象分別綁定不同窗口,但只有最後創建的線程能發送成功,切換窗口焦點後另一個失效。這是典型的 **COM STA (單線程單元) 模型導致的跨線程調用封送失敗**。

問題根因

  • 大漠插件為 COM 組件,默認註冊為 **STA 模型**(註冊表 ThreadingModel = Apartment)。
  • 每個 CWinThread 線程調用 CoInitialize(NULL) 初始化獨立的 STA。
  • COM 規定:STA 對象只能在創建它的線程中直接調用。線程 A 創建的 Cdmsoft 對象,線程 B 無法直接調用其方法(SendString 等),需經過接口封送。
  • MFC CWinThread 未自動建立代理/存根進行跨 STA 封送,導致調用失效或崩潰。

解決方案

方案一:主線程統一持有 COM 對象,工作線程發消息給主線程(推薦)

  1. 在主 UI 線程(或專用 COM 線程)創建並持有所有 Cdmsoft 實例。
  2. 工作線程不直接調用 COM 方法,而是通過 PostMessage/SendMessage 將任務參數(目標句柄、字符串)發送給主線程。
  3. 主線程接收消息後,在自己的 STA 中調用 dm.SendString(hwnd, str)。
  4. 優點:完全避開跨 STA 調用,邏輯清晰,符合 COM 規範。

方案二:使用 MTA (多線程單元) 模型 —— 需插件支持

  • 若大漠插件註冊為 ThreadingModel = Free (MTA) 或 Both,可在任意線程直接調用。
  • 但大多數自動化插件為 STA,**不可由使用者修改**。

方案三:每線程獨立創建 COM 對象(若插件支持多實例)

// 線程函數內
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);  // 明確 STA
Cdmsoft dm;
dm.CreateDispatch(_T("dm.dmsoft"));  // 當前線程創建自己的實例
dm.BindWindow(hwnd, ...);
dm.SendString(hwnd, str);  // 同線程調用,合法
::CoUninitialize();
  • 前提:插件允許同時存在多個實例綁定不同窗口,且授權許可。
  • 缺點:資源開銷大,若插件有全局狀態可能衝突。

代碼調整建議(基於方案一)

// 主對話框/視圖類成員
std::map m_dmMap;  // 主線程持有所有實例
// 或陣列 Cdmsoft m_dm[2];

// 工作線程發送任務
::PostMessage(m_hMainWnd, WM_USER_SEND_STRING, (WPARAM)hwnd, (LPARAM)new CString(str));

// 主線程消息處理
LRESULT CMainDlg::OnSendString(WPARAM wParam, LPARAM lParam) {
    HWND hwnd = (HWND)wParam;
    CString* pStr = (CString*)lParam;
    auto it = m_dmMap.find(hwnd);
    if (it != m_dmMap.end()) {
        it->second.SendString(hwnd, *pStr);
    }
    delete pStr;
    return 0;
}

關鍵點總結

  • STA COM 對象**綁定創建線程**,跨線程直接調用無效。
  • MFC 多線程 + COM 必須設計封送機制(消息隊列、IGlobalInterfaceTable、代理/存根)。
  • 大漠類插件最穩健做法:**主線程持有實例,工作線程只傳數據**。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved