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.
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
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
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
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
UINTs, 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
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
CView object and much of what I am presenting here can be translated with little work to the other
CView. When the function
InstallExtMenu is called, the
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
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);
CFrameWnd contains a member variable called
m_bAutoMenuEnable which is used to disable menus that do not have
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
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,
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.
This is version 1.0!