首先应当明确,MFC中是通过嵌套类而不是多重继承来实现COM接口的,通过接口映射机制将接口和实现该接口的嵌套类关联起来;MFC中提供一套简明的宏来实现嵌套类的定义.其次,MFC通过CCmdTarget类实现了IUnknown接口。
本文首先描述创建一个COM服务器的步骤和核心代码.然后说明客户程序关键代码。
此COM服务器实现一个TimeLogServer组件,为简明起见,此组件只有一个接口ITimeLog,通过ITimeLog的方法OutputLog可以将日志文本输出至日志文件。
创建一个MFC DLL工程,选择支持Automation(当然本程序不一定是自动化服务器,在这里这样做好处在于自动实现了几个必要的输出函数如DllGetClassObject,DllRegisterServer等,否则要自己写)
第一节 COM服务器
一. 声明组件和接口
1.写一个GUIDs.h,在GUIDs.h中声明组件和接口的GUID
//声明组件GUID {A433E701-E45E-11d3-97B5-52544CBA7F28} //DEFINE_GUID(CLSID_TimeLogServer, //0xa433e701, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); static const IID CLSID_TimeLogServer = {0xa433e701, 0xe45e, 0x11d3, {0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28}}; // 声明接口GUID{A433E702-E45E-11d3-97B5-52544CBA7F28} //DEFINE_GUID(IID_ITimeLog, //0xa433e702, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); static const IID IID_ITimeLog = {0xa433e702, 0xe45e, 0x11d3, {0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28}}; |
2.写一个ITimeLogServer.h,在ITimeLogServer.h文件中声明组件和接口
//ITimeLogServer.h #include ";GUIDs.h"; //接口ITimeLog的声明 DECLARE_INTERFACE_(ITimeLog,IUnknown) { STDMETHOD(OutputLog)(BSTR* varLogText)PURE; }; |
说明:
1.宏DEFINE_GUID将组件和接口的progid与GUID相关联.可以用guidgen.exe工具产生。
2.宏DECLARE_INTERFACE_声明接口;该宏第一个参数为接口名,第二个参数为该接口的基类.声明没有基类的接口用DECLARE_INTERFACE宏。
3.宏STDMETHOD声明接口中的方法.此方法的返回值为HRESULT.PURE被解释为";=0";,即此方法为纯虚函数.当方法的返回值不是HRESULT时,用宏STDMETHOD_(返回类型,函数名)(参数)PURE; 二.声明组件类CTimeLogServer和实现接口的嵌套类
在ClassWizard中添加新类CTimeLogServer,其基类选择为CCmdTarget.修改其头文件TimeLogServer1.h,加上#include ";ITimeLogServer.h";;同时在类声明体中加上
//声明实现ITimelog接口的嵌套类 BEGIN_INTERFACE_PART(TimeLog,ITimeLog)//自动声明IUnknown接口的三个方法 STDMETHOD(OutputLog)(BSTR* varLogText); END_INTERFACE_PART(TimeLog) //声明接口映射 DECLARE_INTERFACE_MAP() //声明类厂 DECLARE_OLECREATE(CTimeLogServer) |
三.实现类厂和接口映射
在CTimeLogServer的实现文件中写入:
//实现类厂 IMPLEMENT_OLECREATE(CTimeLogServer,";TimeLogServer";, 0xa433e701, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); //映射接口到相应的嵌套类 BEGIN_INTERFACE_MAP(CTimeLogServer,CCmdTarget) INTERFACE_PART(CTimeLogServer,IID_ITimeLog,TimeLog) END_INTERFACE_MAP() |
四.在组件的构造和析构函数中对全局对象计数
CTimeLogServer::CTimeLogServer() { ::AfxOleLockApp(); }
CTimeLogServer::~CTimeLogServer() { ::AfxOleUnlockApp(); }
|
五.为嵌套类实现IUnknown接口
//为嵌套类而实现IUnknown接口 STDMETHODIMP_(ULONG) CTimeLogServer::XTimeLog::AddRef() { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalAddRef(); }
STDMETHODIMP_(ULONG) CTimeLogServer::XTimeLog::Release() { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalRelease(); }
STDMETHODIMP CTimeLogServer::XTimeLog::QueryInterface(REFIID riid,void**ppvObj) { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalQueryInterface(&;riid,ppvObj); }
|
说明:虽然CCmdTarget类已经实现了IUnknown接口,但是还必须通过上述代码来将嵌套类的IUnknown映射到CCmdTarget支持的IUnknown接口.METHOD_PROLOGUEH宏的两个参数分别是实现组件对象的类和实现接口的嵌套类。 六.实现ItimeLog接口的方法OutputLog
注意本组件的功能是往日志文件中输入日志.
1. 在组件类中添加一个文件指针:
// Attributes public: protected: FILE* m_logfile; |
2. 初始化和退出
首先在CTimeLogServer的构造函数中进行一些初始化:
CTimeLogServer::CTimeLogServer() { ::AfxOleLockApp(); CTime TimeStamp = CTime::GetCurrentTime(); CString FileName; FileName.Format(_T(";%s.log";),TimeStamp.Format(";%Y%m%d";)); m_logfile = fopen(FileName,_T(";a";)); if(m_logfile) { fprintf(m_logfile,_T(";# # # # # # # # # # # # # # # # # \n";)); fprintf(m_logfile,_T(";开始于:%s";),(LPCTSTR)TimeStamp.Format(";%Y年%m月%d日%H:%M %S";)); fprintf(m_logfile,_T(";\n";)); } } //然后在析构函数中关闭文件 CTimeLogServer::~CTimeLogServer() { ::AfxOleUnlockApp(); if(m_logfile) { CTime TimeStamp = CTime::GetCurrentTime(); fprintf(m_logfile,_T(";\n";)); fprintf(m_logfile,_T(";结束于:%s";),(LPCTSTR)TimeStamp.Format(";%Y年%m月%d日%H:%M %S";)); fprintf(m_logfile,_T(";\n";)); fprintf(m_logfile,_T(";# # # # # # # # # # # # # # # # #\n";)); fclose(m_logfile); } } |
3. 实现接口ITimeLog方法
//实现接口ITimeLog方法 STDMETHODIMP CTimeLogServer::XTimeLog::OutputLog(BSTR* varLogText) { METHOD_PROLOGUE(CTimeLogServer,TimeLog) if(pThis->;m_logfile) { CTime TimeStamp = CTime::GetCurrentTime(); CString NowTime = TimeStamp.Format(";%Y年%m月%d日%H:%M:%S";); CString LogText((LPCWSTR)*varLogText); fprintf(pThis->;m_logfile,";\n%s\n%s\n%";,NowTime,LogText); return NOERROR; } else { AfxMessageBox(";没有日志文件!";); return S_FALSE; } } |
七.完善组件服务器
在应用对象CTimeLogServerApp的 实现文件中,处理Instance()和ExitInstance()
BOOL CTimeLogServerApp::InitInstance() { ::AfxOleLockApp(); // Register all OLE server (factories) as running. This enables the // OLE libraries to create objects from other applications. COleObjectFactory::RegisterAll();
return TRUE; } int CTimeLogServerApp::ExitInstance() { // TODO: Add your specialized code here and/or call the base class ::AfxOleUnlockApp(); return CWinApp::ExitInstance(); }
|
第二节 客户程序
使用COM组件服务器的客户程序关键步骤是:初始化COM库,创建组件对象并获取IUnknown接口指针,查询接口并使用,释放组件。
#include ";ITimeLogServer.h"; //初始化COM库,对组件实例化 HRESULT hResult; IUnknown* pIUnknown; hResult = ::CoInitialize(NULL); if(FAILED(hResult)) { ::AfxMessageBox(";不能初始化COM库!";); return FALSE; }
//创建组件实例 pIUnknown = NULL; hResult = ::CoCreateInstance(CLSID_TimeLogServer,NULL, CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&;pIUnknown); if(FAILED(hResult)) { pIUnknown = NULL; ::AfxMessageBox(";不能创建TimeLog对象!";); return FALSE; } //查询接口并使用 if(pIUnknown!=NULL) { ITimeLog* pITimeLog; HResult=pIUnknown->;QueryInterface(IID_ITimeLog,(void**)&;pITimeLog); if(FAILED(hResult)) { ::AfxMessageBox(";不能获取接口ITimeLog!";); pIUnknown->;Release(); return; } BSTR bstrLogText; bstrLogText = m_logtext.AllocSysString(); CString text((LPCWSTR)bstrLogText); ::AfxMessageBox(text);
if(FAILED(pITimeLog->;OutputLog(&;bstrLogText))) { ::AfxMessageBox(";日志输出出错!";); pITimeLog->;Release(); return; } pITimeLog->;Release(); ::AfxMessageBox(";日志已经写入!";); } //释放组件 pIUnknown->;Release(); pIUnknown = NULL; ::CoUninitialize(); |