COM開發拾粹<一> (轉)

worldblog發表於2007-12-14
COM開發拾粹 (轉)[@more@]

COM開發拾粹

  將近一年的時間,我一直在用VC的ATL開發COM,其間遇到過不少的障礙,經過努力,大多數已經解決。在這這個過程中,積累下一了點,現在寫下來,還是為了那兩個目的:整理存檔;和大家共享一些心得。

1. ProgID在哪裡

這是我剛會用ATL嚮導時遇到的第一個問題。想修改ProgID卻怎麼也找不到。原來它躲在和元件同名的.rgs裡,rgs是元件註冊的指令碼檔案,當你用 Regsvr32.exe註冊元件時,元件內部便是了這個檔案。rgs檔案是以資源的形式存於DLL內的。

2.手工新增一個方法,該修改哪些地方。

  假設元件類名叫CObj,介面叫IObj,要加入的方法是Open。首先在IDL檔案中找到介面IObj的定義,在其中加入如下方法定義:

  [id(2), helpstring("method Open")] HRESULT Open([out,retval] VARIANT_BOOL *ret);

注意:id中的數字不要和已經存在的id重複

其次,在CObj的類定義標頭檔案中加入如下成員宣告:

public:

  STDMETHOD(Open)(VARIANT_BOOL *ret);

最後,在CObj類的實現Cpp檔案中加入函式的實現:

STDMETHODIMP Open(VARIANT_BOOL *ret)

{

  *ret = VARAINT_TRUE;

  return S_OK;

}

3. 多用斷言幫助

  COM元件是一個個獨立的實體,它並不知道呼叫它的環境,也不知道呼叫者是否按開發者的約定來呼叫它。所以應該多用SERT來設定檢查點,以便於除錯。例如一個連線元件,在Open方法裡就首先要用斷言檢查ConnectionString屬性是否被賦值:

  ATLASSERT(m_ConnStr != NULL);

斷言後應該緊跟著寫錯誤處理,因為在Release版中,斷言全部無效了,你的錯誤處理程式碼就該發揮作用了。

if(m_ConnStr == NULL)

  return E_FAIL;

斷言失敗到底要不要處呢?這裡提供一種判斷原則:你開發的COM是一個自己的內部使用的COM,也就是客戶端和COM都是你一人或開發組內部開發時,可以不處理斷言失敗,而去檢查使斷言失敗的客戶端的程式碼是否不完善;當你寫的COM是要提交給”別人”使用時就要處理斷言失敗。

4. 寫一些可以”偷懶”的宏。

如果一個元件有很多的屬性,而對屬性的處理不過是直接存取成員變數,寫很多的get、put函式有點煩人。這些函式體看起來幾乎一樣,只是存取的資料型別不同:

get函式體:  *pVal = m_SomeMemberVar;

put函式體:  m_SomeMemberVar = newVal;

我們不妨寫一組宏來處理這種無聊的工作。受ATL中Copy策略類的啟發,我寫了下的宏:

//實現屬性寫操作的通用宏

#define PROP_PUT_IMPL(Name,Type)   

  STDMETHOD(put_##Name)(Type newVal) 

  {       

    m_##Name = newVal;    

    return S_OK; 

  }

 //實現屬性讀操作的通用宏

 #define PROP_GET_IMPL(Name,Type,CopyAdapter) 

  STDMETHOD(get_##Name)(Type* pVal) 

  {   

    CopyAdapter::destroy(pVal); 

  Type src = m_##Name; 

    return CopyAdapter::copy(pVal, &src); 

  }

//實現讀寫屬性的通用宏

#define PROP_IMPL(Name,Type,CopyAdapter) 

  PROP_PUT_IMPL(Name,Type)   

  PROP_GET_IMPL(Name,Type,CopyAdapter)

 :namespace prefix = o ns = "urn:schemas--com::office" />

使用這個宏的前提是儲存屬性的成員函式的命名必須是:”m_屬性名”這樣的形式,如有一個讀寫屬性 Name,型別為BSTR,我可以在對像類中這樣實現它

class  ATL_NO_VTABLE CObj

{

… …

public:

PROP_IMPL(Name,BSTR,CopyBSTR)

private:

CComBSTR m_Name;
}

CopyBSTR是我寫的一個Copy策略類,如下:

class CopyBSTR

{

public:

  static HRESULT copy(BSTR* p1, BSTR* p2)

  {

    ATLASSERT(p2 != NULL);

    if(p2 == NULL)

    return E_POINTER;

    CComBSTR bstrTemp = *p2;

    return bstrTemp.CopyTo(p1);

  }

  static void init(BSTR* str) {}

  static void destroy(BSTR* str) {SyreeString(*str);}

};

實現只讀屬性也很簡單:

……

public:

    PROP_GET_IMPL(Name,BSTR,CopyBSTR)

private:

    m_Name;

 

實現一些常用型別的寫法如下:

PROP_IMPL(PropName,long,_Copy)

PROP_IMPL(PropName,short,_Copy)

PROP_IMPL(PropName,VARAINT_BOOL,_Copy)

PROP_IMPL(PropName,IDispatch*,_CopyInterface)

PROP_IMPL(PropName,BSTR,CopyBSTR)

使用這個宏還有個附加的好處:定義的都是inline函式,會比嚮導生成的函式體好。

 

還有一種程式碼段是很常用的,形如:

  HRESULT hr = Obj->MethodCall();

if(FAILED(hr))

  return hr;

我們可以定義這樣的宏來代替它:

#define RET_ERR_IF_FAIL(exp)   

  do {         

    HRESULT __hr = (exp);   

    if(FAILED(hr) return hr;       

  } while(false)

然後這樣使用:

  RET_ERR_IF_FAIL(Obj->MethodCall());

  RET_ERR_IF_FAIL(Obj->MethodCall2());

怎麼樣,程式碼整潔多了吧。

 

5.BSTR的使用

初學COM肯定要在BSTR上栽跟頭,分配、釋放弄得一頭霧水,洩露在所難免。勸大家儘量不要直接使用BSTR,用封裝類_bstr_t和CComBSTR取而代之。_bstr_t是一個類,它是MS對ANSI C++的擴充套件,有了它,你可以在SDK方式中(非MFC、ATL)方便的使用BSTR型別。CComBSTR是ATL中對BSTR的封裝。用ATL開發時,一般情況下可以用CComBSTR類。在屬性的讀取函式中,可以用CComBSTR的CopyTo方法返回一個BSTR。CComBSTR過載了取地址運算子&,對CComBSTR取地址等於對它內部的BSTR取地址,這樣,CComBSTR也可以用在方法的out,retval型別引數上,例如:

有某COM類的某方法定義如下:

  HRESULT GetUserName([in]BSTR UserID,[out,retval]BSTR *UserName);

此方法內部可如下實現返回UserName的程式碼:

……

m_UserName.CopyTo(UserName);//m_UserName為成員變數,型別為CComBSTR

而此COM的客戶端呼叫GetUserName時,也可以用CComBSTR型別變數來得到UserName.

……

CComBSTR bstrUserID(L”001”),bstrUserName;

Obj->GetUserName(bstrUserID,&bstrUserName);

不管是方法GetUserName內部還是客戶端,都能夠正確的自動的分配、釋放記憶體。只有一種情況例外,那就是連續使用一個CComBSTR變數作為輸出引數的實參時,如果處理不當會產生記憶體洩露。如:

  CComBSTR bstrFieldName,bstrFieldValue;

  bstrFieldName = L”usr_id”;

  RecordsetObj->GetFieldValueAsString(bstrFieldName,&bstrFieldValue);

  m_UserID = bstrFieldValue;

  bstrFieldName = L”usr_name”;

  RecordsetObj->GetFieldValueAsString(bstrFieldName,&bstrFieldValue);//如果方法內部實現直接覆蓋&bstrFieldValue指標,則bstr上次的值會丟失。如果內部是用的CComBSTR.CopyTo則不會。

  _bstr_t型別不是不好,但我們總是在迫不得以時才會用它,也就是用#import預編譯指令匯入一個COM的封裝類時。用import匯入的封裝類只用_bstr_t型別作為BSTR引數型別,而在CComBSTR、_bstr_t、BSTR之間來回轉換也是頂麻煩的事。於是我仿照CAdapt類寫了一個CBSTRAdapt來自動完成轉換的工作。

  class CBSTRAdapt

{

  _bstr_t m_str;

public:

  CBSTRAdapt(const _bstr_t& str)

  {

  m_str = str;

  }

  CBSTRAdapt(const BSTR str)

  {

  m_str = str;

  }

  CBSTRAdapt(const CComBSTR& str)

  {

  m_str = str.m_str;

  }

  operator BSTR()

  {

    return m_str.GetBSTR();

  }

  operator _bstr_t()

  {

    return m_str;

  }

  operator CComBSTR()

  {

    return CComBSTR(m_str.GetBSTR());

  }

};

它的主要用途是完成從CComBSTR到_bstr_t的轉換。

例如有要呼叫如下方法:

bool  IsLogin(_bstr_t UserID);

呼叫程式碼如下:

CComBSTR bstrUserID(L”007”);

 

if(Obj->IsLogin(CBSTRAdapt(bstrUserID))

{

……

}

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993399/,如需轉載,請註明出處,否則將追究法律責任。

相關文章