张邦华
MFC动态库分为规则动态库和扩展动态,根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。在本实例介绍中,采用MFC规则动态库,动态调用的方式。由此,基于VS2003建立两个工程,一个就是被动态嵌入菜单的动态库工程(MenuDll),另外一个就是测试程序(TestDll),测试程序采用单文档MFC。以上不是本文重点论述对象,就不详细介绍。要解决动态库嵌入菜单问题,需要考虑两个方面的问题。第一就是动态库菜单的自动添加,第二就是菜单消息的响应机制。 按照一般的动态库接口添加办法,在MenuDll.def文件中加入动态添加、删除菜单函数接口InstallExtMenu和RemoveExtMenu,以及得到菜单数量的函数接口GetExtMenuItemCount和通过菜单ID映射Windows消息ID函数接口 GetExtMenuItem,在MenuDll.cpp中添加这些函数接口定义,下面是添加测试菜单及其消息映射CMap表,代码如下: BOOL WINAPI InstallExtMenu( HWND ParentWindow,////父辈窗口句柄 DWORD ChildID,///动态菜单ID UINT * NextMenuID )////动态添加菜单 { AFX_MANAGE_STATE(AfxGetStaticModuleState()); BOOL bReturn = FALSE ; if ( ParentWindow != NULL && theApp.m_CommandWnd == NULL )////父辈窗口存在,以及消息响应窗口还没有创建 { HMENU hMenu = ::GetMenu( ParentWindow ) ;////得到父辈窗口的菜单句柄 if ( hMenu != NULL ) { CMenu ParentMenu ; ParentMenu.Attach( hMenu ) ; CMenu * SubMenu = NULL ; UINT TestPosition = GetMenuPosition( &ParentMenu, _T("测试") ) ; // 得到“测试”菜单未知,此函数在msdn中有详细解释。 if ( TestPosition != MENU_POSITION_ERR ) // 如果“测试”菜单存在就得到其子菜单 { SubMenu = ParentMenu.GetSubMenu( TestPosition ) ; } if(SubMenu == NULL)////如果没有子菜单,就添加子菜单 { CMenu Menu ; Menu.CreateMenu() ; HMENU hSubMenu = Menu.Detach(); ParentMenu.InsertMenu( ParentMenu.GetMenuItemCount(), MF_BYPOSITION| MF_POPUP|MF_STRING, (UINT_PTR)hSubMenu, _T("测试") ) ; TestPosition = GetMenuPosition( &ParentMenu, _T("测试") ) ; SubMenu = ParentMenu.GetSubMenu( TestPosition ) ;
} if ( SubMenu->GetMenuItemCount() > 0 )/////如果子菜单存在,就先添加一个分割线。 { SubMenu->AppendMenu( MF_SEPARATOR, 0, _T("") ) ; } theApp.m_CommandToMessage.InitHashTable(ID_COUNT) ;/////添加两个子菜单 SubMenu->AppendMenu( MF_STRING, (UINT_PTR)(*NextMenuID), _T("测试菜单1") ) ; theApp.m_CommandToMessage.SetAt( (*NextMenuID)++, CMenuDllApp::ID_MENU_TEST1 ) ; SubMenu->AppendMenu( MF_STRING, (UINT_PTR)(*NextMenuID), _T("测试菜单2") ) ; theApp.m_CommandToMessage.SetAt( (*NextMenuID)++, CMenuDllApp::ID_MENU_TEST2 ) ; ParentMenu.Detach() ; theApp.m_CommandWnd = new CMenuWnd ; // 创建消息响应窗口 bReturn = theApp.m_CommandWnd->Create( NULL, _T(""), WS_CHILD, CRect(-1,-1,-1,-1), CWnd::FromHandle(ParentWindow), ChildID ) ; CWnd::FromHandle(ParentWindow)->DrawMenuBar() ; //强制重画菜单. } } return bReturn ; } 1. 菜单消息响应机制 Windows消息响应可以分为两种,一种是采用用户自定义的消息WM_USER机制,另外一种采用Windows注册消息机制,Windows注册消息是一个静态UINT变量,在动态库加载时注册,动态库卸载时取消。但是,其中需要解决一个问题,就是菜单ID和Windows消息ID之间的匹配关系。因为所建动态库是MFC规则动态库,故可以采用MFC的模板类Map来映射二者之间的关系,通过函数接口GetExtMenuItem来查询二者之间的关系。在menudll.h中定义: typedef CMap<UINT,UINT,UINT,UINT> CommandToMessage m_CommandToMessage; 在CTestPluginApp类中定义保存所注册的消息ID的变量。 static const UINT ID_MENU_TEST1 ;static const UINT ID_MENU_TEST2 ;以及在动态库加载时注册这两个消息。并和菜单项ID建立对应映射表。 为了简化消息响应,在动态库中添加一个继承与CWnd类的子类,主要目的是采用MFC的消息动态映象机制DECLARE_DYNAMIC(CMenuWnd)来处理消息响应。在其中添加消息响应函数接口: BEGIN_MESSAGE_MAP(CMenuWnd, CWnd) ON_REGISTERED_MESSAGE( CMenuDllApp::ID_MENU_TEST1, OnMenuTest1 ) ///第一个菜单消息响应接口 ON_REGISTERED_MESSAGE( CMenuDllApp::ID_MENU_TEST2, OnMenuTest2 ) ///第二个菜单消息响应接口 END_MESSAGE_MAP()。 2. 主框架消息传递 依据MFC消息响应机制,可以在视类或者文档类中来响应所涉及的消息,为了方便处理,这里主要在主框架类中来处理该消息。按照一般的动态库调用机制,首先将动态库调入,保存其句柄。 在主框架中添加动态库句柄:HMODULE m_TestModule ;在主菜单项中添加菜单“测试”,和弹出菜单“添加动态库”,定义其ID为ID_LOADMENUDLL;并在主框架类中添加其响应事件。在响应事件中添加加载动态库功能,并加载菜单。为了使菜单能够响应操作,需要在主框架中继承两个虚函数OnCmdMsg和OnCommand,并在其中添加其消息响应机制: BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // TODO: 在此添加专用代码和/或调用基类 if ( nCode == CN_COMMAND )////如果是菜单命令 { // if nID translates to our internal message then enable the menu item // otherwise, let OnCmdMsg() handle nID. UINT nSendMsg = 0 ; if(m_TestModule )////菜单动态库已经加入 { GETMENUITEM GetMenuItem =(GETMENUITEM)GetProcAddress( m_TestModule, "GetExtMenuItem"); nSendMsg = GetMenuItem( nID);////判断是不是所添加的菜单消息 if(nSendMsg)/////是菜单消息就返回所所得到的消息,否则返回0 return TRUE ;//// } } return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);////不是时就默认处理 } BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)/////分发命令 { // TODO: 在此添加专用代码和/或调用基类 UINT nSendMsg = 0 ; if(m_TestModule )/////动态库已经加载,首先就考虑是不是动态库的菜单命令 { GETMENUITEM GetMenuItem =(GETMENUITEM)GetProcAddress( m_TestModule, "GetExtMenuItem"); nSendMsg = GetMenuItem( (UINT)wParam);////同上述 if(nSendMsg)////是动态库命令就获取动态库所创建的窗口类,然后分发命令,不是就采用默认处理 { CWnd * pWnd = GetDlgItem( CHILD_WINDOW_ID ) ; if ( pWnd != NULL && pWnd->GetSafeHwnd() != NULL ) { return (BOOL)pWnd->SendMessage( nSendMsg, 0, 0 ) ; } } } return CFrameWnd::OnCommand(wParam, lParam); }
|