COM組件(MFC篇)


1創建進程內組件    1

1.1 目標    1

1.2 創建項目    3

1.2.1 VC++6.0    3

1.2.2 VC++2010    4

1.2.3 VC++6.0VC++2010的區別    6

1.3 升級項目    6

1.3.1 增加接口定義文件    6

1.3.2 覆蓋應用程序類的InitInstance函數    7

1.3.3 導出COM函數    7

1.3.4 修改rc文件    9

1.4 增加COM    10

1.4.1 VC++6.0    10

1.4.2 VC++2010    11

1.4.3 項目結構    13

1.4.4 VC++6.0VC++2010的區別    14

1.5 增加方法    14

1.5.1 VC++6.0    14

1.5.2 VC++2010    15

1.6 增加屬性    17

1.6.1 VC++6.0    17

1.6.2 VC++2010    17

1.7 刪除方法、屬性    18

1.7.1 VC++6.0    18

1.7.2 VC++2010    19

1.8 編碼    19

1.8.1 增加成員變量    19

1.8.2 初始化成員變量    19

1.8.3 實現Add    20

1.8.4 實現Reset    20

1.8.5 實現GetCount    20

1.8.6 實現GetAverage    20

1.8.7 實現GetStdDev    21

1.9 注冊、注銷    21

1.10 再論增加COM    22

2創建進程外組件    23

2.1 創建項目    23

2.2 升級項目    23

2.2.1 增加接口定義文件    23

2.2.2 修改rc文件    24

2.2.3 修改應用程序類的InitInstance函數    24

2.2.4 實現COM    25

2.3 注冊、注銷    27

3 VC++使用組件    29

3.1 #import    29

3.2 MFC包裝類    30

3.2.1 VC++6.0生成包裝類    30

3.2.2 VC++2010生成包裝類    31

3.2.3 包裝類的使用    33

3.3 C語言調用    33

4 VB6.0使用組件    37

4.1 前期綁定    37

4.1.1 引用類型庫    37

4.1.2 查看類型庫    38

4.1.3 編碼    39

4.2 后期綁定    39

 

 

1創建進程內組件

1.1 目標

本章的目標是使用MFC創建一個進程內COM組件。在此組件里,將實現COMCStatisticCOM接口IStatistic,用來進行統計計算。IStatistic的詳細信息如下:

1、方法

void Reset();                //重新開始統計計算

void Add(double dVal);        //增加一個數據

2、屬性

long    Count;                //返回數據個數

double    Average;            //返回平均值

double    StdDev;                //返回標准差

CStatisticVC++代碼如下:

#include <MATH.H>

 

class CStatistic

{

public:

CStatistic()            {Reset();}

public:

void Reset()        {memset(this,0,sizeof(*this));}

void Add(double dVal)

{

++m_nCount;             //樣本個數

m_dSum += dVal;         //所有樣本值的和

m_dSum2 += dVal * dVal;     //所有樣本值的平方和

}

public://屬性定義(VC++的語法)

__declspec(property(get=GetCount))     ULONG        Count;

__declspec(property(get=GetAverage))     double        Average;

__declspec(property(get=GetStdDev))     double        StdDev;

public:

ULONG GetCount() const        {return m_nCount;}

double GetAverage() const

{

if(m_nCount)

{

return m_dSum / m_nCount;

}

return 0.0;

}

double GetStdDev() const

{

double d = 0.0;

if(m_nCount > 1)

{

d = (m_nCount * m_dSum2 - m_dSum * m_dSum)

/ (m_nCount * (m_nCount - 1));

if(d > 0.0)

{

d = sqrt(d);

}

}

return d;

}

private:

ULONG    m_nCount;     //樣本個數

double        m_dSum;     //所有樣本值的和

double        m_dSum2;     //所有樣本值的平方和

};

測試代碼如下:

ULONG        n    =    0;

double        d    =    0.0;

CStatistic    s;

s.Reset();

s.Add(1.0);

s.Add(2.0);

s.Add(3.0);

s.Add(4.0);

n    =    s.Count;     //(1,2,3,4)的個數

d    =    s.Average;     //(1,2,3,4)的平均值

d    =    s.StdDev;     //(1,2,3,4)的標准差

s.Add(5.0);

n    =    s.Count;     //(1,2,3,4,5)的個數

d    =    s.Average;     //(1,2,3,4,5)的平均值

d    =    s.StdDev;     //(1,2,3,4,5)的標准差

這個類的優點在於:它能實時獲得樣本數據的平均值、標准差,且不用把樣本數據存入數組,因此可以連續的長時間工作。

1.2 創建項目

1.2.1 VC++6.0

運行VC++6.0,新建"MFC AppWizard(dll)"項目,如下圖所示。配置好項目名稱、項目目錄后,單擊"OK"按鈕。

1.1

下圖顯示的界面中,選擇"Regular DLL with MFC statically linked",表示創建一個MFC Regular DLL,這個DLL將靜態鏈接MFC共享庫。這就意味着,運行時這個DLL不再需要MFC42.dll。非MFC客戶程序使用此DLL時,能夠減少運行時的依賴項。

一定要勾中"Automation"復選框,它是實現COM組件的關鍵。

單擊"Finish"按鈕。

1.2

顯示界面如下,單擊"OK"按鈕,完成項目創建。

1.3

1.2.2 VC++2010

運行VC++2010,新建"MFC DLL"項目,如下圖所示。配置好項目名稱、項目目錄后,單擊"OK"按鈕。

1.4

顯示創建向導,界面如下面兩張圖所示:

1.5 創建向導——頁面一

頁面二的配置與VC++6.0的完全相同。

1.6 創建向導——頁面二

單擊上圖的"Finish"按鈕,完成項目的創建。

1.2.3 VC++6.0VC++2010的區別

1VC++6.0缺少DllUnregisterServer函數。這意味着VC++6.0編譯生成的COM組件不能通過regsvr32 /u comDLLmfc.dll進行注銷;

2DllRegisterServer函數里,VC++6.0缺少對類型庫的注冊,即缺少代碼AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid)

3CcomDLLmfcApp::InitInstance函數里,VC++6.0沒有調用CWinApp::InitInstance()

建議:采取VC++2010生成的代碼。

1.3 升級項目

假定有一個MFC Regular DLL項目,創建時未勾中"Automation"選項。如何將其轉換為COM組件?

1.3.1 增加接口定義文件

增加<dspName>.odl文件至DLL項目,其內容如下:

//<dspName>.odl

[uuid(71719C6D-3058-4B13-8C91-9DD49848FADF), version(1.0)]

library <TypeLibName>

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

//{{AFX_APPEND_ODL}}

//}}AFX_APPEND_ODL}}

};

說明:

1<dspName>dsp文件名;

271719C6D-3058-4B13-8C91-9DD49848FADF是類型庫的GUID,為避免重復,請替換成其它值。最簡單的辦法就是使用VC++6.0生成的頭文件里的宏。如:#if !defined(AFX_DLGDLG_H__8715C0CF_88F1_4CD3_B8E5_64FBCF26B052__INCLUDED_)中的8715C0CF_88F1_4CD3_B8E5_64FBCF26B052就是一個隨機的GUID,把下划線替換為減號即可使用;

3version(1.0)是類型庫的版本號。1是主版本號,0是次版本號;

4<TypeLibName>是類型庫的名稱,請根據實際需要做相應的修改。

1.3.2 覆蓋應用程序類的InitInstance函數

請覆蓋應用程序類的InitInstance函數,並在該函數內增加代碼COleObjectFactory::RegisterAll();

BOOL CcomDLLmfcApp::InitInstance()

{

CWinApp::InitInstance();

COleObjectFactory::RegisterAll();

return TRUE;

}

1.3.3 導出COM函數

請實現四個COM函數並導出:DllCanUnloadNowDllGetClassObjectDllRegisterServerDllUnregisterServer。代碼如下:

const GUID CDECL _tlid = { 0x71719C6D, 0x3058, 0x4B13

, { 0x8C, 0x91, 0x9D, 0xD4, 0x98, 0x48, 0xFA, 0xDF } };

const WORD _wVerMajor = 1;

const WORD _wVerMinor = 0;

 

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,LPVOID* ppv)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

return AfxDllGetClassObject(rclsid, riid, ppv);

}

STDAPI DllCanUnloadNow(void)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

return AfxDllCanUnloadNow();

}

STDAPI DllRegisterServer(void)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))

return SELFREG_E_TYPELIB;

if (!COleObjectFactory::UpdateRegistryAll())

return SELFREG_E_CLASS;

return S_OK;

}

STDAPI DllUnregisterServer(void)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))

return SELFREG_E_TYPELIB;

if (!COleObjectFactory::UpdateRegistryAll(FALSE))

return SELFREG_E_CLASS;

return S_OK;

}

說明:

1_tlid_wVerMajor_wVerMinor依次是類型庫的GUID、主版本號、次版本號。請與接口定義文件內容保持一致;

2VC++6.0AfxOleUnregisterTypeLib函數只有一個參數,請借鑒VC++.NET的代碼。

def文件里導出這四個函數:

EXPORTS

    DllCanUnloadNow        PRIVATE

    DllGetClassObject        PRIVATE

    DllRegisterServer        PRIVATE

    DllUnregisterServer        PRIVATE

1.3.4 修改rc文件

rc文件里增加"1 TYPELIB "<dspName>.tlb"",如下表所示:

3 TEXTINCLUDE

... ... ...

"#endif\r\n"

"1 TYPELIB ""<dspName>.tlb""\r\n"

"\0"

END

修改rc文件並保存,VC++會把3 TEXTINCLUDEEND之間的語句自動插入到rc文件的尾部。相當於在rc文件中增加了1 TYPELIB "<dspName>.tlb"。還有一種更為簡便的方法:直接把1 TYPELIB "<dspName>.tlb"增加到rc2文件里。

注意:請將<dspName>替換為實際的名稱。

對於VC++2010而言,需要設置資源編譯器的"Additional Include Directeries"目錄。在此,增加<dspName>.tlb所在的目錄。如果不增加這個目錄,編譯rc文件時,將會因為找不到<dspName>.tlb而失敗。

1.7

1.4 增加COM

現在,往項目里增加COM類。

1.4.1 VC++6.0

單擊【Insert】【New Class...】菜單項

1.8

顯示界面如下(如果不顯示如下界面,可能就是缺少clw文件或clw文件內容有誤。請關閉項目,刪除apsclwncbopt文件。再次打開項目,然后按下Ctrl+W,重新建立clw文件。)

"Class type"請選擇"MFC Class"。

"Base class"請選擇"CCmdTarget"或其派生類,因為COM組件功能就是由CCmdTarget實現的。

"Automation"必須選擇"Createable by type ID"。這個就是COM類的ProgID。客戶端程序可以根據這個ProgID實例化COM類。

單擊"OK"按鈕,完成COM類的創建。

1.9

1.4.2 VC++2010

單擊【Project】【Add Class...】菜單項

1.10

選中"MFC Class",然后單擊"Add"按鈕

1.11

增加MFC類的界面與VC++6.0的類似。配置好后,單擊"Finish"按鈕,完成COM類的創建。

1.12

1.4.3 項目結構

VC++6.0的類視圖里增加了"CStatistic"和"IStatistic"。

IStatisticCOM接口,客戶端程序通過它訪問COM組件。

CStatisticCOM類,真正的工作由它來完成。

1.13

同時,odl文件也發生了變化,如下表所示。增加了接口IStatisticdispinterface表示這個接口派生自IDispatch)。增加了COMStatistic,這個COM類實現了接口IStatistic

[ uuid(71719C6D-3058-4B13-8C91-9DD49848FADF), version(1.0) ]

library ComDLLmfc

{

    importlib("stdole32.tlb");

    importlib("stdole2.tlb");

    [ uuid(D5FC59B4-255F-415B-933F-08B97A23CD58) ]

    dispinterface IStatistic

    {

        properties:

            //{{AFX_ODL_PROP(CStatistic)

            //}}AFX_ODL_PROP

        methods:

            //{{AFX_ODL_METHOD(CStatistic)

            //}}AFX_ODL_METHOD

    };

    [ uuid(E43D739C-1270-4B71-B9DB-D3C74FEDDA19) ]

    coclass Statistic

    {

        [default] dispinterface IStatistic;

    };

    //{{AFX_APPEND_ODL}}

    //}}AFX_APPEND_ODL}}

};

1.4.4 VC++6.0VC++2010的區別

VC++6.0創建出來的COM類與VC++2010創建出來的COM類,其最大區別在於:VC++2010指明了COM類的線程模型。如下表所示:

VC++6.0

IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19)

VC++2010

IMPLEMENT_OLECREATE_FLAGS(CStatistic, "comDLLmfc.Statistic", afxRegApartmentThreading, 0x2260a7da, 0xc066, 0x4dd2, 0xaa, 0x61, 0x67, 0xb7, 0xab, 0x7b, 0xca, 0x13)

使用的宏也不同,一個是IMPLEMENT_OLECREATE,另一個是IMPLEMENT_OLECREATE_FLAGS

1.5 增加方法

1.5.1 VC++6.0

鼠標右鍵單擊接口IStatistic,彈出菜單中單擊【Add Method...】菜單項

1.14

下圖就是增加方法的界面。這里增加了方法void Add(double dVal)。單擊"OK"按鈕,完成方法的增加。

1.15

可使用同樣的方法,增加方法void Reset()

1.5.2 VC++2010

鼠標右鍵單擊接口IStatistic,彈出菜單中單擊【Add】【Add Method...】菜單項。

1.16

增加方法的界面如下。與VC++6.0的大致相同。單擊"Finish"按鈕,完成方法void Add(DOUBLE dVal)的添加。

1.17

可使用同樣的方法,增加方法void Reset()

1.6 增加屬性

1.6.1 VC++6.0

在圖1.14中,單擊【Add Property...】菜單項。顯示界面如下:

這里增加了屬性long Count。注意:沒有設置"Set function",說明這個屬性是只讀屬性。

單擊"OK"按鈕,完成屬性的增加。

1.18

同樣方法,可以增加屬性double Averagedouble StdDev

1.6.2 VC++2010

在圖1.16中,單擊【Add Property...】菜單項。顯示界面如下:

這里增加了屬性ULONG Count。注意:沒有設置"Set function",說明這個屬性是只讀屬性。

單擊"Finish"按鈕,完成屬性的增加。

1.19

同樣方法,可以增加屬性double Averagedouble StdDev

1.7 刪除方法、屬性

添加方法、屬性時,如果名稱或參數輸入錯誤,就需要刪除它,然后重新添加。

1.7.1 VC++6.0

按下Ctrl+W,啟動類向導界面。顯示如下圖所示。

進入"Automation"頁面,"Class name"下拉列表里選擇"CStatistic"(COM類)。"External names"里選擇需要刪除的屬性或方法,單擊"Delete"按鈕,然后單擊"OK"按鈕即可刪除選中的屬性或方法。

"External names"列表里,"M"表示方法,"C"表示自定義屬性。

1.20

1.7.2 VC++2010

VC++2010里刪除屬性、方法,似乎只能手動進行,相當的麻煩。

1.8 編碼

1.8.1 增加成員變量

請給CStatistic增加三個成員變量

private:

ULONG    m_nCount;

double        m_dSum;

double        m_dSum2;

1.8.2 初始化成員變量

CStatistic::CStatistic()

{

EnableAutomation();    

AfxOleLockApp();

m_nCount        =    0;

m_dSum        =    0.0;

m_dSum2        =    0.0;

}

1.8.3 實現Add

void CStatistic::Add(double dVal)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

++m_nCount;                     //樣本個數

m_dSum    +=    dVal;             //所有樣本值的和

m_dSum2    +=    dVal * dVal;     //所有樣本值的平方和

}

1.8.4 實現Reset

void CStatistic::Reset()

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

m_nCount    =    0;

m_dSum    =    0.0;

m_dSum2    =    0.0;

}

1.8.5 實現GetCount

long CStatistic::GetCount()

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

return m_nCount;

}

1.8.6 實現GetAverage

double CStatistic::GetAverage()

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

if(m_nCount)

{

return m_dSum / m_nCount;

}

return 0.0;

}

1.8.7 實現GetStdDev

double CStatistic::GetStdDev()

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

double d = 0.0;

if(m_nCount > 1)

{

d = (m_nCount * m_dSum2 - m_dSum * m_dSum)

/ (m_nCount * (m_nCount - 1));

if(d > 0.0)

{

d = sqrt(d);

}

}

return d;

}

1.9 注冊、注銷

編譯comDLLmfc,即可得到進程內COM組件comDLLmfc.dll。使用它之前,需要注冊。

注冊組件可使用如下任意一條命令。它們原理相同:都是載入comDLLmfc.dll,然后調用DllRegisterServer函數

regsvr32 comDLLmfc.dll

Rundll32 comDLLmfc.dll,DllRegisterServer

注銷組件可使用如下任意一條命令。它們原理相同:都是載入comDLLmfc.dll,然后調用DllUnregisterServer函數

regsvr32 /u comDLLmfc.dll

Rundll32 comDLLmfc.dll,DllUnregisterServer

注意:VC++2010可以編譯生成64位的COM組件。在64位操作系統上,regsvr32.exeRundll32.exe將自動識別COM組件是32位的還是64位的。注冊信息會寫入注冊表的如下幾個位置。注意這里的<ProgID>其實就是comDLLmfc.Statistic

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\<ProgID>

對於64位組件,注冊程序直接訪問上述注冊表項;對於32位組件,注冊程序會將上述注冊表項映射至32位的注冊表項。如此一來,同一個組件的32位、64位是可以同時注冊在64Windows上的,它們互不干涉。

1.10 再論增加COM

在圖1.9和圖1.12中,"Automation"有三個選項:NoneAutomationCreateable by type ID。三者有何區別?

"None"表示創建一個普通的C++類,也就是說選擇此項,創建出來的就不是COM類了。

"Createable by type ID"創建出來的是COM類,而且它可以根據輸入的ProgID實例化。

"Automation"創建出來的雖然也是COM類,但是它缺少了下面這幾條語句:

1、缺少IMPLEMENT_OLECREATE

缺少下面這條語句,意味着COMCStatistic不能被注冊、注銷,也不可能被客戶端程序實例化。

IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19)

2、因為COMCStatistic不能被實例化,因此其構造函數里缺少了AfxOleLockApp(),析構函數里缺少了AfxOleUnlockApp()

AfxOleLockApp()增加COM組件的引用計數,AfxOleUnlockApp()減小COM組件的引用計數。當引用計數為零時,DllCanUnloadNow函數里的AfxDllCanUnloadNow將返回TRUE,此時COM組件才能被FreeLibrary

 

 

2創建進程外組件

2.1 創建項目

創建進程外組件的操作很簡單:創建MFC EXE項目時,勾中"Automation"選項即可。本文就不進行說明了。

2.2 升級項目

本章的重點在於說明如何將一個MFC EXE項目升級為COM進程外組件。其要點就是用CCmdTarget的派生類做為COM類,供客戶端程序調用。

2.2.1 增加接口定義文件

請增加<dspName>.odl至項目,其內容如下:

[ uuid(B4573BE3-F956-4B7D-86AD-7628AE22CD9A), version(1.0) ]

library <TypeLibName>

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

//{{AFX_APPEND_ODL}}

//}}AFX_APPEND_ODL}}

};

注意:

1<dspName>dsp文件名;

2B4573BE3-F956-4B7D-86AD-7628AE22CD9A是類型庫的GUID。為避免重復,請替換成其它值。最簡單的辦法就是使用VC++6.0生成的頭文件里的宏。如:#if !defined(AFX_DLGDLG_H__8715C0CF_88F1_4CD3_B8E5_64FBCF26B052__INCLUDED_)中的8715C0CF_88F1_4CD3_B8E5_64FBCF26B052就是一個隨機的GUID,把下划線替換為減號即可使用;

3version(1.0)是類型庫的版本號。1是主版本號,0是次版本號;

4<TypeLibName>是類型庫的名稱,請根據實際需要做相應的修改。

2.2.2 修改rc文件

為了把類型庫信息嵌入exe,需要修改rc文件。使用記事本打開rc文件,找到3 TEXTINCLUDE,在END之前增加一條語句"1 TYPELIB ""<dspName>.tlb""\r\n"

3 TEXTINCLUDE

BEGIN

... ... ...

"#endif\r\n"

"1 TYPELIB ""<dspName>.tlb""\r\n"

"\0"

END

修改rc文件並保存,VC++會把3 TEXTINCLUDEEND之間的語句自動插入到rc文件的尾部。相當於在rc文件中增加了1 TYPELIB "<dspName>.tlb"。還有一種更為簡便的方法:直接把1 TYPELIB "<dspName>.tlb"增加到rc2文件里。

注意:請將<dspName>替換為實際的名稱。

2.2.3 修改應用程序類的InitInstance函數

修改應用程序類的InitInstance函數

//B4573BE3-F956-4B7D-86AD-7628AE22CD9A

const GUID CDECL BASED_CODE _tlid =

{ 0xB4573BE3,0xF956,0x4B7D

,{0x86,0xAD,0x76,0x28,0xAE,0x22, 0xCD,0x9A}};

const WORD _wVerMajor = 1;

const WORD _wVerMinor = 0;

 

BOOL CXXXApp::InitInstance()

{

... ... ...

AfxOleInit(); //增加此函數

... ... ...

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

if(cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)

{//COM客戶端啟動本程序

COleTemplateServer::RegisterAll();

}

else if(cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister)

{//從注冊表里注銷

COleObjectFactory::UpdateRegistryAll(FALSE);

AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor);

return FALSE;

}

else

{ //下面兩行代碼用於注冊本組件及類型庫

COleObjectFactory::UpdateRegistryAll();

AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid);

if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppRegister)

{//注冊之后不運行其它代碼

return FALSE;

}

}

}

注意:

1VC++6.0不支持CCommandLineInfo::AppRegister

2、上面代碼中的_tlid_wVerMajor_wVerMinor依次是類型庫的GUID、主版本號、次版本號。它應與COM.odl文件的內容保持一致。

2.2.4 實現COM

MFC實現的COM類必須派生自CCmdTarget。實現COM類有兩種思路:一是新建一個類;二是修改已有的類,使其變成COM類。具體而言,如果EXE項目里包含文檔類,就可以修改文檔類為COM類,否則可以新建一個。

進程外組件項目里增加一個COM類與進程內組件項目里增加一個COM類的操作完全相同。請參考上一章的內容。本節重點講述如何修改文檔類,使之成為COM類。

1、修改文檔類的頭文件

DECLARE_MESSAGE_MAP()之后,添加如下代碼:

// Generated OLE dispatch map functions

//{{AFX_DISPATCH(CTestDoc)

//}}AFX_DISPATCH

DECLARE_DISPATCH_MAP()

DECLARE_INTERFACE_MAP()

2、修改文檔類的實現文件

構造函數前,增加如下內容

BEGIN_DISPATCH_MAP(CTestDoc, CDocument)

    //{{AFX_DISPATCH_MAP(CTestDoc)

    //}}AFX_DISPATCH_MAP

END_DISPATCH_MAP()

 

// {07AD2F15-3DC9-4ED6-85E6-A42005A11978}

static const IID IID_ITest =

{ 0x7AD2F15, 0x3DC9, 0x4ED6

, { 0x85, 0xE6, 0xA4, 0x20, 0x5, 0xA1, 0x19, 0x78 } };

 

BEGIN_INTERFACE_MAP(CTestDoc, CDocument)

    INTERFACE_PART(CTestDoc, IID_ITest, Dispatch)

END_INTERFACE_MAP()

構造函數和析構函數,增加如下代碼:

CTestDoc::CTestDoc()

{

EnableAutomation();

AfxOleLockApp();

}

 

CTestDoc::~CTestDoc()

{

AfxOleUnlockApp();

}

3、修改接口定義文件內容

增加COM接口和COM

// Primary dispatch interface for CStatistic

[ uuid(07AD2F15-3DC9-4ED6-85E6-A42005A11978) ]

dispinterface IStatistic

{

    properties:

    // NOTE - ClassWizard will maintain property information here.

    // Use extreme caution when editing this section.

    //{{AFX_ODL_PROP(CStatistic)

    //}}AFX_ODL_PROP

    methods:

    // NOTE - ClassWizard will maintain method information here.

    // Use extreme caution when editing this section.

    //{{AFX_ODL_METHOD(CStatistic)

    //}}AFX_ODL_METHOD

};

// Class information for CStatistic

[ uuid(4FE1F183-045A-4AA8-A35D-3C365C4679B7) ]

coclass Statistic

{

    [default] dispinterface IStatistic;

};

4、應用程序類增加如下成員變量

COleTemplateServer m_server;

5、修改應用程序類的InitInstance函數

AddDocTemplate(pDocTemplate);之后增加一行代碼:

m_server.ConnectTemplate(clsid, pDocTemplate, TRUE);

clsidCOMStatisticGUID,請與接口定義文件內容保持一致,如:

// {4FE1F183-045A-4AA8-A35D-3C365C4679B7}

static const CLSID clsid = { 0x4FE1F183, 0x45A, 0x4AA8

,{0xA3,0x5D,0x3C,0x36,0x5C,0x46,0x79,0xB7}};

2.3 注冊、注銷

假定進程外組件的文件名為<ComExeName>,如:C:\App.exe,則:

注冊組件可使用下表任意一條命令:

<ComExeName> /Register

<ComExeName> /Regserver

<ComExeName> /RegisterPerUser

<ComExeName> /RegserverPerUser

注銷組件可使用下表任意一條命令:

<ComExeName> /Unregister

<ComExeName> /Unregserver

<ComExeName> /UnregisterPerUser

<ComExeName> /UnregserverPerUser

注意:

1VC++6.0不支持命令開關/Register/Regserver/RegisterPerUser/RegserverPerUser,也不支持CCommandLineInfo::AppRegister。直接運行程序即可完成注冊;

2VC++6.0不支持命令開關/RegisterPerUser/RegserverPerUser/UnregisterPerUser/UnregserverPerUserVC++2010支持,此時InitInstance函數里的cmdInfo.m_bRegisterPerUser TRUE

3、注冊時InitInstance函數里的ParseCommandLine(cmdInfo);將解析命令行開關,發現/Register/Regserver/RegisterPerUser/RegserverPerUser之一時,會設置cmdInfo.m_nShellCommandCCommandLineInfo::AppRegister。此時,注冊組件的代碼被執行(其實每次正常運行程序,以下注冊組件的代碼也會被執行)。

COleObjectFactory::UpdateRegistryAll();

AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid);

4、注銷時InitInstance函數里的ParseCommandLine(cmdInfo);將解析命令行開關,發現/Unregister/Unregserver/UnregisterPerUser/UnregserverPerUser之一時,會設置cmdInfo.m_nShellCommandCCommandLineInfo::AppUnregister。此時,注銷組件的代碼被執行。

COleObjectFactory::UpdateRegistryAll(FALSE);

AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor);

5COM客戶端調用時,會啟動進程外組件,並增加命令開關/Embedding/Automation。此時,cmdInfo.m_bRunEmbedded cmdInfo.m_bRunAutomated TRUE。以下代碼將被執行

COleTemplateServer::RegisterAll();

 

 

3 VC++使用組件

3.1 #import

如果客戶端程序由C++編寫而成,可以使用#import。代碼如下:

#import "G:\VC\comDLLmfc\Release\comDLLmfc.dll" no_namespace

 

void Test()

{

CoInitialize(NULL);

ULONG    n    =    0;

double        d    =    0.0;

try

{

IStatisticPtr s(__uuidof(Statistic));

s->Reset();

s->Add(1.0);

s->Add(2.0);

s->Add(3.0);

s->Add(4.0);

n    =    s->Count;         //(1,2,3,4)的個數

d    =    s->Average;     //(1,2,3,4)的平均值

d    =    s->StdDev;         //(1,2,3,4)的標准差

s->Add(5.0);

n    =    s->Count;         //(1,2,3,4,5)的個數

d    =    s->Average;     //(1,2,3,4,5)的平均值

d    =    s->StdDev;         //(1,2,3,4,5)的標准差

}//運行到此,智能指針s將被析構,s->Relase將被自動調用

catch(_com_error&e)

{

AfxMessageBox(e.Description() + "\n" + e.ErrorMessage());

}

CoUninitialize();

}

#import根據comDLLmfc.dll里的類型庫生成comDLLmfc.tlhcomDLLmfc.tli。查看comDLLmfc.tlh就可以知道COM組件(comDLLmfc.dll)的接口類IStatistic的定義。查看comDLLmfc.tli就能知道IStatistic的方法是如何實現的。

MFC編寫的COM組件,均為自動化接口,即COM接口派生自IDispatch。對屬性、方法的訪問,均是通過IDispatch::Invoke函數實現。

3.2 MFC包裝類

MFC包裝類就是生成一個派生自COleDispatchDriverC++類,通過這個類去訪問COM組件的屬性、方法。它只能包裝自動化接口,因此MFC編寫的COM組件都可以被MFC類包裝起來。其操作如下:

3.2.1 VC++6.0生成包裝類

按下Ctrl+W,啟動類向導界面。進入Automation頁面,單擊"Add Class..."按鈕。彈出菜單中,單擊【From a type library...】菜單項。

3.1

下圖所示的界面里,選擇COM組件,然后單擊"打開"按鈕。

3.2

VC++6.0顯示如下界面。

"Class name"上面的列表里,列出了組件里所有的COM接口;

"Class name"下面的文本框,是將要生成的包裝類名稱,它的基類注定是COleDispatchDriver

接着指定包裝類的頭文件、實現文件。

最后單擊"OK"按鈕。VC++6.0將創建包裝類。

3.3

3.2.2 VC++2010生成包裝類

單擊【Project】【Add Class...】菜單項

3.4

選中"MFC Class From TypeLib",然后單擊"Add"按鈕。

3.5

"Available type libraries"下拉列表框里選擇類型庫。單擊">>"按鈕,再單擊"Finish"按鈕,將生成包裝類。

3.6

注意:單擊上圖的"File"單選框,就可以指定一個文件。通過這個文件生成包裝類。

3.2.3 包裝類的使用

void Test()

{

CoInitialize(NULL);

ULONG    n    =    0;

double        d    =    0.0;

{

IStatistic s;

if(s.CreateDispatch(_T("comDLLmfc.Statistic")))

{

s.Reset();

s.Add(1.0);

s.Add(2.0);

s.Add(3.0);

s.Add(4.0);

n    =    s.GetCount();    //(1,2,3,4)的個數

d    =    s.GetAverage();    //(1,2,3,4)的平均值

d    =    s.GetStdDev();    //(1,2,3,4)的標准差

s.Add(5.0);

n    =    s.GetCount();    //(1,2,3,4,5)的個數

d    =    s.GetAverage();    //(1,2,3,4,5)的平均值

d    =    s.GetStdDev();    //(1,2,3,4,5)的標准差

}

}//運行到此,s 被析構。IDispatch被自動Release

CoUninitialize();

}

注意:CreateDispatch函數的參數"comDLLmfc.Statistic"就是COM類的ProgID,也就是圖1.9和圖1.12里的"Createable by type ID"。

import方法相比,MFC包裝類只能用於MFC程序。

3.3 C語言調用

使用C語言也可以訪問COM組件,下面是示例代碼:

#include <windows.h>

#include <MALLOC.H>

void Reset(IDispatch*s)

{

DISPPARAMS    dispparams;

VARIANT        vaResult;

UINT            nArgErr = (UINT)-1;

memset(&dispparams,0,sizeof(dispparams));

VariantInit(&vaResult);

s->lpVtbl->Invoke(s,0x5,&IID_NULL,0,DISPATCH_METHOD

                ,&dispparams,&vaResult, NULL, &nArgErr);

VariantClear(&vaResult);

}

void Add(IDispatch*s,double dVal)

{

DISPPARAMS    dispparams;

VARIANT        vaResult;

UINT            nArgErr = (UINT)-1;

memset(&dispparams,0,sizeof(dispparams));

dispparams.cArgs = 1;

dispparams.rgvarg = (VARIANTARG*)malloc(sizeof(VARIANTARG));

dispparams.rgvarg->vt = VT_R8;

dispparams.rgvarg->dblVal = dVal;

VariantInit(&vaResult);

s->lpVtbl->Invoke(s,0x4,&IID_NULL,0,DISPATCH_METHOD

                ,&dispparams,&vaResult, NULL, &nArgErr);

VariantClear(&vaResult);

free(dispparams.rgvarg);

}

ULONG GetCount(IDispatch*s)

{

DISPPARAMS    dispparams;

VARIANT        vaResult;

UINT        nArgErr    =     (UINT)-1;

ULONG        nCount        =    0;

memset(&dispparams,0,sizeof(dispparams));

VariantInit(&vaResult);

s->lpVtbl->Invoke(s,1,&IID_NULL,0,DISPATCH_PROPERTYGET

                ,&dispparams,&vaResult, NULL, &nArgErr);

nCount = vaResult.lVal;

VariantClear(&vaResult);

return nCount;

}

double GetAverage(IDispatch*s)

{

DISPPARAMS    dispparams;

VARIANT        vaResult;

UINT            nArgErr        =     (UINT)-1;

double            d            =    0.0;

memset(&dispparams,0,sizeof(dispparams));

VariantInit(&vaResult);

s->lpVtbl->Invoke(s,2,&IID_NULL,0,DISPATCH_PROPERTYGET

            ,&dispparams,&vaResult, NULL, &nArgErr);

d = vaResult.dblVal;

VariantClear(&vaResult);

return d;

}

double GetStdDev(IDispatch*s)

{

DISPPARAMS    dispparams;

VARIANT        vaResult;

UINT        nArgErr        =    (UINT)-1;

double        d            =    0.0;

memset(&dispparams,0,sizeof(dispparams));

VariantInit(&vaResult);

s->lpVtbl->Invoke(s,3,&IID_NULL,0,DISPATCH_PROPERTYGET

            ,&dispparams,&vaResult, NULL, &nArgErr);

d = vaResult.dblVal;

VariantClear(&vaResult);

return d;

}

void main()

{

const IID DIID_IStatistic = {0xD5FC59B4,0x255F,0x415B

        ,{0x93,0x3F,0x08,0xB9,0x7A,0x23,0xCD,0x58}};

const CLSID CLSID_Statistic = {0xE43D739C,0x1270,0x4B71

        ,{0xB9,0xDB,0xD3,0xC7,0x4F,0xED,0xDA,0x19}};

IDispatch*    s    =    NULL;

ULONG    n    =    0;

double        d    =    0.0;

CoInitialize(NULL);

CoCreateInstance(&CLSID_Statistic,NULL

        ,CLSCTX_INPROC_SERVER,&DIID_IStatistic,&s);

Reset(s);

Add(s,1.0);

Add(s,2.0);

Add(s,3.0);

Add(s,4.0);

n = GetCount(s);         //(1,2,3,4)的個數

d = GetAverage(s);     //(1,2,3,4)的平均值

d = GetStdDev(s);     //(1,2,3,4)的標准差

Add(s,5.0);

n = GetCount(s);         //(1,2,3,4,5)的個數

d = GetAverage(s);     //(1,2,3,4,5)的平均值

d = GetStdDev(s);     //(1,2,3,4,5)的標准差

s->lpVtbl->Release(s);

CoUninitialize();

}

說明:

1MFC編寫的COM組件,其接口均派生自IDispatch,即自動化接口。對屬性、方法的訪問必須通過IDispatch::Invoke函數;

2、上述代碼參考了MFC包裝類。

 

 

4 VB6.0使用組件

4.1 前期綁定

4.1.1 引用類型庫

單擊【Project】【References...】菜單項

4.1

列表中勾中COM組件,然后單擊"OK"按鈕。也可以單擊"Browse..."按鈕,選擇一個文件,然后單擊"OK"按鈕。

4.2

4.1.2 查看類型庫

單擊【View】【Object Browser】菜單項

4.3

可以看到類型庫"comDLLmfc"里的COM接口,及其方法、屬性。

4.4

4.1.3 編碼

Dim n As Long

Dim d As Double

Dim s As New ComDLLmfc.Statistic '創建COM對象

s.Reset

s.Add 1#

s.Add 2#

s.Add 3#

s.Add 4#

n = s.Count        '(1,2,3,4)的個數

d = s.Average        '(1,2,3,4)的平均值

d = s.StdDev        '(1,2,3,4)的標准差

s.Add 5#

n = s.Count        '(1,2,3,4,5)的個數

d = s.Average        '(1,2,3,4,5)的平均值

d = s.StdDev        '(1,2,3,4,5)的標准差

Set s = Nothing    '釋放COM對象

4.2 后期綁定

后期綁定不用引用類型庫,通過CreateObject函數創建COM類。示例代碼如下:

Dim n As Long

Dim d As Double

Dim s As Object

Set s = CreateObject("comDLLmfc.Statistic") '創建COM對象

s.Reset

s.Add 1#

s.Add 2#

s.Add 3#

s.Add 4#

n = s.Count        '(1,2,3,4)的個數

d = s.Average        '(1,2,3,4)的平均值

d = s.StdDev        '(1,2,3,4)的標准差

s.Add 5#

n = s.Count        '(1,2,3,4,5)的個數

d = s.Average        '(1,2,3,4,5)的平均值

d = s.StdDev        '(1,2,3,4,5)的標准差

Set s = Nothing    '釋放COM對象

注意:后期綁定的代碼執行效率比前期綁定要低。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM