none
SHBrowseForFolder()に関する基本的な質問 RRS feed

  • 質問

  • OS:Windows Vista
    Visual Studio 2008 Standard Edition
    言語:Visual C++
    Microsoft Visual C++ 2008 SP1 :導入済み
    Microsoft Visual C++ 2008 SP1 再頒布可能パッケージ (x86):導入済み

    よろしくお願いします。私にはComponent Object Model (COM)の知識はありません。

    SHBrowseForFolder()は、アプリ利用者が選択したフォルダー名を取得する関数です。
    インターネットでサンプルを探したところ、いろんなパターンがあり、どれを採用すれば良いのか判断できません。
    また、ヘルプの「SHBrowseForFolder Function」
    http://msdn.microsoft.com/ja-jp/library/bb762115(en-us,VS.85).aspx
    を見ても、サンプルにSHBrowseForFolder()が使われていません。

    「SHBrowseForFolder Function」の「Remarks」には次の3点が英文で書かれています。
    1.IFileDialog の使用を推奨する。
    2.Component Object Model (COM) を初期化しなければならない。
    3.CoTaskMemFreeを呼び出してIDListを解放しなければならない。

    私は、単純に、利用者が選択したフォルダー名を取得するだけの目的で使いたいです。
    この程度の目的のために上記3個全部の措置を施さねばならないとは思えません。これは素人の直感です。

    そこで質問です。
    選択したフォルダー名を取得するだけの目的の場合、上記1と2は不要ではないでしょうか?
    3を実施しなくても、メモリリークは発生しません。なので3も不要なのではないでしょうか?

    下記のとおり、「とりあえず動く」プログラムを作ってみましたが、これで良いでしょうか。
    3は不要かもしれませんが、1行を書く程度であれば労苦にならないので書いておきます。
    お墨付きのサンプルが出来上がれば幸いです。

    static int CALLBACK CBBrowse(HWND hw, UINT Mes, LPARAM lParam, LPARAM lpData){
    	wchar_t pWC[MAX_PATH];
    
    	switch(Mes){
    	case BFFM_INITIALIZED :
    		SendMessage(hw, BFFM_SETSELECTION, TRUE, lpData);
    		break;
    	case BFFM_SELCHANGED:
    		SHGetPathFromIDList((LPCITEMIDLIST)lParam, pWC);
    		SendMessage(hw, BFFM_SETSTATUSTEXT, NULL, (UINT)pWC);
    		break;
    	}
    	return 0;
    }
    
    void TORIAEZU_UGOKU_Dlg::OnFolderSelect(){
    	wchar_t Buffer[MAX_PATH];
    	LPITEMIDLIST pBrowse;
    	BROWSEINFO bi;
    
    	bi.hwndOwner = GetSafeHwnd();
    	bi.pidlRoot = NULL;
    	bi.pszDisplayName = NULL;
    	bi.lpszTitle = _T("出力先フォルダーを選択して下さい。");
    	bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_STATUSTEXT;
    	bi.lpfn = CBBrowse;
    	wcscpy_s(Buffer, g_sUserOutFolder.GetLength()+1, g_sUserOutFolder);
    	bi.lParam = (long)Buffer;
    
    	pBrowse = SHBrowseForFolder(&bi);
    	if(pBrowse!=NULL){
    		SHGetPathFromIDList(pBrowse, Buffer);
    		選択したフォルダーを使う = Buffer;
    		CoTaskMemFree(pBrowse);
    	}
    }
    
    2009年8月13日 16:46

回答

  • 「SHBrowseForFolder Function」の「Remarks」には次の3点が英文で書かれています。
    1.IFileDialog の使用を推奨する。
    2.Component Object Model (COM) を初期化しなければならない。
    3.CoTaskMemFreeを呼び出してIDListを解放しなければならない。

    私は、単純に、利用者が選択したフォルダー名を取得するだけの目的で使いたいです。
    この程度の目的のために上記3個全部の措置を施さねばならないとは思えません。これは素人の直感です。

    そこで質問です。
    選択したフォルダー名を取得するだけの目的の場合、上記1と2は不要ではないでしょうか?
    3を実施しなくても、メモリリークは発生しません。なので3も不要なのではないでしょうか?

    1.のIFileDialogについてはSHBrowseForFolder()を使う代わりに
    Vista以降の新しいファイルコモンダイアログでフォルダ選択を行おうという話です。

    2.のCOMの初期化は行わなければAPIがエラーを返すはずです。
    エラーを返さないということはどこかですでに初期化が行われているということだと思います。

    3.のCoTaskMemFree()(またはIMalloc::Free(), ILFree()など)は行わないとメモリが解放されません。
    COMが確保しているメモリはランタイムライブラリを使ってないので、
    ランタイムライブラリのメモリリークチェックではメモリリークを確認できません。

    必要だと書かれているものに対して、こうこうこういう理由で不要だと思うと言うならともかく、
    よくわからないけど不要ではないか? と思えるのはどうしてでしょう?
    2009年8月14日 0:03

すべての返信

  • 「SHBrowseForFolder Function」の「Remarks」には次の3点が英文で書かれています。
    1.IFileDialog の使用を推奨する。
    2.Component Object Model (COM) を初期化しなければならない。
    3.CoTaskMemFreeを呼び出してIDListを解放しなければならない。

    私は、単純に、利用者が選択したフォルダー名を取得するだけの目的で使いたいです。
    この程度の目的のために上記3個全部の措置を施さねばならないとは思えません。これは素人の直感です。

    そこで質問です。
    選択したフォルダー名を取得するだけの目的の場合、上記1と2は不要ではないでしょうか?
    3を実施しなくても、メモリリークは発生しません。なので3も不要なのではないでしょうか?

    1.のIFileDialogについてはSHBrowseForFolder()を使う代わりに
    Vista以降の新しいファイルコモンダイアログでフォルダ選択を行おうという話です。

    2.のCOMの初期化は行わなければAPIがエラーを返すはずです。
    エラーを返さないということはどこかですでに初期化が行われているということだと思います。

    3.のCoTaskMemFree()(またはIMalloc::Free(), ILFree()など)は行わないとメモリが解放されません。
    COMが確保しているメモリはランタイムライブラリを使ってないので、
    ランタイムライブラリのメモリリークチェックではメモリリークを確認できません。

    必要だと書かれているものに対して、こうこうこういう理由で不要だと思うと言うならともかく、
    よくわからないけど不要ではないか? と思えるのはどうしてでしょう?
    2009年8月14日 0:03
  • 返信を頂きありがとうございました。

    >.のIFileDialogについてはSHBrowseForFolder()を使う代わりに
    >Vista以降の新しいファイルコモンダイアログでフォルダ選択を行おうという話です。

     理解できました。ここまで正確に英文を読めていませんでした。

    >2.のCOMの初期化は行わなければAPIがエラーを返すはずです。
    >エラーを返さないということはどこかですでに初期化が行われているということだと思います。

     サンプルでは、SHBrowseForFolder()はエラーになりません。
     どこで初期化が行われているのかは不明です。
     もしかして、MFCを使っているから、そこで初期化が行われたのかもしれません(何の根拠もない発想です)。

    >3.のCoTaskMemFree()(またはIMalloc::Free(), ILFree()など)は行わないとメモリが解放されません。
    >COMが確保しているメモリはランタイムライブラリを使ってないので、
    >ランタイムライブラリのメモリリークチェックではメモリリークを確認できません。

     このことは知りませんでした。教えて頂きありがとうございました。

    >必要だと書かれているものに対して、こうこうこういう理由で不要だと思うと言うならともかく、
    >よくわからないけど不要ではないか? と思えるのはどうしてでしょう?

     他のサイトで見たSHBrowseForFolder()のいろんなサンプルでは、IFileDialogやCOMの初期化が載っていなかったので、無くても良いのかと思いました。実際に、無くても動きました。
     解放については、メモリリークが発生していなかったので、不要なのかな、と思いました。
     1~3の措置は無くても動くのに、何故必要だとされているのかが分らなかったのです。

    2009年8月14日 7:04
  • >2.のCOMの初期化は行わなければAPIがエラーを返すはずです。
    >エラーを返さないということはどこかですでに初期化が行われているということだと思います。

     サンプルでは、SHBrowseForFolder()はエラーになりません。
     どこで初期化が行われているのかは不明です。
     もしかして、MFCを使っているから、そこで初期化が行われたのかもしれません(何の根拠もない発想です)。

    MFCをお使いならば、CWinAppの派生クラスのInitInstance()内で、AfxOleInit()を呼び出しているのだと思います。

    この中で、COMの初期化が行われています。
    2009年8月14日 23:54
  • 貴重な情報を提供して頂き、ありがとうございます。今後とも、よろしくおねがいします。
    2009年8月15日 14:45