みなさん、こんにちは。Windows 開発統括部の古内です。立秋を過ぎてますます夏本番といった感じですが、いかがお過ごしでしょうか。
さて、本日は、Windows 7 のタスク バー機能を活用したサンプル コードをご紹介します。2011 年 2 月 23 日に Developing for Windows Blog に 投稿された 「Windows 7 Recipe – Taskbar Single Instance」 の翻訳です。かなり長い記事ですが、最後までお付き合いいただけると幸いです。
今回は、Windows 7 のタスク バー シナリオのために作成したライブラリをみなさんにご紹介します。Windows 7 のタスク バーにはたくさんの新機能があります。その 1 つがタスク バーのジャンプ リストで、アプリケーション ウィンドウに切り替えることなく、アプリケーションに関連したさまざまなタスクを実行することができます。既に、Office 2010、Internet Explorer 9 を始めとする各種ブラウザー、Messenger、Microsoft Zune などのマイクロソフト製およびその他の多くのアプリケーションが Windows 7 タスク バーに対応しています。
タスク バーのジャンプ リスト タスクとは、要するにコマンド ラインの呼び出しです。この API では、渡す必要のある任意の数の引数を付加した、コマンド ライン形式の文字列を指定できます。Windows シェルではこのコマンド ラインをそのまま実行し、ほとんどの場合は、対象のアプリケーションについて別のインスタンスを起動します。詳細については、「Windows 7 タスク バー関連の開発 – ジャンプ リストの活用 (その 1)」、「その 2」、「その 3」 を参照してください。
ただし一部のアプリケーションでは、Windows 7 タスク バーのジャンプ リスト タスクを、アプリケーションのステータスを変更するための "コマンド" として使用します。たとえば、Windows Media Player の場合は、いくつかのタスク ([前回の再生リストを再開] など) が表示され、ユーザーがそのタスクを選択すると、Media Player の 2 つ目のインスタンスを起動する代わりに、対象のタスクそのものが実行されます。Windows Live Messenger の場合は、次の図に示すように、在席中かどうかのステータスを制御するためのタスクが表示されます。
つまり、Windows Live Messenger では、アプリケーションを切り替えることなくステータスを変更できます。
ここで紹介するタスク バーの単一インスタンスのサンプル コードを使用すれば、実行中のインスタンスの状態を変更し、受信するステータス変更通知に応じてアプリケーションの動作を変えるような、Messenger と同様のタスクを使用したアプリケーションを簡単に開発することができます。
このブログでは、Windows 7 Taskbar Single Instance (Windows 7 タスク バーの単一インスタンス) サンプル コード の使用方法を説明します。このライブラリの動作のしくみについて詳しく知りたい方は、このライブラリに付属のドキュメント (英語) をダウンロードしてご確認ください。ネイティブの C++ バージョンと、マネージ (.NET) バージョンを提供しています。
Windows 7 タスク バーのジャンプ リスト タスク API の使い方はシンプルです。必要な作業は、IShellItem または IShellLink のリストを入力するだけです。各エントリとして、「コマンド」 ラインに任意の引数を付加して入力します。ただし、Windows 7 タスク バーから単純に IShellItem や IShellLink をアクティブ化すると、新しいプロセスが起動されてしまう (ユーザーから見るとアプリケーションの新しいインスタンスが起動される) ので、注意が必要です。対象のシェル リンクがカスタムの実行可能ファイルを参照している場合は、アクティブ化によってカスタム プロセスの新たなインスタンスが起動されます。これにより、目的の結果が得られる場合もありますが (たとえば、新しいメモ帳ウィンドウで新しいドキュメントを開く場合など)、状況によっては、タスク バー タスクを使用して現在のインスタンスの状態に影響を及ぼしたい (メッセンジャー アプリケーションのステータス変更など) 場合もあります 。
単純に言えば、Windows 7 ジャンプ リスト タスクをクリックするたびに、Windows は毎回新しいプロセスを起動して対象のコマンド ラインを実行します。Windows 7 タスク バーのジャンプ リスト タスクを通じて、「情報をやり取り」 するための API は用意されていません。タスク バーのジャンプ リスト タスクによってアプリケーションと 「情報をやり取り」 するには、まずアプリケーションの起動時に同じアプリケーションが既に実行されてないかチェックするコードを実行します。そして、既にアプリケーションが実行されている場合には、その実行中のアプリケーション インスタンスに対しユーザーがクリックした必要なタスクを通知して、作成したばかりの新しいインスタンスを閉じる必要があります。
通常、これには既にインスタンスが実行されているかどうかを一定の形式の同期オブジェクトによってチェックし (ミューテックスなど)、通知自体は通信メカニズムによって実行されます。基本的に、これは Singleton パターンの実装といえます。
こうしたタスクを実行するためのコードは、多くの種類のアプリケーションについて頻繁に記述されており、通常きわめて似通っています。そのため、このタスク バーの単一インスタンス サンプル コードでは、ネイティブ コードとマネージ コードの両方の開発者を対象に、シンプルで再利用可能なコード ライブラリを提供することを目的としています。このライブラリは既定の設定のままで利用することもできますが、必要に応じて簡単にカスタマイズしたり拡張することも可能です。
Singleton パターンの詳細な実装方法と各種サンプルをご覧になりたい方は、サンプル コードと付属のドキュメント (英語) をダウンロードできます。それでは、このライブラリの使用方法に移りましょう。
先ほど挙げた問題点を踏まえて、解決策を検討し、実際のコードを詳しく見ていきます。ライブラリのいずれかのサンプル コードを実行し、ジャンプ リスト タスクを使用してアプリケーションのステータスを変更すると、現在実行中のアプリケーションのステータスは変更されますが、アプリケーションの 2 つ目のインスタンスは起動されないことに気付かれることでしょう。バックグラウンドで何が起きているのかを詳しく確認するには、Process Explorer (Sysinternals の一部) を使用する必要があります。このツールを使うと、システムの各プロセスを確認することができます。新しいプロセスは数秒間、緑色でハイライトされ、終了されようとしているプロセスは赤でハイライトされます。
これを前提に、タスク バーの単一インスタンス サンプル コードの動作を確認していきましょう。アプリケーションは既に実行されているものとします。下記の図で、上の方に表示され ている小さなウィンドウは使用するサンプル アプリケーションを表しており、下の大きなウィンドウは Process Explorer を表しています。赤枠で囲まれた部分には、WpfSample.exe プロセスが 2 行に分けて表示されています。そのうち黄色でハイライトされている 1 行はメイン プロセスまたは実行中のアプリケーションであり、図の上方にある「Window1」という小さな緑色のウィンドウ内に表示されています。
2 つ目の WpfSample.exe プロセスは緑色でハイライトされています。これは、アプリケーションのステータスを前の状態から 「オンライン」 状態に変更するために、ジャンプ リストにある [オンライン] タスクをクリックした結果作成された新しいプロセスです。ご覧のように、Windows はアプリケーションの 2 つ目のインスタンスを起動していますが、ウィンドウには表示されていません。アプリケーションはまだ非表示の状態です。
次に、サンプル コードが作動し、アプリケーションの新しく作成されたインスタンスから既存のインスタンスへ 「オンライン」 のメッセージが "送信" されます。続いて、サンプル コードは、新しく作成されたインスタンスの終了を確認します。次の図で示すように、2 行目が赤でハイライトされ、このプロセスが終了されます。
このライブラリのサンプル コードを利用するうえで重要となるのが、SingleInstanceManager クラスです。サンプル コードを使用するには、静的な Initialize() メソッドを通じて SingleInstanceManager のインスタンスを作成します。このメソッドにより、SingleInstanceManagerSetup 型のオブジェクトが許可され、SingleInstanceManager の動作をカスタマイズできるようになります。カスタマイズしない場合、SingleInstanceManager はこのブログで示す既定値を使用します。この Setup オブジェクトでは少なくとも、アプリケーションを単一インスタンスとして識別するためのアプリケーション ID を指定する必要があります。
この SingleInstanceManager は、マネージ コードとネイティブ コードの両方をサポートします。ここでは、ネイティブ コード (C++ コード) を例に説明します。
1: SingleInstanceManagerSetup simSetup(L"MyApp");
2: g_SIM = SingleInstanceManager::Initialize(&simSetup);
3:
4: /* アプリケーション コードをここに入力 */
5:
6: // メモリを解放
7: delete g_SIM;
SingleInstanceManager オブジェクトが起動している限り、同じアプリケーションの別のインスタンスを 1 つ目のインスタンスと並行して実行することはできません。2 つ目のアプリケーション インスタンスを起動しようとすると、既定では、2 つ目のアプリケーションのプロセスはアプリケーション コードを一切実行することなく終了します。必要に応じて、プロセスを終了する代わりに、SingleInstanceManager が例外をスローするように設定することもできます (「その他の構成オプション」の項を参照)。
追加起動されたアプリケーション インスタンスが送信する引数を受信するには、SingleInstanceManagerSetup オブジェクトの作成時に引数ハンドラーを指定する必要があります。
1: void ArgsHandler(LPCWSTR* pArguments, DWORD dwLength, LPVOID pContext)
2: {
3: // 受信する引数を処理
4: }
6: int _tmain(int argc, _TCHAR* argv[])
7: {
8: // ハンドラー (メソッドの 1 つを呼び出すオブジェクト インスタンスなど) に、
9: // NULL 引数がコンテキストとして渡される
10: SingleInstanceManagerSetup simSetup(L"MyApp", ArgsHandler, NULL);
11: g_SIM = SingleInstanceManager::Initialize(&simSetup);
12:
13: /* アプリケーション コードをここに入力 */
14:
15: // メモリを解放
16: delete g_SIM;
17: return 0;
18: }
既定では、1 つ目のアプリケーション インスタンスが既に存在する場合、2 つ目のプロセスで SingleInstanceManager オブジェクトを初期化しようとすると (新しいアプリケーション インスタンスの起動の一環として)、常に 2 つ目のインスタンスのコマンド ライン引数が 1 つ目のインスタンスに送信され、構成済みのハンドラーによって、その引数が通知されます。1 つ目のインスタンスに送信される引数は、Setup オブジェクトを介してカスタマイズできます (「その他の構成オプション」を参照)。この Setup オブジェクトを使用すると、アプリケーション間でのパラメーターの送信方法だけでなく、その他の通知をすべて制御できます。
SingleInstanceManager の動作は、Setup オブジェクト (SingleInstanceManagerSetup) のさまざまなプロパティを通じてカスタマイズできます。ここでは、そうしたプロパティの概要を説明します。ネイティブの C++ では、これらのプロパティに "Set...()" メソッドと "Get...()" メソッドを使用してアクセスできるほか、クラス コンストラクター経由で設定することも可能です。
3: MyClass *pClass = (MyClass *) pContext;
4: pClass->SomeMethod(...); // 受信する引数を処理
5: }
6:
7: int _tmain(int argc, _TCHAR* argv[])
8: {
9: MyClass *pClass = new MyClass();
10: SingleInstanceManagerSetup simSetup(L"MyApp", ArgsHandler, pClass);
15: delete g_SIM;
16: delete pClass;
DeliveryStrategyFactory は、SingleInstanceManager で次の 2 つの場合に使用されるクラスです。
それぞれの Strategy 実装には、2 つのプロセス間でのやり取りに必要となるすべてのロジックとコードが含まれています。既定で指定された Strategy がユーザーのニーズに合わない場合は、別の実装を簡単にプラグインして指定できます。