目錄
1.3.2 覆蓋應用程序類的InitInstance函數 7
2.2.3 修改應用程序類的InitInstance函數 24
第1章創建進程內組件
1.1 目標
本章的目標是使用MFC創建一個進程內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,新建"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.0與VC++2010的區別
1、VC++6.0缺少DllUnregisterServer函數。這意味着VC++6.0編譯生成的COM組件不能通過regsvr32 /u comDLLmfc.dll進行注銷;
2、DllRegisterServer函數里,VC++6.0缺少對類型庫的注冊,即缺少代碼AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid);
3、CcomDLLmfcApp::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文件名;
2、71719C6D-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,把下划線替換為減號即可使用;
3、version(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函數並導出:DllCanUnloadNow、DllGetClassObject、DllRegisterServer、DllUnregisterServer。代碼如下:
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、主版本號、次版本號。請與接口定義文件內容保持一致;
2、VC++6.0的AfxOleUnregisterTypeLib函數只有一個參數,請借鑒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 TEXTINCLUDE與END之間的語句自動插入到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文件內容有誤。請關閉項目,刪除aps、clw、ncb、opt文件。再次打開項目,然后按下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"。
IStatistic是COM接口,客戶端程序通過它訪問COM組件。
CStatistic是COM類,真正的工作由它來完成。
圖1.13
同時,odl文件也發生了變化,如下表所示。增加了接口IStatistic(dispinterface表示這個接口派生自IDispatch)。增加了COM類Statistic,這個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.0與VC++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 Average和double StdDev。
1.6.2 VC++2010
在圖1.16中,單擊【Add Property...】菜單項。顯示界面如下:
這里增加了屬性ULONG Count。注意:沒有設置"Set function",說明這個屬性是只讀屬性。
單擊"Finish"按鈕,完成屬性的增加。
圖1.19
同樣方法,可以增加屬性double Average和double 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.exe和Rundll32.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位是可以同時注冊在64位Windows上的,它們互不干涉。
1.10 再論增加COM類
在圖1.9和圖1.12中,"Automation"有三個選項:None、Automation、Createable by type ID。三者有何區別?
"None"表示創建一個普通的C++類,也就是說選擇此項,創建出來的就不是COM類了。
"Createable by type ID"創建出來的是COM類,而且它可以根據輸入的ProgID實例化。
"Automation"創建出來的雖然也是COM類,但是它缺少了下面這幾條語句:
1、缺少IMPLEMENT_OLECREATE
缺少下面這條語句,意味着COM類CStatistic不能被注冊、注銷,也不可能被客戶端程序實例化。
IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19)
2、因為COM類CStatistic不能被實例化,因此其構造函數里缺少了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文件名;
2、B4573BE3-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,把下划線替換為減號即可使用;
3、version(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 TEXTINCLUDE與END之間的語句自動插入到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; } } } |
注意:
1、VC++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);
clsid是COM類Statistic的GUID,請與接口定義文件內容保持一致,如:
// {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 |
注意:
1、VC++6.0不支持命令開關/Register、/Regserver、/RegisterPerUser、/RegserverPerUser,也不支持CCommandLineInfo::AppRegister。直接運行程序即可完成注冊;
2、VC++6.0不支持命令開關/RegisterPerUser、/RegserverPerUser、/UnregisterPerUser、/UnregserverPerUser。VC++2010支持,此時InitInstance函數里的cmdInfo.m_bRegisterPerUser 為 TRUE;
3、注冊時InitInstance函數里的ParseCommandLine(cmdInfo);將解析命令行開關,發現/Register、/Regserver、/RegisterPerUser、/RegserverPerUser之一時,會設置cmdInfo.m_nShellCommand為CCommandLineInfo::AppRegister。此時,注冊組件的代碼被執行(其實每次正常運行程序,以下注冊組件的代碼也會被執行)。
COleObjectFactory::UpdateRegistryAll(); AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid); |
4、注銷時InitInstance函數里的ParseCommandLine(cmdInfo);將解析命令行開關,發現/Unregister、/Unregserver、/UnregisterPerUser、/UnregserverPerUser之一時,會設置cmdInfo.m_nShellCommand為CCommandLineInfo::AppUnregister。此時,注銷組件的代碼被執行。
COleObjectFactory::UpdateRegistryAll(FALSE); AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor); |
5、COM客戶端調用時,會啟動進程外組件,並增加命令開關/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.tlh和comDLLmfc.tli。查看comDLLmfc.tlh就可以知道COM組件(comDLLmfc.dll)的接口類IStatistic的定義。查看comDLLmfc.tli就能知道IStatistic的方法是如何實現的。
MFC編寫的COM組件,均為自動化接口,即COM接口派生自IDispatch。對屬性、方法的訪問,均是通過IDispatch::Invoke函數實現。
3.2 MFC包裝類
MFC包裝類就是生成一個派生自COleDispatchDriver的C++類,通過這個類去訪問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(); } |
說明:
1、MFC編寫的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對象 |
注意:后期綁定的代碼執行效率比前期綁定要低。