2012-02-05 103 views
1

我有一個C++應用程序,有時需要將信息導出到電子表格。它旨在使用COM和ActiveX與Microsoft Excel和OpenOffice Calc進行集成。從單獨線程啓動時出現OpenOffice Automation問題

我注意到OpenOffice的一個較新版本,我的程序在任何時候嘗試導出時都會超時並失敗。

我做了相當多的研究找出了故障需要以下兩個事件之前:

1)用自定義程序(即使該程序沒有做任何事情更簡單的UI窗口的創建比傳遞到默認程序的所有內容)

2)一個單獨的線程中的代碼通過COM和ActiveX推出的OpenOffice()被執行

我要指出的創造,任何給定的時間,有隻有一個線程在做OpenOffice集成。它恰好是與處理UI的人不同的線程。

我也注意到一些其他的怪異。

如果窗口類不涉及自定義過程,則不會發生錯誤。但是,如果涉及任何自定義過程,它確實發生。即使自定義窗口過程絕對不會將所有消息傳遞給默認的窗口過程,也會發生錯誤。

如果沒有製作UI窗口,單獨線程中的代碼將完美無瑕地執行。

如果集成代碼是從與UI相同的線程啓動的,則不會發生錯誤。如果集成首先在與UI相同的線程中執行,則後續創建和執行單獨的線程將無誤地運行。

這是最奇怪的觀察:我正在使用Visual Studio 2005進行調試。如果我在調用「loadComponentFromURL」之前設置了斷點,則掛起不會發生。但是,如果我沒有設置中斷點,那麼當發生掛起時,我可以中斷執行,並且我會發現調用堆棧指示它停留在等待從WaitForMultipleObjectsEx(...)返回的RPC調用過程中的某處。 。

下面是一個完整的代碼示例。如果您使用最新版本的OpenOffice在機器上編譯並運行它,它將會掛起。在WinMain(...)函數中,有一個調用TestOOCalc的註釋。如果您取消註釋,您會發現該程序現在可以完美地啓動OpenOffice Calc。

鑑於沒有多個線程試圖同時訪問OpenOffice,這看起來似乎不應該是一個線程問題。

我無法找到任何有關這種現象或根本原因的地方。我真的不希望將所有工作都放在與UI相同的線程中,因爲這會使UI在漫長的操作過程中無響應。

的思考?想法?

#include <windows.h> 
#include <atlbase.h> 
#include <process.h> 

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
    return DefWindowProc(hwnd, message, wParam, lParam); 
} 

BOOL MakeUIWindow(HINSTANCE hInstance) 
{ 
    // Class definition for Main Window 
    WNDCLASS wndclass; 
    ZeroMemory(&wndclass, sizeof(wndclass)); 

    wndclass.style   = CS_HREDRAW | CS_VREDRAW; 
    wndclass.lpfnWndProc  = WndProc; 
    wndclass.hInstance  = hInstance; 
    wndclass.lpszClassName = TEXT("Problem Window Class"); 

    // Register the Main Window class 
    if (!RegisterClass(&wndclass)) 
     return FALSE; 

    HWND hwnd = CreateWindowEx(0, TEXT("Problem Window Class"), 
             TEXT("Problem"), WS_OVERLAPPEDWINDOW, 
             10, 10, 500, 500, 
             NULL, NULL, hInstance, NULL); 

    ShowWindow(hwnd, SW_NORMAL); 

    return TRUE; 
} 

BOOL ActiveX_MethodCall(CComPtr<IDispatch> &rcpPropInterface, const WCHAR *wszMethod, const UINT uiArgs, VARIANTARG *pArgs, CComPtr<IDispatch> &rcpResult) 
{ 
    DISPID dispid; 
    HRESULT hr = rcpPropInterface.GetIDOfName(wszMethod, &dispid); 
    if (FAILED(hr)) 
     return FALSE; 

    DISPPARAMS dp; 
    EXCEPINFO ei; 
    VARIANT varReturn; 
    ZeroMemory(&varReturn, sizeof(varReturn)); 
    ZeroMemory(&dp, sizeof(dp)); 
    ZeroMemory(&ei, sizeof(ei)); 

    varReturn.vt = VT_EMPTY; 

    dp.cArgs = uiArgs; 
    dp.rgvarg = pArgs; 

    hr = rcpPropInterface->Invoke(dispid, IID_NULL, NULL, DISPATCH_METHOD, &dp, &varReturn, NULL, NULL); 

    if (FAILED(hr)) 
     return FALSE; 

    rcpResult.Attach(varReturn.pdispVal); 
    return TRUE; 
} 

// Performs an initialization of OpenOffice 
BOOL TestOOCalc() 
{ 
    if (FAILED(CoInitialize(NULL))) 
     return FALSE; 

    // Get class IDs for the ActiveX object specified 
    CLSID clsid; 
    if (FAILED(CLSIDFromProgID(L"com.sun.star.ServiceManager", &clsid))) 
     return FALSE; 

    CComPtr<IDispatch> cpSvcMgr; 
    if (FAILED(cpSvcMgr.CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER))) 
     return FALSE; 

    CComPtr<IDispatch> cpDesktop; 
    { // context change for local variants 
     VARIANTARG varArg; 
     ZeroMemory(&varArg, sizeof(varArg)); 

     varArg.scode = DISP_E_PARAMNOTFOUND; 
     varArg.vt = VT_BSTR; 
     varArg.bstrVal = SysAllocString(L"com.sun.star.frame.Desktop"); 

     if (!ActiveX_MethodCall(cpSvcMgr, L"createInstance", 1, &varArg, cpDesktop)) 
     { 
      VariantClear(&varArg); 
      return FALSE; 
     } 

     VariantClear(&varArg); 
    } 

    // Call Desktop.loadComponentFromURL Method 
    CComPtr<IDispatch> cpWorkbook; 
    { // context change for local variants 
     VARIANTARG pvarArgs[4]; 
     ZeroMemory(&pvarArgs, sizeof(pvarArgs)); 

     pvarArgs[3].scode = DISP_E_PARAMNOTFOUND; 
     pvarArgs[3].vt = VT_BSTR; 
     pvarArgs[3].bstrVal = SysAllocString(L"private:factory/scalc"); 

     pvarArgs[2].scode = DISP_E_PARAMNOTFOUND; 
     pvarArgs[2].vt = VT_BSTR; 
     pvarArgs[2].bstrVal = SysAllocString(L"_blank"); 

     pvarArgs[1].scode = DISP_E_PARAMNOTFOUND; 
     pvarArgs[1].vt = VT_I4; 
     pvarArgs[1].lVal = 0; 

     SAFEARRAYBOUND saBound; 
     saBound.lLbound = 0; 
     saBound.cElements = 0; 
     SAFEARRAY *psaArgs = SafeArrayCreate(VT_VARIANT, 1, &saBound); 
     pvarArgs[0].scode = DISP_E_PARAMNOTFOUND; 
     pvarArgs[0].vt = VT_ARRAY | VT_VARIANT; 
     pvarArgs[0].parray = psaArgs; 

     if (!ActiveX_MethodCall(cpDesktop, L"loadComponentFromURL", 4, pvarArgs, cpWorkbook)) 
     { 
      SafeArrayDestroy(psaArgs); 
      VariantClear(&pvarArgs[3]); 
      VariantClear(&pvarArgs[2]); 
      VariantClear(&pvarArgs[1]); 
      VariantClear(&pvarArgs[0]); 

      return FALSE; 
     } 

     SafeArrayDestroy(psaArgs); 
     VariantClear(&pvarArgs[3]); 
     VariantClear(&pvarArgs[2]); 
     VariantClear(&pvarArgs[1]); 
     VariantClear(&pvarArgs[0]); 
    } 

    return TRUE; 
} 

unsigned int __stdcall thrTestOOCalc(void *vShare) 
{ 
    TestOOCalc(); 
    return 0; 
} 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) 
{ 
    if (!MakeUIWindow(hInstance)) 
     return 0; 

    //TestOOCalc(); 

    HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, thrTestOOCalc, NULL, 0, NULL); 
    WaitForSingleObject(hThread, INFINITE); 

    return 0; 
} 

回答

1

它已經很長一段時間,因爲一個與COM日常工作,但對我來說這看起來像在公寓線程抽水消息的經典失敗。

檢查以下內容:

  1. 現OpenOffice的組件聲明爲單元線程
  2. 如果不是,請嘗試使用CoInitializeEx在MTA中初始化您的線程。
  3. 如果面向對象組件聲明爲單元線程,則需要在新創建的線程上抽取消息。

希望這會有所幫助。

+0

謝謝你Vaugaus的信息。 OpenOffice自動化的文檔非常薄弱,但我認爲它是**公寓線程。但是,我不確定如何或在哪裏建立消息循環,因爲我的線程在調用堆棧爲空時從不等待任何事情。相反,每當我的線程調用OO啓動的一部分「loadComponentFromURL」時就會發生掛起。掛起發生在調用堆棧內部,它永遠等待WaitForMultipleObjectsEx(...)的返回。 – dshockey 2012-02-06 15:25:13

+1

COM文檔指出,您不需要公寓線程的消息泵的唯一情況是,如果您只調用簡單的方法(即沒有回調等)。我可以想象這樣一個場景:你調用OO組件,它決定爲了什麼原因產生一個新的線程,並調用一個方法(在本身或另一個組件上,AFAIK,這並不重要);此時您確實需要消息泵(因爲OO正在穿過公寓組件的線程)。不幸的是,我不知道在哪裏介紹消息泵。 – Vagaus 2012-02-07 10:33:57

+2

我發現了這個問題。問題在於主UI線程暫時等待完成處理線程的信號,並且沒有通過它的消息循環循環。通過設計消息傳遞方案並允許UI線程恢復消息泵,問題就消失了。顯然,COM對象正在等待其在UI線程中發佈的某條消息的結果。謝謝你讓我走上正確的軌道來搞清楚這一點。 – dshockey 2012-02-15 00:53:08