Introduction
This article shows how to add a menu interface to an application from a DLL at any time.
This example was written using VC++.NET 2003 but it should translate to VC++ 6.0 easily, as much of this was developed using 6.0 early on. No managed code was harmed in the process of writing this article.
Background
I wanted a flexible way to load a DLL into my application to test it in-house and not leave any marks when the customer got the application. Future enhancements could include targeted computer-based training modules.
This is not meant to be a be-all end-all treatise, but, rather, a springboard for extending applications after the application has been written.
Using the code
There are two parts to this problem: the plug-in DLL and the target application. The target application needs to know how to call the plug-in DLL without the benefit of lib file. We accomplish this by standardizing the plug-in interface. The interface for this project is in the TestPlugin project in plugin_api.h. There are other ways of handling the interface. For instance, you could create a structure of function pointers and populate it after LoadLibrary
and clean it out before FreeLibrary
. In this example, you only have to store the HMODULE
value. If you had more than one plug-in DLL, you would only need to store the HMODULE
values and not have to carry many structures of the function pointers.
Let's talk about the plug-in DLL first.
The Interface: There are four methods defined publicly for this DLL in TestPlugin.def. They are InstallExtMenu
and RemoveExtMenu
which install and remove the menus respectively, GetExtMenuItemCount
which gives the application the number of menu items installed, and GetExtMenuItem
which serves to map the menu control ID to a Windows message identifier. More can be done to extend the interface but this appears to be the bare minimum to get this up and running. The file plugin_api.h handles the details of connecting to the correct DLL based on a save HMODULE
value from LoadLibrary
.
CTestPluginApp: There are two ways of introducing user-defined Windows messages. One is defining a value greater than WM_USER
; the other is to get a value using RegisterWindowMessage
. WM_USER
is useful for messages internal to an application. RegisterWindowMessage
is useful when a particular message may be used across applications. We are using RegisterWindowMessage
because this DLL may be servicing more than one application and because other DLLs can also use the registered messages. The registered messages are really static UINT
s, attached to the CTestPluginApp
object and initialized when the DLL is loaded. CTestPluginApp
also contains the menu ID registered to the message ID map which is used by GetExtMenuItem
to return the registered message when the menu item is selected. You will notice that the map class used is MFC's CMap<>
template. My only reason for using it here is to maintain the MFC framework and not to clutter the code by importing STL. My personal preference is to use std::map
over CMap
.
CCommandWnd
: This window receives the registered message from the target application. When the application initializes the plug-in DLL, it passes an HWND
to the DLL so that the DLL can set up the menus for that window. In addition to setting up the menus, the DLL also creates a CCommandWnd
window as a child to the window passed in.
Now let's talk about the target application.
CMainFrame
: Okay, so making the CMainFrame
window responsible for maintaining the menus and how they are handled is a bit arbitrary. You could do this either from the CView
-based window or the CDocument
object. I wanted to keep all of the code for handling the menus and the plug-in in one place and CMainFrame
seemed to be the easiest way to handle it. There are some really good reasons for relocating the code to the CDocument
or CView
object and much of what I am presenting here can be translated with little work to the other CDocument
and CView
. When the function InstallExtMenu
is called, the HWND
for CMainFrame
is sent along with a value which will be used to identify the CCommandWnd
from the plug-in. The plug-in creates the CCommandWnd
as a child window to CMainFrame
and the CCommandWnd
window can be retrieved using CWnd::GetDlgItem
.
CMainFrame::OnCommand
: This is where we translate the menu command ID to the registered message used by the plug-in DLL (the red indicates the plug-in's interface):
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
UINT nSendMsg = 0 ;
if (::GetExtMenuItem(m_TestModule, (UINT)wParam, &nSendMsg) != FALSE)
{
CWnd * pWnd = GetDlgItem( CHILD_WINDOW_ID ) ;
if ( pWnd != NULL && pWnd->GetSafeHwnd() != NULL )
{
return (BOOL)pWnd->SendMessage( nSendMsg, 0, 0 ) ;
}
}
return CFrameWnd::OnCommand(wParam, lParam);
}
CMainFrame::OnCmdMsg
: CFrameWnd
contains a member variable called m_bAutoMenuEnable
which is used to disable menus that do not have ON_COMMAND
or ON_UPDATE_COMMAND_UI
handlers. The menus that the plug-in installs do not have native handlers, so you would think that we need to set m_bAutoMenuEnable
to FALSE
to enable our menus. Unfortunately, this would also enable other menus that we may not want enabled. Fortunately, we don't need to bother with m_bAutoMenuEnable
. By overriding CFrameWnd::OnCmdMsg
, we can ask the plug-in (if it is loaded) if it owns this menu and enable it if it does:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if ( nCode == CN_COMMAND )
{
UINT nPostItem = 0 ;
if ( ::GetExtMenuItem( m_TestModule, nID, &nPostItem ) != FALSE )
{
return TRUE ;
}
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
Points of Interest
I have set up a macro called _PLUGIN_ON_DEMAND
in stdafx.h. If you undef this macro, the CTargetApp
will try to load the DLL in its InitInstance method and unload it in the ExitInstance
method. I have also included an alternate IDR_MAINFRAME
menu (with the subtitle ALTMENU) that you can use when the _PLUGIN_ON_DEMAND
is not defined to show how the menus can be added when the top-level Tests menu does not exist.
History
This is version 1.0!
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.