Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to add dynamic menus to your application using DLLs

0.00/5 (No votes)
25 Nov 2005 2  
How to add dynamic menus to your application using DLLs, if you want to support add-ons to your base application.

Introduction

You can add dynamic menus to your application using DLLs. This is helpful if you want to support add-ons to your base application. These add-ons may be provided by you or any third party (of course, not free of charge!). This is an old technique but I still find it useful. (For the sake of this article, I assume that the readers are familiar with writing DLLs.)

Using the code

The idea is to specify a directory in which your application looks for the add-on DLLs. The application then uses the LoadLibrary function to load DLLs. The sample application provided with this article looks in the current directory. It uses CFileFind to look for any DLL in the current directory. Once it finds a DLL, it checks whether the DLL has the �specific format� of add-on DLLs. The format of add-on DLLs should be such that they provide a set of predefined functions (which are known to the application and the application uses the GetProcAddress API to get the address of these functions). I have defined the following functions for my program:

  • bool MenuTitle(CString& str);
  • int NumberOfMenus(void);
  • bool MenuText(int n, CString& str);
  • bool MenuFunction(int n);

While defining the functions in the DLL, please note that the functions should be properly exported. Specifying DllExport alone is not enough. The DllExport attribute tells the linker to generate an export table entry for the specified function. This export entry is decorated. This is necessary to support DllExport-ing of overloaded functions. But it also means that the string you pass to GetProcAddress needs to be decorated. The decoration scheme varies from architecture to architecture and from calling convention to calling convention. So, for example, the function �NumberOfMenus� is exported from a DLL which is compiled with VC++6 , its decorated name is "?NumberOfMenus@@YAHXZ", so you have to do GerProcAddress(hinst, ?NumberOfMenus@@YAHXZ) instead of GerProcAddress(hinst, NumberOfMenus).

To get around with this problem, the function must be exported by its undecorated name. The easiest way to do this is to define the name of the functions in the �DEF� file of the DLL. (The DEF file of the sample DLL is shown below: TestDll.def: it declares the module parameters for the DLL.)

LIBRARY      "TestDll"
DESCRIPTION  'TestDll Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can go here
    NumberOfMenus
    MenuText
    MenuFunction
    MenuTitle

OK, so the function definition is like this:

_declspec(dllexport) int  NumberOfMenus(void);
_declspec(dllexport) bool MenuText(int  n,  CString& str);
_declspec(dllexport) bool MenuFunction(int n);
_declspec(dllexport) bool MenuTitle(CString& str);

But when I run the program, it generates a Windows error box saying the following:

Debug Error!

Program:\Debug\Test.exe
Module:
File: i386\chkesp.c
Line: 149

The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

(press Retry to debug the application)

[Abort][Retry]

When I looked for help in MSDN, I found that the above condition applies when one of the following conditions are true:

  1. You call a function that does not use the __cdecl modifier.
  2. The function contains parameters that are larger than 64 kilobytes (KB).
  3. You compile your application in Debug mode and with the /RTCs compiler option enabled.

Therefore, change the function declaration to:

_declspec(dllexport) int  __cdecl NumberOfMenus(void);
_declspec(dllexport) bool __cdecl MenuText(int  n,  CString& str);
_declspec(dllexport) bool __cdecl MenuFunction(int n);
_declspec(dllexport) bool __cdecl MenuTitle(CString& str);

Before I proceed any further, let me explain the purpose of each of the above functions.

The function MenuTitle returns the title of the popup menu provided by this DLL. The application adds a popup-menu with this name. The function NumberOfMenus defines the number of menu items provided by this add-on and MenuText gives the text string for each menu item. The function MenuFunction provides the handler for each sub-menu (provided by this add-on DLL).

typedef int (__cdecl  *NUMMENUPTR)(void);
typedef bool (__cdecl  *MENUTEXTPTR)(int ,CString&);
typedef bool (__cdecl *MENUTITLEPTR)(CString&);
typedef bool (__cdecl *MENUFUNCTIONPTR)(int);
struct DllStructure
{
    HINSTANCE m_hInst;
    UINT m_uStartID;
    int m_nMenu;
    MENUFUNCTIONPTR m_pFn;
};

For the sake of simplicity, the application class is an SDI application, and all the menus are added to the main frame class. The mainframe class maintains an array of the above structure (one per add-on DLL).

void CMainFrame::AddonMenus()
{
    CFileFind ff;
    if (!ff.FindFile("*.dll")) return;
    bool bNext;
    NUMMENUPTR pfn;
    MENUTEXTPTR pfn2;
    MENUTITLEPTR pfn3;
    MENUFUNCTIONPTR pfn4;
    CString strMenu;
    int nCommand=ID_COMMAND_START;
    do 
    {
        bNext=(bool)    ff.FindNextFile();
        CString strFileName=ff.GetFileName( );
        HINSTANCE hInst=LoadLibrary(strFileName);
        if (!hInst) continue;
        pfn=(NUMMENUPTR) GetProcAddress(hInst,TEXT("NumberOfMenus"));
        if (!pfn) continue;
        pfn2=(MENUTEXTPTR) GetProcAddress(hInst,TEXT("MenuText"));
        if (!pfn2) continue;
        pfn3=(MENUTITLEPTR) GetProcAddress(hInst,TEXT("MenuTitle"));
        if (!pfn3) continue;
        pfn4=(MENUFUNCTIONPTR) GetProcAddress(hInst,TEXT("MenuFunction"));
        if (!pfn4) continue;

        int nMenus=(*pfn)();
        CString strMainMenu;
        if (!(*pfn3)(strMainMenu)) continue;
        CMenu *pDllMenu= new CMenu;
        pDllMenu->CreateMenu();
        for(int i=0;i<nMenus;i++)
        {    
            if ((*pfn2)(i,    strMenu))
            {
                pDllMenu->AppendMenu(MF_STRING,nCommand+i,strMenu);
            }
        }
        AddPopUp(strMainMenu,pDllMenu);
        DllStructure DllSt;
        DllSt.m_hInst=hInst;
        DllSt.m_nMenu=nMenus;
        DllSt.m_uStartID=nCommand;
        DllSt.m_pFn=pfn4;
        m_Dlls.Add(DllSt);
        nCommand+=nMenus;
    }while (bNext);
}

BOOL CMainFrame::AddPopUp(CString str,CMenu* pMenu)
{
    CMenu *pMainMenu=GetMenu();
    if (!pMainMenu || !pMenu) return NULL;
    return pMainMenu->AppendMenu(MF_STRING|MF_POPUP  ,(int)pMenu->m_hMenu,str);
}

The function AddonMenus() is called from CMainFrame::OnCreate().

The application program assigns 100 consecutive command IDs for these add-on DLLs. This is done by defining ID_COMMAND_START and ID_COMMAND_END in the resource.h and changing the _APS_NEXT_COMMAND_VALUE.

#define ID_COMMAND_START            32771
#define ID_COMMAND_END                32870
#define _APS_NEXT_COMMAND_VALUE        32871

The main assumption here is that the total number of menus in the add-on DLLs does not exceed 100 (a crude assumption indeed!!).

Finally ON_COMMAND_RANGE(ID_COMMAND_START,ID_COMMAND_END,DllFunctions) is used to call the menu handlers (from the DLLs).

The menu handler is defined as follows:

void CMainFrame::DllFunctions(UINT nID)
{
    for(int i=0;i<m_Dlls.GetSize();i++)
    {
        if (nID >= m_Dlls[i].m_uStartID && nID 
            <m_Dlls[i].m_uStartID+m_Dlls[i].m_nMenu)
        {
            m_Dlls[i].m_pFn(nID-m_Dlls[i].m_uStartID);
        }
    }
}

Finally use "FreeLibrary" to unmap your DLLs from memory when the application terminates.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here