當心Invoke埋下的雷

【標籤】dispatch error; invoke error; COM; 連接點; VTS_VARIANT;


【報錯提示】
VS2010-0xC015000F 正在被停用的激活上下文不是最近激活的
VS2012-0x00000001 處有未經處理的異常(在 a.exe 中): 0xC0000005: 執行位置 0x00000001 時發生訪問衝突


【應用場景】
主程序通過連接點獲得COM組件事件通知,事件激發後回調崩潰。有些函數正常,有些崩潰。
崩潰的函數有個特點,事件接口中的參數類型是VARIANT。


【根本原因】
事件激發後,組件會通過連接點的Dispatch接口調用Invoke函數。
Invoke函數的實現中(COleDispatchImpl::Invoke in mfc100ud.dll),會先把參數拷貝到棧上,然後調用映射的函數。

拷貝參數過程中出現瞭如下代碼(oledisp1.cpp):

case VT_VARIANT:
    *(_STACK_PTR*)pStack = (_STACK_PTR)pArg;
    pStack += sizeof(_STACK_PTR);
    break;
也就是當參數是VARIANT類型的時候,只會把參數指針放到棧上,而不是拷貝整個參數。

如果映射的函數中,對應的參數寫成VARIANT,那麼調用過程中取值和調用完成後釋放棧參數就會出錯。因爲VARIANT*被當成VARIANT了。
如果這個參數的後面還有參數,也會導致錯誤。因爲VARIANT和VARIANT*所佔的字節大小是不一樣的。

其實微軟也註釋了,只是我沒有仔細看註釋,想當然了。在afxdisp.h中,
#define VTS_VARIANT         "\x0C"      // a 'const VARIANT&' or 'VARIANT*'


【解決方案】
接口不用改,激發回調處不用改(嚮導生成的要改),接收器要改。

//接收器舉例,注意最後一個參數類型VTS_VARIANT,對應的OnCallbackA函數中不能是 VARIANT v 必須是 VARIANT* v
BEGIN_DISPATCH_MAP(CxxView, CView)
    DISP_FUNCTION_ID(CxxView,"CallbackA",1,OnCallbackA,VT_EMPTY,VTS_I4 VTS_VARIANT)
END_DISPATCH_MAP()

void CxxView::OnCallbackA(LONG n, VARIANT* v){
}

//嚮導生成的代理有bug
激發事件後,調用Invoke的Fire_xx函數,其中Fire_xx函數有bug,必須看一下下面的相關鏈接。

還有一個最簡單的解決方案,不用VARIANT,哈哈。。。


【相關鏈接】
第一,[in]形式的VARIANT參數,要刪除嚮導生成代碼中的&即可。
http://support.microsoft.com/kb/250847/zh-cn
錯誤: ATL 連接點嚮導生成的代碼的 variant 類型的值參數提供 C4305 警告事件
第二,[out]形式的VARIANT*,鏈接中有兩種解決方案。
http://support.microsoft.com/kb/264985/zh-cn
錯誤: ATL Connection Point Wizard-Generated 代碼與事件 [out] 變量 * 或 [out] 長時間 * 參數提供 C4305 警告


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章