LoadMenuIndirect

MFCやVCLなどのクラスを一切使わずにWin32アプリケーションを作ったことのある人なら リソースファイル(*.rc)を作ってメニューバーやダイアログなどを作成しLoadMenuなどで呼び出す、といったことをやったことがあると思います。 ヘルプでロードする関数を調べているとLoadMenuIndirectやCreateDialodIndirectというように、 末尾に「Indirect」がついた関数が目に付いたことはないでしょうか。 これらの関数はメニューバーやダイアログの構造を示すバイナリデータへのポインタを渡すとそれらを生成してハンドルを返すというものです。 LoadMenuやCreateDialodではexeファイルに組み込まれたリソースの中からお望みのバイナリデータを探してくれますが、 〜Indirectはリソースとして定義したものに限らず任意のバイナリデータを指定できる点が異なります。

違った見方をすればLoadMenuはLoadMenuIndirectを使っている、とも言えます。 実際、

HMENU hmenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
と書くところを
HRSRC hrsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_MENU1), MAKEINTRESOURCE(RT_MENU));
void *pointer = LoadResource(NULL, hrsrc);
HMENU hmenu = LoadMenuIndirect((MENUTEMPLATE*)pointer);
と書くことで同じことができます。

では、LoadMenuIndirectに渡しているバイナリデータとはどんなものなのでしょう。 LoadMenuIndirectのヘルプを調べてみるとMENUTEMPLATEは、 メニューテンプレートの場合はMENUITEMTEMPLATEHEADER構造体とそれに続く一つ以上のMENUITEMTEMPLATE構造体から、 拡張メニューテンプレートの場合はMENUEX_TEMPLATE_HEADER構造体とそれに続く一つ以上のMENUEX_TEMPLATE_ITEM構造体から構成される、と書いてあります。 今回私が調べたのは拡張ではない方のメニューテンプレートです。

MENUITEMTEMPLATEHEADER構造体の宣言は以下のとおりです。

typedef struct {          // mith 
    WORD versionNumber;   // version number; must be zero 
    WORD offset;          // offset first MENUITEMTEMPLATE structure 
} MENUITEMTEMPLATEHEADER; 
versionNumberには常にゼロを代入します。
offsetはMENUITEMTEMPLATE構造体までのオフセット(何バイト先にあるか)を代入します。

MENUITEMTEMPLATE構造体は

typedef struct {        
    WORD mtOption;       // menu item flags 
    WORD mtID;           // menu item identifier 
    WCHAR mtString[1];   // null-terminated string for menu item 
} MENUITEMTEMPLATE; 
となっています。
mtOptionはMF_で始まる値を入力しますが、基本的に以下のものの組み合わせでいいと思います。
MF_SEPARATOR セパレーター
MF_GRAYED 灰色表示
MF_DISABLED 使用不可
MF_CHECKED チェック付き
MF_USECHECKBITMAPS チェック時に専用のビットマップを使う(?)
MF_BITMAP ビットマップを使う(?)
MF_OWNERDRAW オーナー描画(?)
MF_POPUP ポップアップ
MF_MENUBARBREAK バーでブレイク
MF_MENUBREAK 列でブレイク
MF_HILITE ポップアップあるいはメニューの最後の項目
?が付いているものに関しては使い方がよくわかりません。これらのフラグで特に気を付けないといけないのは MF_POPUPとMF_HILITEの使い方です。MF_POPUPを持つメニューアイテムには後に続くmtIDを記述してはいけません。 「記述してはいけない」というのは「どんな値が入っていても無視される」という意味ではなくて 「mtOptionの次にあるのはmtStringである」ということです。 MF_HILITEの項目がひとつも無いとバイナリデータの終端が検出できなくなりプログラムがバグります。
mtIDはそのメニューアイテムがクリックされたときにWM_COMMANDメッセージと共に送られる値で、WPARAMのLOWORDに格納されます。
mtStringはNULL終端のUnicode文字列でメニューバーに表示されます。日本語を表示する場合SJISコードではダメなので注意してください。

サンプルは大体こんな感じです。バイナリデータはWORDの配列menu_templateで実装しています。

#include <windows.h>

#define IDM_OPEN    0x0101
#define IDM_SAVE    0x0102
#define IDM_QUIT    0x0103
#define IDM_FSCREEN 0x0201

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDM_OPEN:
                    MessageBox(hwnd, "Open", "Test", MB_OK);
                    break;
                case IDM_SAVE:
                    MessageBox(hwnd, "Save", "Test", MB_OK);
                    break;
                case IDM_QUIT:
                    MessageBox(hwnd, "終了", "Test", MB_OK);
                    break;
                case IDM_FSCREEN:
                    MessageBox(hwnd, "全画面表示", "Test", MB_OK);
                    break;
            }
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
    HWND     hwnd;
    HMENU    hmenu;
    WNDCLASS wc;
    MSG      msg;

    // メニュー
    const WORD menu_template[] =
    {
        //// MENUITEMTEMPLATEHEADER ////
        0, // versionNumber   version number; must be zero 
        0, // offset          offset first MENUITEMTEMPLATE structure 

        //// MENUITEMTEMPLATE ////
        MF_POPUP, L'フ', L'ァ', L'イ', L'ル', L'(', L'&', L'F', L')', 0,           // ファイル
            0           , IDM_OPEN   ,   L'&', L'O', L'p', L'e', L'n', 0,               // Open
            0           , IDM_SAVE   ,   L'&', L'S', L'a', L'v', L'e', 0,               // Save
            MF_SEPARATOR,        0   ,   0,                                             // セパレータ
            MF_HILITE   , IDM_QUIT   ,   L'終', L'了', L'(', L'&', L'Q', L')', 0,       // 終了
        MF_POPUP | MF_HILITE, L'表', L'示', L'(', L'&', L'V', L')', 0,             // 表示
            MF_HILITE   , IDM_FSCREEN,   L'全', L'画', L'面', L'表', L'示', 0,         // 全画面表示
    };
    hmenu = LoadMenuIndirect(menu_template);

    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = WndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = hThisInstance;
    wc.hIcon            = LoadIcon(NULL, IDI_WINLOGO );
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "Sample";
    RegisterClass (&wc);
    hwnd = CreateWindow
            (wc.lpszClassName,
             "サンプル - LoadMenuIndirect",
             WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME,
             CW_USEDEFAULT, CW_USEDEFAULT,
             320, 200,
             NULL,
             hmenu,
             hThisInstance,
             NULL
             );
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while( GetMessage(&msg, NULL, 0, 0) > 0 )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}