Click here to Skip to main content
Click here to Skip to main content
Go to top

Plug-in DLLs and Menu Interfaces

, 14 Sep 2005
Rate this:
Please Sign up or sign in to vote.
An article on installing a menu interface for a load-on-demand DLL.

Before and After

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 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 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.

  1. 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.
  2. 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)
    {
        // if wParam translates to our internal message
        // then send the internal message
        UINT nSendMsg = 0 ;
        if (::GetExtMenuItem(m_TestModule, (UINT)wParam, &nSendMsg) != FALSE)
        {
            CWnd * pWnd = GetDlgItem( CHILD_WINDOW_ID ) ;
            if ( pWnd != NULL && pWnd->GetSafeHwnd() != NULL )
            {
                // if ::GetExtMenuItem returns TRUE and we have the child 
                // window then send the message to the child
                return (BOOL)pWnd->SendMessage( nSendMsg, 0, 0 ) ;
            }
        }
        return CFrameWnd::OnCommand(wParam, lParam);
    }
  3. 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 )
        {
            // if nID translates to our internal message
            // then enable the menu item
            // otherwise, let OnCmdMsg() handle nID.
            UINT nPostItem = 0 ;
            // does the plugin own this menu item?
            if ( ::GetExtMenuItem( m_TestModule, nID, &nPostItem ) != FALSE )
            {
                return TRUE ; // if yes, then enable it by returning TRUE
            }
        }
        // otherwise, let the CFrameWnd handle it 
        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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Bergman
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
GeneralPlease traslate it into VC++6.0 PinmemberAndrewpeter7-Feb-11 2:27 
GeneralRe: Please traslate it into VC++6.0 [modified] PinmemberMichael Bergman19-Feb-11 23:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140905.1 | Last Updated 14 Sep 2005
Article Copyright 2005 by Michael Bergman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid