COM/ATL項目開發小記

最近在忙COM的項目,中途遇到一些問題和自己的一些想法,先記錄下來,以便以後再遇到可以查閱,先胡亂寫一通,將來東西多了,再整理成篇吧。

 

1.關於ATL Merge ProxyStub的問題,具體的問題就不描述了,自己新建一個ATL Merge一下就明白了。由於使用的是VS2010做開發,VC6的方式已經行不通了

(附VC6方案http://www.informit.com/library/content.aspx?b=Visual_C_PlusPlus&seqNum=264),經長時間的搜索終於在下面的網址找到了解決方案,但是悲劇的是我在寫作這個記錄的時候,下面的網址打不開了。http://connect.microsoft.com/VisualStudio/feedback/details/100544/mt-d-is-incompatible-with-merge-proxystub

難道就因爲他暴露了微軟自己的解決方案?微軟這麼小氣?

方案是這樣子:1.新建一個ATL的工程(不要選擇merge,不需要定義接口),將其他所有需要散列集的Interface對應的idl文件包含進來,然後編譯此工程的XXX_PS工程。之後這個編譯出來的ProxyStub的dll就是你整個項目所有的散列集dll,按那個網址的說法,微軟自己也是這麼幹的。以後就不需要單獨每個COM對應一個PSdll了。

 

2.關於COM接口傳遞C++類對象的問題

目前考慮到兩種方式,一種是直接聲明爲local(缺點不能進行遠程調用,只能在本進程中用), 或者實現爲COM對象,傳遞IUnknown指針,網上還有個國外的人,提供了另一種實現方案,就是利用MFC的CObject基類,實現Serialize序列化方式。仁者見仁智者見智,我們項目多用前兩種方式。

 

3.COM的函數調用切記一定用SUCCEDED和FAILED宏做判斷

由於項目是從MFC改過來的,以前的判斷常常忘記修改,雖然不是什麼導致崩潰的大問題,但是總給你莫名其奧妙的邏輯錯誤。

 

4.關於BSTR

這個只須切記,不要再給BSTR賦常量了!例如,BSTR bstr = L””,這樣雖然可能不會導致崩潰,但是有些COM相關的函數需要BSTR被動態分配。項目中遇到一個問題,SysStringLen的調用一直返回0,明明bstr是有值的,最後發現是因爲BSTR常量字符串導致。最好,最方便,最不容易出錯的就是使用BSTR的包裝類吧。CComBSTR和_bstr_t都可以,一個是ATL的封裝,一個是C++的封裝。 ATL開發中,對BSTR判空,可以簡單的進行:

   CString strFile = COLE2T(bstrFile);
    if (strFile.IsEmpty())
    {
        return S_OK;
    }

 

5.關於AddRef、Release和智能指針

其實做COM開發的都懂,但是還是會偶爾忘記的,項目中也遇到了一個函數調用傳出COM接口忘記AddRef最後程序退出崩潰的問題。建議就是:能用智能指針的地方儘量用,畢竟人的腦子經常會忘記事情,不用的地方,一定要注意是否是屬於那種局部使用的情況,如若不是,記得要AddRef。一般的智能指針都重載了!,==,*(),&等操作符,可以直接對指針判空(!p),(NULL == p),或者&p可以直接傳給Fuc(IXXXXX** p)的接口,但是對於CComSafeArray,由於沒有提供&操作符,因此對於接受(SAFEARRAY** punkArray)的函數,深入解析ATL建議可以通過類似以下方式使用:

    CComSafeArray<IUnknown*> pArray;
    SAFEARRAY *pSAFEARRAY = NULL;
    spJLIColGroup->GetInterfacesFromIID(IID_IJLUISimulationSinkConnpoint, &pSAFEARRAY);
    pArray.Attach(pSAFEARRAY);

關於智能指針的使用,還有一點需要記住的是,全局變量,需要在::CoUninitialize();之前,手動調用.Release()進行釋放。
 

6.關於COM相關的數據結構定義,.idl還是.h?

由於IDL中也是能包含.h文件的,具體過程中,估計還是定義在idl中更好一些。在idl中如果要防止重定義,因爲不能直接if define。因此需要用到idl專門的語法。

cpp_quote("#ifndef _STRUCT_")
cpp_quote("#define _STRUCT_")
//數據結構定義

typedef struct xxx{
 USHORT  xxx;      //按鈕ID
}COxxx, *LPxxx ;
cpp_quote("#endif")

這樣在生成的.h中就會包含#ifndef

 

7.關於COM Singleton

深入解析ATL第二版裏面說Singleton是邪惡的,DLL中只在每個進程中才是唯一的,進程外服務器中Singleton只在最好的情況下才是每個系統唯一的,通常,多數使用Singleton的更好方式是多個實例共享狀態,而不是單個共享實例。

我們項目中有個COM組件就是使用的這個方式,多實例共享狀態,狀態用Mutex進行保護。

 

8.使用ProgID還是複雜沒有意義的UUID?

項目中還是事先定義出規範,每個COM組件都有唯一,方便記憶的ProgID。使用的時候直接用ProgID,既有意義,又容易記憶。做過office自動化就清楚了,ProgID還是更好的選擇。

 

9.關於接口繼承(IA<-IB,ComI實現IB這樣的接口繼承方式)

在項目中遇到一個問題,CComQIPtr<IA> spXML; hr = spXML.CoCreateInstance(......之後hr提示COM接口沒有註冊。本來COM是多是介紹“橫向繼承的”(類似ComI同時實現IA和IB,不需要IB繼承至IA),而這裏想使用IA的抽象,直接讓IB接口繼承IA。這裏就需要而外做一些導出接口的操作。主要需要修改兩個地方。

1.在idl文件中coclass ComI

  {

   interface IA;

   [default] interface IB;

  };

添加interface IA;以便導出IA。

2.在對應的實現文件中

BEGIN_COM_MAP(CComI)
COM_INTERFACE_ENTRY(IA)

COM_INTERFACE_ENTRY(IB)
END_COM_MAP()

也添加IA的導出,這樣就可以正常使用接口繼承了。其實跟橫向繼承的導出是一樣,只是這種豎向的繼承往往會忘記做這些導出

 

10.COM數組

首先是[size_is]指定列集時的數組內存大小。http://msdn.microsoft.com/en-us/library/windows/desktop/aa367164(v=vs.85).aspx

雖然這樣做了,但是仍有人遇到exe之間調用COM接口傳遞數組不成功的問題,比如這位兄弟http://topic.csdn.net/t/20051021/17/4342421.html

因此可能會優先考慮VARIANT或者SAFEARRAY。

1)

SafeArray轉爲Varaint示例代碼:

STDMETHODIMP CXXXXXXXX::GetInterfacesFromIID(REFIID iid, VARIANT* punkArray)
{
    ::VariantInit(punkArray);
    punkArray->vt = VT_EMPTY;
     ......
    {
        CComSafeArray<IUnknown*> pSafeArray(nCount);
        ......
        {
            IUnknown *pUnKnown = NULL;
            iter->second->QueryInterface(iid, (void**)&pUnKnown);
            if (NULL != pUnKnown)
            {
                pSafeArray.SetAt(nIndex, pUnKnown, FALSE);
                ++nIndex;
            }
        }
        CComVariant varArray = pSafeArray.m_psa;
        return varArray.Detach(punkArray);
    } 

    return S_OK;
}


CComSafeArray包裝SAFEARRAY示例代碼:

    CComSafeArray<IUnknown*> pArray;
    SAFEARRAY *pSArray = NULL;
    spColGroup->GetInterfacesFromIID(IID_IRecordJLValidDeviceInfo, &pSArray);
    pArray.Attach(pSArray);
    UINT lLBound = 0, lUBound = 0;
    lLBound = pArray.GetLowerBound();
    lUBound = pArray.GetUpperBound();
    IUnknown* pUnKnown = pArray.GetAt(0);

2)SAFEARRAY與CComSafeArray

使用從外面傳遞進來的SAFEARRAY的時候,採用下面的方法:

void SetArray(SAFEARRAY * psa)  
    
 CComSafeArray<long>  sa;  
 sa.Attach(psa);  
 //操作    
  .....   
 sa.Detach();  

返回SAFEARRAY的時候,用下面的方法:

void GetArray(SAFEARRAY ** ppsa)   
{   
      CComSafeArray  <long> sa(10);  
      ......  
      *ppsa=sa.Detach();  
}          



 11.CoInitialize || CoInitializeEx

項目中在做單元測試時,遇到一個問題。程序在運行期間一切正常,退出時,智能指針釋放崩潰。最後查到是因爲線程啓動時沒有初始化COM庫導致。

關於初始化的重要性,網上有個高手總結了一下原因:COM高手總結的八個經驗和教訓http://www.yesky.com/98/1832098.shtml

 

12.關於COM 套間以及數據傳遞

網上有人總結得非常好:

http://www.cnblogs.com/wubiyu/archive/2010/01/29/1659214.html

http://blog.csdn.net/zzffly9/article/details/1541787

http://www.cnblogs.com/Quincy/archive/2011/03/03/1969510.html

http://www.vckbase.com/index.php/wv/1315

VC知識庫中的COM部分系列文章

http://www.vckbase.com/index.php/wenku/index/fid/90

 

項目還在進行中,持續記錄......

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