« RTTIのプリプロセッサでの判別。 | トップページ | BCB6で共有RTLライブラリをDLLで有効にするとExe側のFreeLibraryで落ちる »

2005年8月19日 (金)

ファイルを開く

エクスプローラーでダブルクリックしたのと同じようにファイルを実行したいとき、
プログラムを実行するための関数を使うわけですが、いくつか種類があります。

ざっとCreateProcess(), WinExec(), ShellExecute(), ShellExecuteEx()といったところでしょうか。

もちろん上記の関数群で十分な場合も多いですが、「エクスプローラーでファイルをダブルクリックしたときと同じ」動きをするわけではありません。
CreateProcessは文字通りプロセスを生成するものでファイル名だけからでは起動させづらいでしょう。
WinExecやShellExecute、特にShellExecuteExはよく使われます。実際ShellExecuteExでほとんど事足りるでしょう。
ただ、特定の状況下で困ったりします。
たとえばショートカットファイルのプロパティで、ショートカットタブの詳細設定ボタンから開かれた詳細プロパティダイアログで別の視覚情報で実行するを設定した場合、これをShellExecuteExで実行しても普通に実行されてしまいます。
Explorerではきちんと「別のユーザーで実行」ダイアログが開きます。

では上記を解決するにはどうしたらいいか。
要するにファイルを右クリックしたときのデフォルト項目を実行すればいいわけです。
この項目を実行するにはIContextMenu::InvokeCommandを使います。
そしてIContextMenuはIShellFolder::GetUIObjectOfから取得します。

と、書くのは簡単ですが、やるのはとっても大変でした。
以下のようになります。

////////////////////////////////////////////////////////////////////////////////

//stl
#include <string>
//windows
#include <windows.h>
#include <Shlobj.h>
#pragma comment(lib, "Ole32.lib")

////////////////////////////////////////////////////////////////////////////////

#if !defined( TBENFORCEH )
	#define TB_ENFORCE( enforcer, statement )	statement
	#pragma warning( disable :4552 )	//warning C4552: '!' : 演算子にプログラム上の作用がありません。作用を持つ演算子を使用してください
#endif	//#if !defined( TBENFORCEH )

////////////////////////////////////////////////////////////////////////////////

class	System
{
public:
	virtual	~System() throw(){}
	static void	Run( const std::string& path );
};

////////////////////////////////////////////////////////////////////////////////

void	System::Run( const std::string& path )
{
	//COMを初期化
	TB_ENFORCE( NotEqualFail( S_OK ), CoInitialize( NULL ) );

	//デスクトップフォルダを取得
	IShellFolder* pDesktopFolder;
	TB_ENFORCE( NotEqualFail( S_OK ), ::SHGetDesktopFolder( &pDesktopFolder ) );

	//引数のパスへのフルパスを含むITEMIDLISTを作成
	LPITEMIDLIST pFullPathIDList;
	{
		//まず引数をユニコードに変換
		OLECHAR	convertedPath[MAX_PATH*2+1];
		TB_ENFORCE( API(), ::MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path.c_str(), -1, convertedPath, MAX_PATH*2 ) );

		//ITEMIDLISTを取得
		ULONG	chEaten, dwAttributes;
		TB_ENFORCE( NotEqualFail( S_OK ), pDesktopFolder->ParseDisplayName( NULL, NULL, convertedPath, &chEaten, &pFullPathIDList, &dwAttributes ) );
	}

	//引数のパスの親フォルダのIShellFolderを取得。
	IShellFolder*	pCurrent = pDesktopFolder;
	{
		//IMallocを取得しておく。
		IMalloc*	pMalloc;
		::SHGetMalloc( &pMalloc );
		//IShellFolderをトラバース
		do
		{
			//フルパスを示すITEMIDLISTから今見ているフォルダのITEMIDLISTをコピー
			LPITEMIDLIST pCurrentIDList;
			{
				//コピー元のアイテムのサイズを取得
				int	srcSize = pFullPathIDList->mkid.cb;
				//コピー先のサイズはUSHORTのサイズを足したもの
				int	dstSize = srcSize + sizeof(USHORT);
				//新しいアイテム用メモリを確保
				pCurrentIDList = reinterpret_cast< LPITEMIDLIST >( TB_ENFORCE( NullFail(), pMalloc->Alloc( dstSize ) ) );
				//内容をコピー
				memcpy( pCurrentIDList, pFullPathIDList, srcSize );
				//余りは0で埋める。
				memset( reinterpret_cast<LPBYTE>(pCurrentIDList) + srcSize, 0, dstSize - srcSize );
			}
			//今見てるフォルダのITEMIDLISTからIShellFolderを取得
			IShellFolder*	pTemp;
			TB_ENFORCE( NotEqualFail( S_OK ), pCurrent->BindToObject( pCurrentIDList, NULL, IID_IShellFolder, reinterpret_cast< void**>( &pTemp ) ) );
			//取得できたので前のIShellFolderは解放
			pCurrent->Release();
			//pCurrentを置き換える。
			pCurrent = pTemp;
			//ついでにITEMIDLISTもいらないので解放
			pMalloc->Free( pCurrentIDList );
			//ItemIDListを次に進める。
			pFullPathIDList = reinterpret_cast<LPITEMIDLIST>( ( reinterpret_cast<LPBYTE>( pFullPathIDList ) ) + pFullPathIDList->mkid.cb	);
		}
		while( reinterpret_cast<LPITEMIDLIST>( ( reinterpret_cast<LPBYTE>( pFullPathIDList ) ) + pFullPathIDList->mkid.cb  )->mkid.cb != 0 );
		//↑pFullPathIDListの次がsize==0ならループを終わる。

		//IMallocを解放
		pMalloc->Release();
	}
	//pCurrentがpathの指すファイルの親フォルダ。
	//pFullPathIDListは今ファイル名を指す。

	//以上の情報からそのファイルの右クリックメニューを取得して既定のコマンドを実行

	//まずIContextMenuを取得。
	IContextMenu*	pContextMenu;
	TB_ENFORCE( NotEqualFail( S_OK ), pCurrent->GetUIObjectOf( NULL, 1, ( LPCITEMIDLIST* )(&pFullPathIDList), IID_IContextMenu, 0, reinterpret_cast< void ** >( &pContextMenu ) ) );

	{//コマンドを実行
		//IContextMenu::InvokeCommandの引数用構造体を用意
		CMINVOKECOMMANDINFO	invokeInfo = { sizeof( invokeInfo ) };
		//lpVerbメンバにメニューの識別子を設定
		HMENU	hMenu = TB_ENFORCE( API< HMENU >( NULL ), ::CreatePopupMenu() );
		TB_ENFORCE( TrueFail(), FAILED( pContextMenu->QueryContextMenu( hMenu, 0, 1, 0xffff, CMF_EXPLORE ) ) );
		invokeInfo.lpVerb = MAKEINTRESOURCE( TB_ENFORCE( EqualFail( -1 ), GetMenuItemID( hMenu, TB_ENFORCE( API(-1), GetMenuDefaultItem( hMenu, TRUE, NULL ) ) ) )-1 );
		invokeInfo.nShow = SW_SHOWNORMAL;
		//実行
		HRESULT	result = pContextMenu->InvokeCommand( &invokeInfo );
		//「0x800704c7 この操作はユーザーによって取り消されました。」の場合はエラーとしない。
		if( 0x800704c7 != result && S_OK != result )
			TB_ENFORCE( FalseFail(), !"pContextMenu->InvokeCommand( &invokeInfo )" );
	}
	//COMを解放
	CoUninitialize();
}

////////////////////////////////////////////////////////////////////////////////

|

« RTTIのプリプロセッサでの判別。 | トップページ | BCB6で共有RTLライブラリをDLLで有効にするとExe側のFreeLibraryで落ちる »

コメント

この記事へのコメントは終了しました。

トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/61222/972662

この記事へのトラックバック一覧です: ファイルを開く:

« RTTIのプリプロセッサでの判別。 | トップページ | BCB6で共有RTLライブラリをDLLで有効にするとExe側のFreeLibraryで落ちる »