COM開發拾粹<一> (轉)
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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- COM開發拾粹<二> (轉)
- JS 專案拾粹 六: 將 Markdown 轉為思維導圖的開源工具JS開源工具
- JS 專案拾粹 三: 唯一 ID 頭像圖片生成的 JS 庫JS
- JS 專案拾粹 二:自動生成中國山水畫的 JS 實現JS
- 使用純粹的JS構建 Web ComponentJSWeb
- JS 專案拾粹 五: 一行 JS 程式碼讓網頁從亮模式變為暗模式JS網頁模式
- 讓自己純粹一點
- 使用C#開發COM+元件 (轉)C#元件
- JS 專案拾粹 四: TimeCat 超高壓縮比的網頁無損錄屏JS網頁
- (零) React Native 專案開發拾遺React Native
- 書評《自適應軟體開發》(一)--.com時代的遺物 (轉)
- webpack 拾翠:充分利用 CommonsChunkPlugin()WebPlugin
- 玩轉iOS開發:iOS中的GCD開發(一)iOSGC
- 簡單純粹
- 基於gin的golang web開發:Gin技術拾遺GolangWeb
- MaxCompute Mars開發指南
- 玩轉iOS開發:iOS中的NSOperation開發(一)iOS
- .NET 基礎拾遺(5):多執行緒開發基礎執行緒
- 玩轉 iOS 開發:NSURLSession 講解 (一)iOSSession
- Direct Show 開發的一個示例 (轉)
- 開發 Laravel 的 Composer 包Laravel
- Composer & Laravel 包本地開發Laravel
- 讀js純粹筆記JS筆記
- Java 太笨?純粹誹謗Java
- 發一個java開源大介紹www.open-open.comJava
- 《書評《自適應軟體開發》(二)--.com時代的遺物 (轉)
- .NET 基礎拾遺(7):Web Service 的開發與應用基礎Web
- MaxCompute 圖計算開發指南
- 朝花夕拾——更新兩個開源專案
- Nix:一個純粹的函式式包管理器函式
- .NET 開發 (轉)
- 開發 Laravel 包併發布到 packagist(composer)Laravel
- 玩轉iOS開發:iOS開發中的裝逼技術 – RunTime(一)iOS
- 玩轉iOS開發:iOS開發中的裝逼技術 - RunTime(一)iOS
- 書海拾貝|開發藝術探索之 android 的訊息機制Android
- 一個commit引發的思考MIT
- OLE程式開發利用(開發EXCEL) (轉)Excel
- 【轉】交換機開發(一)—— 交換機的工作原理