目錄
第1章創建進程內組件
1.1 目標
本章的目標是使用ATL創建一個進程內COM組件。在此組件里,將實現COM類CStatistic及COM接口IStatistic,用來進行統計計算。IStatistic的詳細信息如下:
1、方法
void Reset(); //重新開始統計計算
void Add(double dVal); //增加一個數據
2、屬性
long Count; //返回數據個數
double Average; //返回平均值
double StdDev; //返回標准差
CStatistic的VC++代碼如下:
#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,新建"ATL COM AppWizard"項目,如下圖所示。配置好項目名稱、項目目錄后,單擊"OK"按鈕。
圖1.1
顯示界面如下所示,直接單擊"Finish"按鈕。
圖1.2
顯示界面如下,單擊"OK"按鈕,完成項目創建。
圖1.3
1.2.2 VC++2010
運行VC++2010,新建"ATL Project"項目,如下圖所示。配置好項目名稱、項目目錄后,單擊"OK"按鈕。
圖1.4
顯示創建向導,界面如下面兩張圖所示:
圖1.5 創建向導——頁面一
圖1.6 創建向導——頁面二
單擊上圖的"Finish"按鈕,完成項目的創建。
1.3 增加COM類
現在,往項目里增加COM類。
1.3.1 VC++6.0
單擊【Insert】【New ATL Object...】菜單項
圖1.7
顯示界面如下。請選中"Objects"里的"Simple Object",然后單擊"Next"按鈕。
圖1.8
顯示界面如下,一共有兩個頁面。
圖1.9 增加COM類——頁面一
下圖所示界面里,Interface有兩個選項:Dual表示雙接口(也叫自動化接口),它派生自IDispatch;Custom表示自定義接口,它派生自IUnknown。自定義接口直接訪問虛函數表,其效率較高。自動化接口效率較低,但是它支持的語言較多。
圖1.10 增加COM類——頁面二
1.3.2 VC++2010
單擊【Project】【Add Class...】菜單項
圖1.11
選中"ATL Simple Object",然后單擊"Add"按鈕
圖1.12
顯示界面如下,一共有三個頁面
圖1.13 增加COM類——頁面一
圖1.14 增加COM類——頁面二
圖1.15 增加COM類——頁面三
1.3.3 項目結構
VC++6.0的類視圖里增加了"CStatistic"和"IStatistic"。
IStatistic是COM接口,客戶端程序通過它訪問COM組件。
CStatistic是COM類,真正的工作由它來完成。
圖1.16
同時,idl文件也發生了變化,如下表所示。增加了接口IStatistic,增加了COM類Statistic,這個COM類實現了接口IStatistic。
// comDLLatl.idl import "oaidl.idl"; import "ocidl.idl"; [ uuid(0FEBC618-38BC-4A5B-AE09-3C56635F4D73), version(1.0), helpstring("comDLLatl 1.0 Type Library") ] library COMDLLATLLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ object, uuid(7FB59FA6-E715-4C23-9520-DE93293E0B5F), helpstring("IStatistic Interface"), pointer_default(unique) ] interface IStatistic : IUnknown { }; [ uuid(471BF24D-B793-44A1-9205-7BF36A6EB698), helpstring("Statistic Class") ] coclass Statistic { [default] interface IStatistic; }; }; |
1.4 增加方法
1.4.1 VC++6.0
鼠標右鍵單擊接口IStatistic,彈出菜單中單擊【Add Method...】菜單項
圖1.17
下圖就是增加方法的界面。這里增加了方法void Add(double dVal)。單擊"OK"按鈕,完成方法的增加。注意:對於自動化接口,Return Type只能是HRESULT。
圖1.18
可使用同樣的方法,增加方法void Reset()。
1.4.2 VC++2010
鼠標右鍵單擊接口IStatistic,彈出菜單中單擊【Add】【Add Method...】。
圖1.19
增加方法的界面如下。與VC++6.0的大致相同。單擊"Finish"按鈕,完成方法void Add(DOUBLE dVal)的添加。
圖1.20
可使用同樣的方法,增加方法void Reset()。
1.5 增加屬性
1.5.1 VC++6.0
在圖1.17中,單擊【Add Property...】菜單項。顯示界面如下:
這里增加了屬性long Count。注意:沒有設置"Put function",說明這個屬性是只讀屬性。
單擊"OK"按鈕,完成屬性的增加。
圖1.21
同樣方法,可以增加屬性double Average和double StdDev。
1.5.2 VC++2010
在圖1.19中,單擊【Add Property...】菜單項。顯示界面如下:
這里增加了屬性ULONG Count。注意:沒有設置"Put function",說明這個屬性是只讀屬性。
單擊"Finish"按鈕,完成屬性的增加。
圖1.22
同樣方法,可以增加屬性double Average和double StdDev。
1.6 刪除方法、屬性
使用ATL,刪除屬性、方法,似乎只能手動進行,相當的麻煩。
1.7 編碼
1.7.1 增加成員變量
請給CStatistic增加三個成員變量
private: ULONG m_nCount; double m_dSum; double m_dSum2; |
1.7.2 初始化成員變量
CStatistic的構造函數里,初始化這三個成員變量
CStatistic() { m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; } |
1.7.3 實現Add
STDMETHODIMP CStatistic::Add(double dVal) { ++m_nCount; //樣本個數 m_dSum += dVal; //所有樣本值的和 m_dSum2 += dVal * dVal; //所有樣本值的平方和 return S_OK; } |
1.7.4 實現Reset
STDMETHODIMP CStatistic::Reset() { m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; return S_OK; } |
1.7.5 實現get_Count
STDMETHODIMP CStatistic::get_Count(long *pVal) { *pVal = m_nCount; return S_OK; } |
1.7.6 實現get_Average
STDMETHODIMP CStatistic::get_Average(double *pVal) { if(m_nCount) { *pVal = m_dSum / m_nCount; } else { *pVal = 0.0; } return S_OK; } |
1.7.7 實現get_StdDev
STDMETHODIMP CStatistic::get_StdDev(double *pVal) { *pVal = 0.0; if(m_nCount > 1) { *pVal = (m_nCount * m_dSum2 - m_dSum * m_dSum) / (m_nCount * (m_nCount - 1)); if(*pVal > 0.0) { *pVal = sqrt(*pVal); } } return S_OK; } |
1.8 注冊、注銷
編譯comDLLatl,即可得到進程內COM組件comDLLatl.dll。使用它之前,需要注冊。
注冊組件可使用如下任意一條命令。它們原理相同:都是載入comDLLatl.dll,然后調用DllRegisterServer函數
regsvr32 comDLLatl.dll |
Rundll32 comDLLatl.dll,DllRegisterServer |
注銷組件可使用如下任意一條命令。它們原理相同:都是載入comDLLatl.dll,然后調用DllUnregisterServer函數
regsvr32 /u comDLLatl.dll |
Rundll32 comDLLatl.dll,DllUnregisterServer |
注意:VC++2010可以編譯生成64位的COM組件。在64位操作系統上,regsvr32.exe和Rundll32.exe將自動識別COM組件是32位的還是64位的。注冊信息會寫入注冊表的如下幾個位置。注意這里的<ProgID>其實就是comDLLatl.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位是可以同時注冊在64位Windows上的,它們互不干涉。
第2章 VC++使用組件
2.1 #import
請參考《COM組件(MFC篇)》。需要注意的是:MFC創建的COM組件,其接口必定是Dual接口(圖1.10、圖1.15中的Interface選項),也就是派生自IDispatch的自動化接口。ATL可以創建Custom接口,這種接口將派生自IUnknown,tli文件里的函數通過虛函數實現,效率較高。Custom接口的不足之處在於:它可能不能被某些腳本語言(如:VBS)調用。
2.2 MFC包裝類
如果接口是Dual接口,就可以生成MFC包裝類。具體操作請參考《COM組件(MFC篇)》。
2.3 C語言調用
使用C語言也可以訪問COM組件。
如果接口是Dual接口,請參考《COM組件(MFC篇)》。
如果接口是Custom接口,則可以使用編譯類型庫時產生的C/C++頭文件。如下面這段代碼:(節選自comDLLatl_i.h)
#define IStatistic_Add(This,dVal) ((This)->lpVtbl->Add(This,dVal)) #define IStatistic_get_Count(This,pVal) ((This)->lpVtbl->get_Count(This,pVal)) |
亦即:可以通過宏IStatistic_Add、IStatistic_get_Count去訪問方法、屬性。