分类: C/C++
2008-03-18 14:19:24
本文只介绍《COM应用程序框架》的主要设计部分,更多、更详细的文档信息请参见下载文件包中的文档和源代码。
一、设计说明
《COM应用程序框架》是把标准的Microsoft Windows多文档处理应用程序使用COM技术来设计.所以针对多文档处理应用程序的需求,不再多写。如果您不了解请参见MSDN或者是其它编程基础方面的书籍。
《COM应用程序框架》分为两种,一种是COM多文档应用程序框架,第二种是单文档应用程序框架。在这里我们只介绍多文档应用程序,不介绍单文档应用程序。
《COM应用程序框架》建立在一个单独的AIFrame.DLL文件中,所有的功能都通过COM接口进行操作.《COM应用程序框架》将使用如下两种库的组合进行设计,1.使用MFC+ATL组合, 2.使用WTL+ATL组合,下面分别对这两种组合的优点和缺点说明一下,最后选择一种最优组合。
1. 使用MFC+ATL组合图:
2. 使用WTL+ATL组合图:
从上面两附图中很容易看出,使用MFC+ATL组合开发COM应用程序框架,MFC存在一层函数调用,代码执行速度会慢一些。小程序可能看不出来,大程序也就明显了。 如果使用WTL+ATL组合开发COM应用程序框架,就不会多一层调用,代码执行速度非常快,就像是用Win32 SDK编写代码一样没有什么区别,因为WTL是模板代码,在编译后不会存在一层函数调用。所以《COM应用程序框架》将采用WTL+ATL组合进行设计,这可能是最佳方案。
二、通用设计
1. 数据视图
所谓数据视图,就是添加到《COM应用程序框架》中每一个窗口,无论这个窗口是用做什么,还是什么形状的,统称为数据视图。
所有客户端程序向《COM应用程序框架》添加的数据视图必须从IDataView纯虚接口继承下来,必须是.这样《COM应用程序框架》才能工作正常。
数据视图纯虚接口:IDataView。父类是IDispatch。
2. 命令的响应函数
函数名称:NotifyCommand(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle);
参数:codeNotify - 通报代码,现在没有使用。
cmdID – 某个命令ID,可以是菜单也可以是工具格中的按钮。
bHandle – 如果命令还继续向下路径设置为VARIANT_TRUE,不向下路径设置为VARIANT_FALSE.
三、《COM多文档应用程序框架》设计
1. 框架
《COM多文档(MDI)应用程序框架》只有一个MDI主窗口,但是可以拥有N多MDI子窗口,每个MDI子窗口可以拥有多个数据视图,每一个数据视图必须从IDataView接口继承下来。
MDI主窗口框架类名:CMDIFrame,接口是IMDIFrame,是所有MDI子窗口、停靠数据视图、工具条、菜单的容器,并负责把命令分发给当前活动的MDI子窗口或者是数据视图。MDI子窗口框架类名:CMDIChildFrame,接口是IMDIChildFrame。可以拥有N多数据视图,并把命令分发给当前活动的数据视图。
MDI子窗口框架集合类名:CMDIChildFrames,接口是IMDIChildFrames,用来管理所有的MDI子窗口和创建DMI子窗口。
《COM多文档应用程序框架》接口图:
2. 事件
MDIFrame事件 目前仅提供两个事件。
OnQuit(VARIANT_BOOL *vbQuit)
NotifyCommand(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle)
IMDIChildFrame事件 目前仅提供一个事件。
NotifyCommand(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle)
3. 命令路由
MDI主窗口框架、MDI子窗口框架事件类和数据视图都拥有一个命令处理方法NotifyCommand。《COM多文档应用程序框架》就是通过NotifyCommand向客户端发送命令处理。
MDI主窗口事件首先接收到菜单或者是工具条命令,处理完必后返回或者是发送给MDI子窗口框架事件处理,在MDI子窗口框架处理完必后返回或者是发送给让当前活动的数据视图处理.完成后命令返回。
命令是否向下路由是有NotifyCommand方法的bHandle变量决定,设置为VARIANT_TRUE命令向下路由,设置为VARIANT_FALSE命令不向下路由。
4. UML类图
用COM设计的类比较长抓图不太方便,UML图、类的信息和函数成员请参见源代码。
四、 客户端设计
客户端应用程序必须实现两个功能块,第一个是数据视图,第二个是命令处理。
五、 项目文件夹说明
文件夹名称 |
类型 |
说明 |
AiFrame |
.dll |
《COM应用程序框架》全部代码所在文件夹。 |
inc |
.h |
《COM应用程序框架》客户端使用的一些公用头文件和已经设计的类。 |
MFC_Test_AiFrame |
.exe |
使用MFC调用《COM应用程序框架》的实例 |
WTL_Test_AiFrame |
.exe |
使用WTL调用《COM应用程序框架》的实例 |
Bin |
Bin |
生成的.dll 和.exe文件存放的文件夹 |
#include "..\inc\aiframeimpl.h"在某个 .cpp中加入
#include "..\inc\AiFrame_i.c"向项目中添加一个文本编辑视图类,如下图.
class CTextView : public CEditView ,public CDataViewImpl { .... NC_BEGIN_MAP() //在这里加入您的视图命令处理. NC_END_MAP() virtual HRESULT STDMETHODCALLTYPE CreateWnd(HWND hWndParent);... }打开TextView.cpp文件加入如下代码: //必须有CreateWnd方法,有《COM应用程序框架》调用。
HRESULT STDMETHODCALLTYPE CTextView::CreateWnd(HWND hWndParent) { CWnd *pWnd = (CWnd*)this; CWnd *wndParent = CWnd::FromHandle(hWndParent); CRect _Rect(0,0, 100, 100); if (pWnd->Create(NULL, ("MFC CEditView "), WS_HSCROLL|WS_VSCROLL|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE|ES_MULTILINE|ES_AUTOHSCROLL, _Rect, wndParent, 0)) { m_pEdit = &GetEditCtrl(); return S_OK; } return S_FALSE; }CxxxxxApp类中主要完成一个功能.继承CNotifyObjectImpl对象,用来处理《COM应用程序框架》发送来的命令.CNotifyObjectImpl类是InotifyObject接口的实现模板类。建立主窗口和一个新建文件的子菜单响应该命令.
class CFrameApp : public CWinApp ,public CComObjectRootEx ,public IDispEventImpl { public: CFrameApp(); IMDIFrame *m_lpMdiFrame; void WinMain(); BEGIN_SINK_MAP(CFrameApp) //不使用 _ATL_FUNC_INFO结构 iFrmae 在调用事件的时候返回"没有注册的库"。这是为什么? //SINK_ENTRY_EX(EventType, DIID__IMDIFrameEvents, 0x1, OnQuit) //SINK_ENTRY_EX(EventType, DIID__IMDIFrameEvents, 0x2, NotifyCommand) SINK_ENTRY_INFO(EventType, DIID__IMDIFrameEvents, DISPID_SHOW, OnQuit, &OnShowInfo1) SINK_ENTRY_INFO(EventType, DIID__IMDIFrameEvents, DISPID_SHOW2, NotifyCommand, &OnShowInfo3) END_SINK_MAP() NC_BEGIN_MAP() NC_COMMAND_ID_HANDLER(ID_FILE_NEW,FileNew) NC_COMMAND_ID_HANDLER(ID_FILE_EXIT,OnFileExit) NC_COMMAND_ID_HANDLER(ID_HELP_ABOUT,OnAbout) NC_COMMAND_ID_HANDLER(ID_HELP_WINDOWS, OnWindowWindows) NC_END_MAP() HRESULT CreateMain(); void FileNew(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle); void OnFileExit(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle); void OnAbout(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle); void OnWindowWindows(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle); STDMETHODIMP OnQuit(VARIANT_BOOL *vbQuit) { //MessageBox(0, _T("Events. Quit"), _T(""), 0); return S_OK; } // 重写 public: virtual BOOL InitInstance(); // 实现 DECLARE_MESSAGE_MAP() }; HRESULT CFrameApp::CreateMain() { CreateMDIStruct lpMDI={0}; HRESULT hr = 0; hr = CoCreateInstance(CLSID_MDIFrame, NULL, CLSCTX_ALL, IID_IMDIFrame, (VOID**)&m_lpMdiFrame); if (FAILED(hr)) { ATLASSERT(0); return hr; } lpMDI.cbSize = sizeof(CreateMDIStruct); lpMDI.lParam = NULL; lpMDI.lpszWindowName = L"Test COM MDIFrame"; lpMDI.hMenu = LoadMenu(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)); hr = m_lpMdiFrame->CreateWnd(&lpMDI); m_lpMdiFrame->ShowMe(VARIANT_TRUE); hr = this->DispEventAdvise((IUnknown*)m_lpMdiFrame); if (FAILED(hr)) { ATLASSERT(0); } return hr; }处理新建文件菜单的命令:
void CFrameApp::FileNew(UINT codeNotify, UINT cmdID, VARIANT_BOOL *bHandle) { IMDIChildFrames *lpFrames = NULL; IMDIChildFrame *lpChildFrame = NULL; HRESULT hr = 0; //从主窗口IMDIFrame接口引出IMDIChildFrames 集合. hr = m_lpMdiFrame->get_MDIChildFrames((IDispatch**)&lpFrames); if (FAILED(hr)) { ATLASSERT(0); } //用IMDIChildFrames 集合建立一个mdi子窗口.lpChildFrame,反回子窗口接口类. CTextView *lpTexView = NULL; lpTexView = new CTextView(); lpFrames->CreateChildFrame((IDataView*)lpTexView, &lpChildFrame); //设置子窗口标题. lpChildFrame->put_Title(L"MFC TextView"); }七、其它信息