フォルダ選択ダイアログボックスの初期フォルダ指定

今回は、 COM 操作のところでも記述されているフォルダ選択ダイアログボックスについてです。このフォルダ選択ダイアログボックスは、コールバック関数を準備することで、初期状態で選択されているフォルダを指定できるようになるのであります。アプリケーション側で BrowseCallbackProc コールバック関数を準備し、 SHBrowseForFolder 関数呼び出し時に、 BROWSEINFO 構造体にこのコールバック関数のアドレスを指定します。

int CALLBACK BrowseCallbackProc(
    HWND   hwnd,      // ダイアログボックスのハンドル
    UINT   uMsg,      // イベントを識別する値
    LPARAM lParam,    // uMsg に依存する値
    LPARAM lpData     // アプリケーション定義値
);

uMsg パラメータに BFFM_INITIALIZED が渡されたときには、フォルダ選択ダイアログボックスが初期化されたことを示すので、このときにダイアログボックスにメッセージを送信して、選択されているフォルダを変更するようにします。

lpData パラメータには、 BROWSEINFO 構造体の lParam メンバに指定された値が渡されるので、必要な情報を格納した構造体を作成して、そのアドレスをこのパラメータに渡してしまいましょう。

ダイアログボックスで選択されているフォルダを変更するには、 SendMessage 関数を使用してダイアログボックスに BFFM_SETSELECTION メッセージを送信します。このとき、 SendMessage 関数の wParam パラメータには TRUE を、 lParam パラメータにはパス名を表す文字列のアドレスを指定します。

さて、 HSP 側からどの情報を渡せばいいのかを考えて見ましょう。必要となる情報は、言うまでもなく、初期フォルダのパス名です。これは、 HSP 側の変数にパス名を格納して、そのアドレスを指定することにします。

実は、もう1つ必要となる情報があります。それは、 SendMessage 関数(より正確には SendMessageA 関数)のアドレスです。コールバック関数側では、 API 関数アドレスの情報を取得することができないため、必要となる関数アドレスの情報は HSP 側から送ってやる必要があるのです。

ということで、以下のような構造体を考えて、そのアドレスを BROWSEINFO 構造体の lParam メンバに指定することにしましょう。

typedef struct {
    LPSTR    pszPath;          // パス名
    FARPROC  pfnSendMessage;   // SendMessageA 関数アドレス
} HSP_BROWSE_DATA;

FARPROC 型とは、 API 関数アドレスを表すデータ型として定義されているものです。 FARPROC 型では、引数の型については何も定義されていませんが、戻り値の型は int 型と定義されています。ですから、戻り値を使用する場合に、本来の関数の型が int 型以外の場合にはキャストする必要があります。今回は戻り値を使用しないので、問題ありませんが。


実際のソースコードは以下のようになります。

ソースファイル brfolder.c

#include <windows.h>
#include <shlobj.h>

// HSPからの情報を渡すための構造体
typedef struct {
    LPCSTR   pszPath;          // パス名
    FARPROC  pfnSendMessage;   // SendMessageA 関数アドレス
} HSP_BROWSE_DATA;

int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
    // lpData は HSP_BROWSE_DATA 構造体アドレス
    HSP_BROWSE_DATA *pData = (HSP_BROWSE_DATA *)lpData;

    if (uMsg == BFFM_INITIALIZED) {
        pData->pfnSendMessage(hwnd, BFFM_SETSELECTION, TRUE, pData->pszPath);
    }
    return 0;
}

コンパイル・マシン語抽出を行ないます。前回使用したバッチファイルをそのまま使用することにしましょう。

C:\WINDOWS>d:

D:\>cd hspmcn

D:\hspmcn>mcnbcc brfolder

D:\hspmcn>bcc32 -c -O1 brfolder.c
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
brfolder.c:
警告 W8064 brfolder.c 16: プロトタイプ宣言のない関数の呼び出し(関数 BrowseCallbackProc )
警告 W8057 brfolder.c 19: パラメータ 'lParam' は一度も使用されない(関数 BrowseCallbackProc )

D:\hspmcn>tdump -oxCOMENT brfolder.obj brfolder.txt
Turbo Dump  Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation

D:\hspmcn>strat brfolder.txt
D:\hspmcn>

今回も2つほど警告が出ています。「プロトタイプ宣言のない関数の呼び出し」の警告は、関数の型として FARPROC 型を使用しているために発生します。この型では、引数の型の定義がされませんので。もう1つの警告は前回と同じですね。

これによって作成されたファイル brfolder.txt の内容は以下のようになっています。

(…………………… 省   略 ……………………)
000F55 PUBDEF  'BrowseCallbackProc'    Segment: _TEXT:0000
000F9B LEDATA  Segment: _TEXT          Offset: 0000  Length: 0023
    0000: 55 8B EC 83 7D 0C 01 8B  45 14 75 0F FF 30 6A 01   U駆マ..畿.u..0j.
    0010: 68 66 04 00 00 FF 75 08  FF 50 04 33 C0 5D C2 10   hf....u..P.3タ]ツ.
    0020: 00                                                 .
000FC5 MODE32

上のマシン語コードの部分を取り出して前回のバイナリ変換プログラムにかけると、以下のスクリプトが出力されます。

xdim fncode, 9
fncode.0 = $83ec8b55, $8b010c7d, $0f751445, $016a30ff, $00046668
fncode.5 = $0875ff00, $330450ff, $10c25dc0, $00000000

以上から、全体のスクリプトは次のようになります。今回はダイアログボックス表示部分をモジュール命令にしてみました。実行させるには lollipop モジュールも必要になります。

    #include "llmod.as"
    #include "rrmod/com/lollipop.as"
  ; #include "xdim.as"         ; Lollipop モジュールでも xdim が定義されている

    #module

    #deffunc BrowseFolder str
    mref p1, 32                ; 初期フォルダ
    mref stt, 64               ; stat(選択されれば 1)
    mref rfst, 65              ; refstr
    mref bmscr, 67             ; ウィンドウの BMSCR 構造体

    sdim inifldr, 260 : inifldr = p1
    sdim retfldr, 260

    ; IMalloc インターフェース取得
    getptr  pm, pMalloc
    dllproc "SHGetMalloc", pm, 1, D_SHELL@
    if (stat < 0)|(pMalloc == 0) : stt = 0 : return

    ; BROWSEINFO 構造体
    dim binfo, 8
    binfo.0 = bmscr.13         ; 親ウィンドウハンドル
    binfo.1 = 0                ; デスクトップフォルダをルートに
    sdim buf, 260
    getptr  binfo.2, buf
    sztitle = "フォルダを選択してください。"
    getptr  binfo.3, sztitle   ; 表示文字列
    binfo.4 = 0x0003           ; BIF_RETURNONLYFSDIRS | BIF_DONTGOBELOWDOMAIN
    if inifldr != "" {
        ; コールバック関数のマシン語コード
        xdim fncode, 9
        fncode.0 = $83ec8b55, $8b010c7d, $0f751445, $016a30ff, $00046668
        fncode.5 = $0875ff00, $330450ff, $10c25dc0, $00000000

        ; HSP_BROWSE_DATA 構造体
        getptr hbdata.0, inifldr    ; パス名のアドレス
        dll_getfunc hbdata.1, "SendMessageA", D_USER@

        getptr binfo.5, fncode      ; コールバック関数のアドレス
        getptr binfo.6, hbdata      ; HSP_BROWSE_DATA 構造体のアドレス
    }
    ; フォルダ選択ダイアログボックス表示
    ; SHBrowseForFolderA 関数呼び出し
    getptr  pm, binfo
    dllproc "SHBrowseForFolderA", pm, 1, D_SHELL@
    pidl = stat                ; 選択アイテムの PIDL
    fret = 0
    if pidl {
        ; PIDL からパス名取得
        pm.0 = pidl                   ; PIDL
        getptr  pm.1, retfldr         ; パス名を格納する変数アドレス
        dllproc "SHGetPathFromIDListA", pm, 2, D_SHELL@
        if stat : fret = 1

        ; IMalloc->Free(pidl);
        icall  pMalloc, 5, pidl, 1
    }
    ; インターフェース解放
    release  pMalloc
    stt = fret
    if fret : rfst = retfldr
    return

    #global

    ; Windows ディレクトリを初期フォルダに
    BrowseFolder windir
    if stat {
        dialog "選択されたフォルダは "+refstr
    } else {
        dialog "キャンセルされました"
    }
    end