用MFC建立COM服務(wù)器對象的框架步驟
任務(wù)1:建立第一個(gè)COM服務(wù)器
任務(wù)2:用VC調用COM接口
任務(wù)3:用VB調用COM接口
任務(wù)4:給接口增加新的屬性和方法
任務(wù)5:增加新的自定義接口
任務(wù)6:繼承接口類(lèi)
例子程序在tecsp/docs/samples/comMFCDemo下
COM服務(wù)器:comMFCDemo
COM對象:coDrawObj,coDrawObjLine,coDrawObjRect
任務(wù)1:建立第一個(gè)COM服務(wù)器
操作:
1. 新建一個(gè)工程comMFCDemo,MFC AppWizard(dll),Regular DLL using shared MFC DLL,Automation.
說(shuō)明:此時(shí)已建立COM服務(wù)器的基本框架,包括用于實(shí)例化和注冊用的
DllCanUnloadNow(),DllGetObject(),DllRegisterServer()三個(gè)函數.
2. 在工程中加入mfcdual.h,mfcdual.cpp文件(在tecsp/comm目錄中,應將該目錄放在頭文件查找路徑中)
在stdafx.h中加入
#include "mfcdual.h"
#include "comMFCDemo_i.h"
說(shuō)明:mfcdual.h中定義了一些用于簡(jiǎn)化編程的宏定義,減少MFC COM編程的重復勞動(dòng).按照本文檔維護COM對象接口將非常輕松簡(jiǎn)明.
comMFCDemo_i.h是midl從odl文件中生成的,包括COM對象,接口定義,GUID定義等.
3. 編譯選項
Project => Settings... => MIDL => Project Options
加入:/h comMFCDemo_i.h
MIDL將編譯odl文件生成類(lèi)型庫comMFCDemo.tlb,頭文件comMFCDemo_i.h,定義文件comMFCDemo_i.c.
任務(wù)2:新增一個(gè)COM對象
1. 用Class Wizard建立一個(gè)新類(lèi):CcoDrawObj,基類(lèi)為CCmdTarget,選擇Createable by type ID: comMFCDemo.coDrawObj.
說(shuō)明:為避免手工添加接口定義,先選擇自動(dòng)化接口,將會(huì )自動(dòng)生成odl文件和相應的接口定義,只需做少量的修改工作即可.選擇Createable by type ID是為了能夠通過(guò)類(lèi)廠(chǎng)來(lái)創(chuàng )建COM對象.
2. 修改odl文件中IDualDrawObj的定義
2.1 將
[ uuid(13144AD4-ECC8-46F4-88F7-2FAC305595EA) ]
dispinterface IcoDrawObj
改成:
[ uuid(13144AD4-ECC8-46F4-88F7-2FAC305595EA),
oleautomation,
dual
]
interface IcoDrawObj : IDispatch
interface IcoDrawObj : IDispatch
說(shuō)明:建議將COM對象的缺省接口定義成雙接口,可以在多種環(huán)境中使用.
2.2 刪除properties: 和 methods:這兩行
2.3 在{}中定義屬性和方法
// IcoDrawObj 屬性:
[propput, helpstring("橫坐標")]
HRESULT x([in] short new_x);
[propget, helpstring("橫坐標")]
HRESULT x([out, retval] short* ret_x);
// IcoDrawObj 方法:
[helpstring("繪制圖形")]
HRESULT Draw();
說(shuō)明:上面的注釋?xiě)A粢苑奖憬涌诶^承時(shí)使用;helpstring用于屬性和方法的注釋說(shuō)明,應加上;只讀屬性刪除propput定義的兩行.
注意new_x和ret_x變量的書(shū)寫(xiě)方法,put屬性使用new_前綴加上屬性名,get屬性使用ret_前綴加上屬性名,這樣是為了方便使用宏定義來(lái)自動(dòng)生成屬性聲明和實(shí)現.
2.4 修改coclass定義
將
coclass coDrawObj
{
[default] dispinterface IcoDrawObj;
};
改成:
coclass coDrawObj
{
[default] interface IcoDrawObj;
};
2.5 編譯odl文件
選擇FileView,在comMFCDemo.odl文件上點(diǎn)擊右鍵,選擇Compile comMFCDemo.odl,將生成類(lèi)型庫comMFCDemo.tlb,頭文件comMFCDemo_i.h和定義文件comMFCDemo_i.c.
將comMFCDemo_i.h和comMFCDemo_i.c加入工程中.
選擇Project => Settings... => comMFCDemo_i.c,在右側面板選擇C/C++ => Category:Precompiled Headers => Not using precompiled headers
3. 修改COM對象頭文件coDrawObj.h文件
3.1 加入屬性對應的內部成員變量:protected,m_前綴加上屬性名
short m_x;
3.2 接口聲明
// ==============
// 雙接口支持開(kāi)始
// ==============
// ----------------------
// IcoDrawObj接口聲明開(kāi)始
// ----------------------
// 此處用宏定義是為了支持接口繼承 ,在此處添加屬性和方法
#define DECLARE_INTERFACE_PART_IcoDrawObj \
STDMETHOD(get_x)(THIS_ short FAR* ret_x); \
STDMETHOD(put_x)(THIS_ short new_x); \
STDMETHOD(Draw)(); \
BEGIN_DUAL_INTERFACE_PART(coDrawObj, IcoDrawObj)
DECLARE_INTERFACE_PART_IcoDrawObj
END_DUAL_INTERFACE_PART(coDrawObj)
需要修改:
DECLARE_INTERFACE_PART_IcoDrawObj - IcoDrawObj是接口名字
BEGIN_DUAL_INTERFACE_PART(coDrawObj, IcoDrawObj) - coDrawObj是組件名字,IcoDrawObj是接口名字。
END_DUAL_INTERFACE_PART(coDrawObj) - coDrawObj是組件名字,IcoDrawObj是接口名字。
// ----------------------
// IcoDrawObj接口聲明結束
// ----------------------
// ISupportErrorInfo支持
DECLARE_DUAL_ERRORINFO()
// ==============
// 雙接口支持結束
// ==============
說(shuō)明:需要注意的是宏定義DECLARE_INTERFACE_PART_IcoDrawObj,最后面是接口的名字.該宏主要用于接口繼承時(shí),在子接口聲明中聲明父接口的屬性和方法.以后也在該宏定義中添加新的屬性和方法的聲明.
3.3 屬性讀寫(xiě)實(shí)現
說(shuō)明:屬性讀寫(xiě)實(shí)現基本結構都很相似,為減少代碼書(shū)寫(xiě)量和書(shū)寫(xiě)錯誤,特采用宏定義方式.
需要修改部份:
宏定義IMPLEMENT_PROPERTIES_IcoDrawObj是為了用于接口繼承,最后面是接口的名字。
目前實(shí)現了兩種屬性讀寫(xiě):
普通屬性(short, float等常規類(lèi)型)
DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) - 需改動(dòng)部份:x 是屬性名字,(short new_x) 與聲明中相同。
IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) - 需改動(dòng)部份:x 是屬性名字。
DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) - 需改動(dòng)部份:x 是屬性名字,(short FAR* ret_x) 與聲明中相同。
IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) - 需改動(dòng)部份:x 是屬性名字。
接口指針屬性
因為接口指針的賦值需要調用AddRef方法,故特別增加此宏定義。使用的唯一不同之處在于PROPERTY后面增加_INTERFACE后綴。
// ================
// 屬性讀寫(xiě)實(shí)現開(kāi)始
// ================
// ---------------------------
// IcoDrawObj 屬性讀寫(xiě)實(shí)現開(kāi)始
// ---------------------------
#define IMPLEMENT_PROPERTIES_IcoDrawObj(objClass, baseClass) \
DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) \
IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) \
DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) \
IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) \
// ---------------------------
// IcoDrawObj 屬性讀寫(xiě)實(shí)現結束
// ---------------------------
// ================
// 屬性讀寫(xiě)實(shí)現結束
// ================
// ============
// 方法實(shí)現開(kāi)始
// ============
3.4 方法實(shí)現
MFC采用嵌套類(lèi)的形式實(shí)現多接口,方法的實(shí)現基本結構相同,故采用宏定義減少代碼書(shū)寫(xiě)量和書(shū)寫(xiě)錯誤。
解決方法是每個(gè)接口的方法都調用嵌套父類(lèi)的同名函數,這樣嵌套類(lèi)方法的實(shí)現全部是同一種結構,可以用宏定義生成。
對每一個(gè)方法在嵌套父類(lèi)中創(chuàng )建一個(gè)proteced virtual的同名成員函數,建立一個(gè)以接口名字命名的實(shí)現文件IcoDrawObj.cpp,將同名成員函數的實(shí)現放在里面。這樣在接口定義好后,可以讓程序員只編寫(xiě)這個(gè)文件中的函數實(shí)現,達到統一控制接口定義的目的。
3.4.1 嵌套類(lèi)方法實(shí)現
以下嵌套類(lèi)方法實(shí)現的宏定義放在頭文件coDrawObj.h中屬性讀寫(xiě)實(shí)現的后面。
需要修改部份:
宏定義IMPLEMENT_METHODS_IcoDrawObj用于接口繼承,最后面是接口的名字。
STDMETHODIMP objClass::X##baseClass::Draw()最后的方法名字
pThis->Draw(); 最后的方法名字
// ============
// 方法實(shí)現開(kāi)始
// ============
// -----------------------
// IcoDrawObj 方法實(shí)現開(kāi)始
// -----------------------
#define IMPLEMENT_METHODS_IcoDrawObj(objClass, baseClass) \
STDMETHODIMP objClass::X##baseClass::Draw() \
{ \
BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
pThis->Draw();\
END_NEST_INTERFACE_FRAME \
} \
// -----------------------
// IcoDrawObj 方法實(shí)現結束
// -----------------------
// ============
// 方法實(shí)現結束
// ============
3.4.2 嵌套父類(lèi)方法實(shí)現
IcoDrawObj.cpp文件樣本(接口定義完成后,程序員僅需編寫(xiě)此文件中的函數實(shí)現即可):
// IcoDrawObj.cpp
#include "stdafx.h"
#include "coDrawObj.h"
void CcoDrawObj::Draw()
{
AfxMessageBox( "This is coDrawObj.");
}
4. 修改coDrawObj.cpp文件
4.1 構造函數colDrawObj()
// 自動(dòng)化支持
EnableAutomation();
// 聚合支持
EnableAggregation();
4.2 刪除IID定義
刪除類(lèi)似以下這行,因為前面已在工程中加入了MIDL自動(dòng)生成的comMFCDemo_i.c文件,我們可以在一個(gè)文件中管理全部的GUID定義。
static const IID IID_IcoDrawObj =
{ 0x13144ad4, 0xecc8, 0x46f4, { 0x88, 0xf7, 0x2f, 0xac, 0x30, 0x55, 0x95, 0xea } };
4.3 多接口支持定義
將
BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, Dispatch)
END_INTERFACE_MAP()
改成:
// 多接口支持定義
BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, coDrawObj)
DUAL_ERRORINFO_PART(CcoDrawObj)
END_INTERFACE_MAP()
需要修改部分:CcoDrawObj為COM對象類(lèi),IID_IcoDrawObj為接口GUID,coDrawObj為接口名字刪去I前綴。
4.4 類(lèi)廠(chǎng)實(shí)現
系統已自動(dòng)加入,類(lèi)似以下這行:
// {09671A2C-04FA-495E-A40C-410073A3A36E}
IMPLEMENT_OLECREATE(CcoDrawObj, "comMFCDemo.coDrawObj", 0x9671a2c, 0x4fa, 0x495e, 0xa4, 0xc, 0x41, 0x0, 0x73, 0xa3, 0xa3, 0x6e)
4.5 實(shí)現標準的基接口方法
加入以下代碼:
// 實(shí)現標準的IDispatch方法
DELEGATE_DUAL_INTERFACE(CcoDrawObj, coDrawObj)
// 實(shí)現標準的IUnknown方法
// DELEGATE_IUNKNOWN_INTERFACE(CcoDrawObj, coDrawObj)
需要修改部分:CcoDrawObj為COM對象類(lèi),coDrawObj為接口名字刪去I前綴。
對自動(dòng)化接口使用第一個(gè)宏定義,對自定義接口使用第二個(gè)宏定義。
每個(gè)接口需要一行宏定義,修改部份為最后的coDrawObj為接口名字刪去I前綴。
4.6 ISupportErrorInfo支持
加入以下一行:
// ISupportErrorInfo 支持實(shí)現
IMPLEMENT_DUAL_ERRORINFO(CcoDrawObj, IID_IcoDrawObj)
需要修改部分:CcoDrawObj為COM對象類(lèi),IID_IcoDrawObj為缺省接口GUID。
說(shuō)明:目前僅支持一個(gè)接口(請使用缺省接口),以后再加入多接口支持。
4.7 接口屬性實(shí)現
每個(gè)接口加入以下一行:
// 接口屬性實(shí)現
IMPLEMENT_PROPERTIES_IcoDrawObj(CcoDrawObj, coDrawObj)
需要修改部份:IcoDrawObj為接口名字,CcoDrawObj為COM對象類(lèi),coDrawObj為接口名字刪去I前綴。
4.8 接口方法實(shí)現
每個(gè)接口加入以下一行:
// 接口方法實(shí)現
IMPLEMENT_METHODS_IcoDrawObj(CcoDrawObj, coDrawObj)
需要修改部份:IcoDrawObj為接口名字,CcoDrawObj為COM對象類(lèi),coDrawObj為接口名字刪去I前綴。
5. 注冊COM服務(wù)器
好了,現在這個(gè)支持雙接口的COM服務(wù)器已經(jīng)完成了,趕快注冊吧。
Tools => Register Control
COM服務(wù)器:comMFCDemo
自動(dòng)化對象:coDrawObj,支持雙接口IcoDrawObj,屬性x,方法Draw
任務(wù)2:用VC調用COM接口
1. 在工程中加入新項目:vcDemo,選擇Dialog Base
在項目中加入../comMFCDemo_i.h和../comMFCDemo_i.c文件
選擇Project => Settings... => comMFCDemo_i.c,在右側面板選擇C/C++ => Category:Precompiled Headers => Not using precompiled headers
2. 在A(yíng)pp文件中InitInstance函數中加入COM庫初始化:
if ( !AfxOleInit() )
return FALSE;
3. 在對話(huà)框中加入一個(gè)按鈕,定義它的點(diǎn)擊消息
4. 在vcDemoDlg.cpp中
#include "../comMFCDemo_i.h"
IcoDrawObj* pIcoDrawObj = NULL;
BOOL CVcDemoDlg::OnInitDialog()
{
...
HRESULT hr;
hr = CoCreateInstance( CLSID_coDrawObj, NULL, CLSCTX_INPROC_SERVER, IID_IcoDrawObj, reinterpret_cast<void**>(&pIcoDrawObj));
if ( FAILED(hr) )
return FALSE;
...
}
BOOL CVcDemoDlg::DestroyWindow()
{
if ( pIcoDrawObj )
{
pIcoDrawObj->Release();
pIcoDrawObj = NULL;
}
return CDialog::DestroyWindow();
}
void CVcDemoDlg::OnButton1()
{
if ( pIcoDrawObj )
{
pIcoDrawObj->Draw();
}
}
5. 好了,現在可以編譯運行,看看點(diǎn)擊按鈕后出現了什么?
任務(wù)3:用VB調用COM接口
我們創(chuàng )建的是自動(dòng)化對象可以在VB中方便使用
1. 創(chuàng )建VB應用
2. 加入COM對象引用
Project => References... => Browse.. => ../Debug/comMFCDemo.tlb
3. 創(chuàng )建一個(gè)按鈕,生成它的點(diǎn)擊事件
Private Sub Command1_Click()
Dim coDrawObj As New coDrawObj
coDrawObj.Draw
End Sub
4. 好了,可以運行,點(diǎn)擊按鈕。
任務(wù)4:給接口增加新的屬性和方法
1. 修改odl文件
interface IcoDrawObj : IDispatch
{
// IcoDrawObj 屬性:
[propput, helpstring("橫坐標")]
HRESULT x([in] short new_x);
[propget, helpstring("橫坐標")]
HRESULT x([out, retval] short* ret_x);
>> [propput, helpstring("縱坐標")]
>> HRESULT y([in] short new_y);
>> [propget, helpstring("縱坐標")]
>> HRESULT y([out, retval] short* ret_y);
// IcoDrawObj 方法:
[helpstring("繪制圖形")]
HRESULT Draw();
>> [helpstring("關(guān)于")]
>> HRESULT AboutMe();
};
編譯odl文件
2. 在COM對象頭文件coDrawObj.h中加入屬性對應的內部成員變量
short m_y;
3. 在COM對象頭文件coDrawObj.h中修改接口聲明
#define DECLARE_INTERFACE_PART_IcoDrawObj \
STDMETHOD(get_x)(THIS_ short FAR* ret_x); \
STDMETHOD(put_x)(THIS_ short new_x); \
>> STDMETHOD(get_y)(THIS_ short FAR* ret_y); \
>> STDMETHOD(put_y)(THIS_ short new_y); \
STDMETHOD(Draw)(); \
>> STDMETHOD(AboutMe)(); \
4. 在COM對象頭文件coDrawObj.h中修改屬性讀寫(xiě)實(shí)現
#define IMPLEMENT_PROPERTIES_IcoDrawObj(objClass, baseClass) \
DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) \
IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) \
DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) \
IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) \
>> DECLARE_PUT_PROPERTY(objClass, baseClass, y)(short new_y) \
>> IMPLEMENT_PUT_PROPERTY(objClass, baseClass, y) \
>> DECLARE_GET_PROPERTY(objClass, baseClass, y)(short FAR* ret_y) \
>> IMPLEMENT_GET_PROPERTY(objClass, baseClass, y) \
4. 在COM對象頭文件coDrawObj.h中修改接口方法實(shí)現
#define IMPLEMENT_METHODS_IcoDrawObj(objClass, baseClass) \
STDMETHODIMP objClass::X##baseClass::Draw() \
{ \
BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
pThis->Draw();\
END_NEST_INTERFACE_FRAME \
} \
>> STDMETHODIMP objClass::X##baseClass::AboutMe() \
>> { \
>> BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
>> pThis->AboutMe();\
>> END_NEST_INTERFACE_FRAME \
>> } \
5. 創(chuàng )建嵌套父類(lèi)CcoDrawObj的protected virtual成員函數AboutMe
將函數實(shí)現移到IcoDrawObj.cpp中,交給程序員去完成。
void CcoDrawObj::AboutMe()
{
AfxMessageBox( "This is coDrawObj AboutMe().");
}
6. 編譯,注冊?,F在可以在vcDemo中加入 pIcoDrawObj->AboutMe();測試一下。
任務(wù)5:增加新的自定義接口
缺省自動(dòng)化接口提供對其它開(kāi)發(fā)平臺程序員的支持,COM對象內部之間還需要一些內部的工作需要用自定義接口實(shí)現。
1. 修改odl文件
增加新接口(接口uuid是用guidgen生成的)
[ uuid(8C1A8020-627B-4c37-AE70-51F9AD56FFBD),
object,
helpstring("coDrawObj的計算方法")
]
interface ICalc : IUnknown
{
[helpstring("計算")]
HRESULT Calc();
};
將新接口加入到COM對象類(lèi)中
coclass coDrawObj
{
[default] interface IcoDrawObj;
>> interface ICalc;
};
編譯odl文件
2. 修改COM對象頭文件coDrawObj.h
2.1 增加自定義接口聲明
// ----------------------
// ICalc接口聲明開(kāi)始
// ----------------------
#define DECLARE_INTERFACE_PART_ICalc \
STDMETHOD(Calc)(); \
BEGIN_INTERFACE_PART(Calc, ICalc)
DECLARE_INTERFACE_PART_ICalc
END_INTERFACE_PART(Calc)
// ----------------------
// ICalc接口聲明結束
// ----------------------
需要修改:
DECLARE_INTERFACE_PART_ICalc - ICalc是接口名字
BEGIN_INTERFACE_PART(coDrawObj, ICalc) - Calc是接口名字刪去I前綴,ICalc是接口名字。
END_INTERFACE_PART(Calc) - Calc是接口名字刪去I前綴,ICalc是接口名字。
2.2 接口方法實(shí)現
// -----------------------
// ICalc 方法實(shí)現開(kāi)始
// -----------------------
#define IMPLEMENT_METHODS_ICalc(objClass, baseClass) \
STDMETHODIMP objClass::X##baseClass::Calc() \
{ \
BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
pThis->Calc();\
END_NEST_INTERFACE_FRAME \
} \
// -----------------------
// ICalc 方法實(shí)現結束
// -----------------------
需要修改部份:IMPLEMENT_METHODS_ICalc最后為接口名字
STDMETHODIMP objClass::X##baseClass::Calc() 最后為接口方法名字
pThis->Calc();最后為接口方法名字
2.3 創(chuàng )建嵌套父類(lèi)CcoDrawObj的protected virtual成員函數Calc
將函數實(shí)現移到ICalc.cpp中,交給程序員去完成。
void CcoDrawObj::Calc()
{
AfxMessageBox( "This is coDrawObj Calc().");
}
3. 修改COM對象實(shí)現文件coDrawObj.cpp
加入
// 多接口支持定義
BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, coDrawObj)
>> INTERFACE_PART(CcoDrawObj, IID_ICalc, Calc)
DUAL_ERRORINFO_PART(CcoDrawObj)
END_INTERFACE_MAP()
需要修改:
IID_ICalc - 接口的GUID
CcoDrawObj - COM對象類(lèi)
Calc - 接口名字刪去I前綴
// 實(shí)現ICalc的標準IUnknown方法
DELEGATE_IUNKNOWN_INTERFACE(CcoDrawObj, Calc)
需要修改:
CcoDrawObj - COM對象類(lèi)
Calc - 接口名字刪去I前綴
IMPLEMENT_METHODS_ICalc(CcoDrawObj, Calc)
需要修改:
ICalc - 接口名字
CcoDrawObj - COM對象類(lèi)
Calc - 接口名字刪去I前綴
4. 編譯運行
文章來(lái)源于領(lǐng)測軟件測試網(wǎng) http://www.ltesting.net/

