ショートカット

 FAQの中でもトップクラスと言って間違いない「ショートカット」。ですが、それなりにちゃんとしたアンサーがないような気がするのは筆者だけでしょうか? そんな感じでいってみましょう。

ショートカットに使うインターフェイス
 ショートカットの操作に必要なのはIShellLinkインターフェイスと、IPersistFileインターフェイスです。ひとつだけじゃダメなのがみそです。ちなみに、これまで通りどっちもポインタとして扱いますが、LPなんたらという形にどちらもなってないんで注意。
 まぁ、そんな感じで次のようにメンバ変数として持っておいてください。


	IShellLink	*m_pShellLink;		//IShellLinkへのポインタ。
	IPersistFile	*m_pPersistFile;	//IPersistFileへのポインタ。
	

 あと、とーぜん、アイテムIDリストとか使うのであればこれまでのテクニックを総動員するわけなので、その辺もね。

初期化しましょう
 ショートカットを操作するためには、これまで以上に念入りな準備が必要です。次のような処理を、ショートカットの操作を行う前に(できればコンストラクタとかで)行ってください。


BOOL CT_Filer2View::LinkInit()
{
	HRESULT hRes;	//各結果。

	// メンバ変数を初期化します。
	m_pPersistFile = NULL;
	m_pShellLink = NULL;

	// まず、OLEを使うために初期化しておきます。
	hRes = ::CoInitialize( NULL );

	if( hRes == E_OUTOFMEMORY )
		return FALSE;
	if( hRes == E_INVALIDARG )
		return FALSE;
	if( hRes == E_UNEXPECTED )
		return FALSE;

	// 空のインターフェイスを用意します。
	hRes = ::CoCreateInstance( CLSID_ShellLink, NULL,
				CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&m_pShellLink );

	if( hRes == CLASS_E_NOAGGREGATION )
		return FALSE;
	if( hRes == REGDB_E_CLASSNOTREG )
		return FALSE;

	
	// IPersistFileへのポインタを取得します。
	hRes = m_pShellLink->QueryInterface( IID_IPersistFile, (LPVOID *)&m_pPersistFile );

	if( hRes != S_OK )
		return FALSE;

	return TRUE;
}
	

 これまでのインターフェイス関係とはちょっと違う感じでしょう。でも難しいことはありません。いや、まぁ詳しく知ろうとすれば難しいでしょう。わたしゃ知ろうとしてないので全然難しくありません、はい。

 まずCoInitialize()。これはAPIです。COM関係のAPIは、どうやらCoがプレフィックスとして付くようです。
 この関数は、そのCOM関係APIを使うための前準備として使用しなければならないようです。
 この関数はただ呼べばいいんですが、注意して欲しいのは、返ってきた値がS_FALSEの時には、基本的に問題ないのでそのまま素通りさせてください。っていうか、たいがいはこの値が返ってくると思います。

 次に呼ぶCoCreateInstance()は、空のインターフェイスを作製するものです。
 これまで、IShellFolderにしろIEnumIDListにしろ、なんらかのAPIやインターフェイスから取得してきました。これらにはすでにデータの入った、いわば「中身の詰まった」インターフェイスだったわけです。
 しかし、ショートカットの場合には少し違ってきます。これまでのインターフェイスはメモリ上の抽象オブジェクトを扱ってきましたが、インターフェイスは「ファイル」という永続オブジェクトです。ファイルの中からデータを取りだし、それを格納する領域が必要というわけです。
 そのための関数がCoCreateInstance()というわけです。引数などについては難しく考えると難しくなってしまうので、簡単にそのまま使ってしまいましょう。

 さて、実はIShellLinkインターフェイスには、ファイルを操作する機能はありません。が、実際には内包しています。ファイルの操作にはIPersistFileインターフェイスを使用します。
 その、内包されているらしいインターフェイスへのポインタを取得するのがQueryInterface()メソッドです。このメソッドはすべてのインターフェイスが持っていて、他のインターフェイスへのポインタを取得できるようになっています。ただ、何でもかんでも好きなインターフェイスを取得できるというわけでもないようです。まー要するによく分からないってことです、はい。
 でもとりあえず、IShellLink::QueryInterface()を使うことでIPersistFileインターフェイスへのポインタを取得できるわけです。このポインタを取得することで、IShellLinkにもファイル操作の機能が付くというわけです。

 とまぁ、ここまでが下準備ですが、この辺はそのまま使って構いません。つまり、アプリケーションの仕様とかでこの辺を書き換える必要とかはないということですね。

後処理もしましょう
 インターフェイス関係にはつきものの後処理です。デストラクタとかでね。


BOOL CT_Filer2View::UnInit()
{
	// IPersistFileへのポインタを破棄します。
	if( m_pPersistFile != NULL )
		m_pPersistFile->Release();

	// IShellLinkへのポインタを破棄します。
	if( m_pShellLink != NULL )
		m_pShellLink->Release();

	::CoUninitialize();

	return TRUE;
}
	

 Release()メソッドはこれまでにも何度か出てきているので説明の必要はないでしょう。CoUninitialize()CoInitialize()と対になるAPIです。これも気にせず使ってしまえばいいでしょう。

ショートカットのロード
 では、待望のショートカットの読み込みを行ってみましょう。


BOOL CT_Filer2View::Load(LPCTSTR p_pchFile)
{
	HRESULT	hRes;	//各結果。
	OLECHAR	ochLinkFile[MAX_PATH];

	// ユニコードに変換します。
	::MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, p_pchFile, -1,
                        ochLinkFile, MAX_PATH );

	// ショートカットを読み込みます。
	hRes = m_pPersistFile->Load( ochLinkFile, STGM_READ );
	if( hRes != S_OK )
		return FALSE;

	// リンクを決定します。
	hRes = m_pShellLink->Resolve( NULL, SLR_UPDATE );
	if( hRes != NOERROR )
		return FALSE;

	return TRUE;
}
	

 引数には、ショートカットのフルパス(例えばC:\Windows\Desktop\ショートカット.lnkのような形)を渡します。例によってUnicodeでないとダメなので変換します。
 変換した後のパスをIPersistFile::Load()メソッドを使用して読み込みます。でもこれだけではダメです。ここまでは単にファイルとして読み込んだだけなので、ちゃんと「ショートカット」として読み込む必要があります。
 そのためのメソッドがIShellLink::Resolve()メソッド。この時点で初めてショートカットを読み込みます。
 このメソッドのフラグを変えると、ショートカットが見つからなかった場合に探させたり訊ねさせたりすることができます。実際に試してみるといいでしょう。リンク先のファイル名を変更して、スタートメニューから実行してみると「ショートカットの検索」っていうダイアログが出て、自動的に検索してくれます。
 と言っても、この検索は単にサブフォルダ内を検索していくだけなので、それほどすごいもんでもないと思います。ちなみにこの機能を使うときには、第1引数に親ウィンドウのウィンドウハンドルを指定してください(例えばビューウィンドウとか)。

データの取得
 ロードした後、ショートカットからデータを読み込みます。それにはIShellLink::Getなんたらというメソッドを使用すればOK。ただ、いくつかクセがあるのでその辺を注意するべきでしょう。

 指し示すファイルの取得はIShellLink::GetPath()を使用します。ただし、これはフルパスの文字列なので、マイコンピュータとかは取得できません。こういったシェル関係のファイルを取得する場合にはIShellLink::GetIDList()を使用します。こちらならアイテムIDリストとしてファイルを取得できます。

 かなり謎なのがIShellLink::GetDescription()で、普通のショートカットからは、このメソッドで文字列を取得することができません。ただ、後述のIShellLink::SetDescription()で文字列をショートカットにセットして作製すると、その文字列を取得することができます。要するに内部データとしての文字列ということでしょう。でもなんに使うんでしょうね。
 しかも奇妙なことに、コントロールパネル系のショートカットは、内部的に文字列を持っています。そのコントロールパネルの情報が入っていて、例えば「システム」であれば、「システムの基本情報を表示し、詳細設定を変更します。」なんてことが書かれています。でもこの文字列はIShellLink::GetDescription()では取得できません。謎です。

 アイコンの取得はIShellLink::GetIconLocation()で行えますが、中にはこれで取得できないアイコンもあるので、単純にアイコンを取得したい場合にはSHGetFileInfo()を使っちゃうのが一番楽でしょう。

「スタートメニュー」フォルダの取得
 ショートカットの置き場所は、基本的に「スタートメニュー」か「デスクトップ」ということになっています。どちらもウィンドウズフォルダ(たいがいはC:\Windows)の中のフォルダだから、普通のパスとして取得できます。これらの特殊フォルダの取得方法は、ふたつあります。

 ひとつは、SHGetSpecialFolderLocation()を使用するもの。実際には次のような感じ。


	char		chPath[MAX_PATH];
	LPITEMIDLIST	pidl;

	// デスクトップフォルダ。
	::SHGetSpecialFolderLocation( GetSafeHwnd(), CSIDL_DESKTOPDIRECTORY, &pidl );
	::SHGetPathFromIDList( pidl, chPath );
	TRACE( "CSIDL_DESKTOPDIRECTORY %s\n", (LPCTSTR)chPath );

	// スタートメニューフォルダ。
	::SHGetSpecialFolderLocation( GetSafeHwnd(), CSIDL_STARTMENU, &pidl );
	::SHGetPathFromIDList( pidl, chPath );
	TRACE( "CSIDL_STARTMENU %s\n", (LPCTSTR)chPath );
	

 もうひとつはレジストリから取得する方法。HKEY_CURRENT_USER\Software\Microsoft\Windows
\CurrentVersion\Explorer\Shell Folders
に特殊フォルダが登録されているのでそちらから取得してもいいでしょう。
 どちらにしろ、特殊フォルダのパス名はユーザーの設定によって違う可能性があるということだけは憶えておきましょう。以上のようなコードを使用せずに「決め打ち」することだけは絶対に避けましょう

 あと、インストーラを作製する場合にはSHBrowseForFolder()を使用するといいでしょう(これの解説は「フォルダを選択するダイアログ(前編)(後編)」を見てね)。この場合、ルートフォルダ(BROWSEINFO構造体pidlRootメンバの値)に上で取得した「スタートメニュー」のフォルダを設定するといい感じかも。

データのセット
 さて、ここからは「ショートカットの作製」に入ります。コード自体は「後処理」まで書いてあればOK。もちろん「ショートカットの設定を変更する」のであれば、ここまでのコードすべてが必要になるでしょう。

 データのセットにはIShellLink::Setなんたらというメソッドを使用します。このメソッドは「データの取得」で説明したのと対になって存在しているので、どちらかが使えれば大丈夫でしょう。
 ただ、たったひとつIShellLink::SetRelativePath()だけは謎のメソッドとして君臨します。なんに使うのか分からないし、筆者の持ってるリファレンスと引数が違うし(最後の引数はDWORDです)。なんなんでしょ。

 さて、新規にショートカットを作製する場合には、どのようにデータを設定すればいいか分からないかもしれません。ここで、一例を示しておきます。


	m_pShellLink->SetIDList( pidl );	//リンク先のアイテムID。
	m_pShellLink->SetShowCmd( SW_SHOW );	//標準の表示。
	m_pShellLink->SetWorkingDirectory( "" );	//カレントフォルダはナシ。
	m_pShellLink->SetIconLocation( NULL, 0 );	//これで、フツーのアイコンが表示されます。
	m_pShellLink->SetHotkey( 0 );			//ホットキーナシ。
	m_pShellLink->SetDescription( "" );	//タイトルナシ。
	

 軒並み必要そうなのを羅列していますが、実際には必要ないかもしれません。
 注意としてまずリンク先ですが、上の例ではファイルをアイテムIDリストでセットしています。パスを文字列としてセットするにはIShellLink::SetPath()を使ってください。
 アイコンの設定は、上のように省略すると、うまくリンク先のアイコンが指定されるので便利です。
 こんなところでしょう。結構適当にセットしても大丈夫みたいです。

ショートカットのセーブ
 データをセットしたら、ショートカットを保存しましょう。といってもほとんどロードと同じです。


BOOL CT_Filer2View::Save( LPCTSTR p_pchFile )
{
	HRESULT	hRes;	//各結果。
	OLECHAR	ochLinkFile[MAX_PATH];

	// ユニコードに変換します。
	::MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, p_pchFile, -1,
                        ochLinkFile, MAX_PATH );

	// ショートカットを書き込みます。
	hRes = m_pPersistFile->Save( ochLinkFile, TRUE );

	if( hRes != S_OK )
		return FALSE;

	return TRUE;
}
	

 違いとしてはIShellLink::Resolve()の必要がないくらいでしょうか。あとは、セーブするパスはちゃんと.lnkの拡張子を付けておきましょう。

以上で終わり!
 いかがだったでしょう。筆者の知る限り、今回のようにC++中心で解説されたものは少ないと思います。MFCで利用するにはいいんじゃないでしょうか。
 ショートカットで難しいのは「初期化」の部分です。が、これはもうそのまま使ってしまってください。少なくとも「ショートカット」においては、この部分について理解する必要はありません。こういう「理解する必要のある部分」と「必要のない部分」の見切りのつけ方なんかも、プログラミング技術のひとつなのかなとか思います。

 さて、次回で「COMインターフェイス・ファイラーを作ろう!」講座は最終回を迎えます。まとめ的なちょこちょことした部分を書いて、この講座を締めくくろうと思ってます。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.