Windows 7 サンプル コード - タスク バーの単一インスタンス

みなさん、こんにちは。Windows 開発統括部の古内です。立秋を過ぎてますます夏本番といった感じですが、いかがお過ごしでしょうか。

さて、本日は、Windows 7 のタスク バー機能を活用したサンプル コードをご紹介します。2011 年 2 月 23 日に Developing for Windows Blog に 投稿された 「Windows 7 Recipe – Taskbar Single Instance」 の翻訳です。かなり長い記事ですが、最後までお付き合いいただけると幸いです。


Windows 7 サンプル コード - タスク バーの単一インスタンス

今回は、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 タスク バーから単純に IShellItemIShellLink をアクティブ化すると、新しいプロセスが起動されてしまう (ユーザーから見るとアプリケーションの新しいインスタンスが起動される) ので、注意が必要です。対象のシェル リンクがカスタムの実行可能ファイルを参照している場合は、アクティブ化によってカスタム プロセスの新たなインスタンスが起動されます。これにより、目的の結果が得られる場合もありますが (たとえば、新しいメモ帳ウィンドウで新しいドキュメントを開く場合など)、状況によっては、タスク バー タスクを使用して現在のインスタンスの状態に影響を及ぼしたい (メッセンジャー アプリケーションのステータス変更など) 場合もあります 。

単純に言えば、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: }
  5:  
  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...() " メソッドを使用してアクセスできるほか、クラス コンストラクター経由で設定することも可能です。

  • ApplicationId プロパティは、アプリケーションを識別するために使用され、このプロパティをキーとしてアプリケーションの単一インスタンスが特定されます。アプリケーション ID は、ログオン ユーザーごとに一意である点に注意してください (同一マシン上で、異なるユーザーによって 2 つのアプリケーション インスタンスが実行されている場合、2 つのアプリケーション ID は一致しません)。ApplicationId は、SingleInstanceManagerSetup オブジェクトを作成する際の唯一の必須プロパティです。
  • ArgumentsHandler プロパティは、ハンドラー メソッドを指定し、送信されてくる引数の通知を受信するために使用されます (例として、「他のアプリケーション インスタンスからの通知の受信」セクションを参照)。
  • Context プロパティはネイティブ コードでのみ利用でき、引数のハンドラーに渡される Context オブジェクトを指定するために使用されます。
  1: void ArgsHandler(LPCWSTR* pArguments, DWORD dwLength, LPVOID pContext)
  2: {
  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);
  11:     g_SIM = SingleInstanceManager::Initialize(&simSetup);
  12:  
  13:     /* アプリケーション コードをここに入力 */
  14:  
  15:     delete g_SIM;
  16:     delete pClass;
  17:     return 0;
  18: }
  • TerminationOption プロパティを使用すると、アプリケーション インスタンスが既に実行されており、現在のインスタンスを閉じる必要がある場合 (元のインスタンスに引数を通知した後で) の一連のアクションを選択できます。この enum には次の 2 つの値を指定できます。
  • Exit (既定) - ユーザー コードには戻らずに、現在のプロセスを終了します。マネージ コードでは "System.Environment.Exit() " が、ネイティブ コードでは "ExitProcess() " が呼び出され、いずれの場合も ExitCode プロパティで指定した終了コードが実行されます。
  • Throw - 別のアプリケーション インスタンスが既に実行されていることを示す例外をスローします。その後、例外を受信したユーザーが、正常に処理を終了できます。
  • ExitCode プロパティでは、TerminationOption プロパティが "Exit" に指定されており、かつアプリケーション インスタンスが既に実行されている場合に使用される終了コードを指定できます。既定値は 0 です。
  • ArgumentsProvider プロパティを使用すると、1 つ目のアプリケーション インスタンスに引数を渡す際の既定の応答を置き換えることができます。既定では、現在のプロセスのコマンド ライン引数が引数として渡され、マネージ コードでは "Environment.GetCommandLineArgs()" が、ネイティブ コードでは "GetCommandLineW () " および "CommandLineToArgvW () " が使用されます。
  • Factory プロパティでは、SingleInstanceManager が引数を渡す方法をカスタマイズできます。既定では、マネージ コードでは "RemotingStrategyFactory" が、ネイティブ コードでは "NamedPipeStrategyFactory" が使用されます。これらの Factory オブジェクトにより、"RemotingStrategy" Strategy オブジェクトと "NamedPipeStrategy" Strategy オブジェクトがそれぞれ生成されます。

DeliveryStrategyFactory は、SingleInstanceManager で次の 2 つの場合に使用されるクラスです。

  • 通知: 現在のアプリケーション インスタンスが 1 つ目のインスタンスではない場合に、元のアプリケーション インスタンスにコマンド ライン引数を通知する場合
  • 受信: 現在のインスタンスが 1 つ目のインスタンスである場合に、他のアプリケーション インスタンスから送信されてくるコマンド ライン引数の通知を受信する場合

それぞれの Strategy 実装には、2 つのプロセス間でのやり取りに必要となるすべてのロジックとコードが含まれています。既定で指定された Strategy がユーザーのニーズに合わない場合は、別の実装を簡単にプラグインして指定できます。

  • ArgumentsHandlerInvoker は、登録済みの引数ハンドラーを呼び出すために SingleInstanceManager によって使用されるオブジェクトです。デフォルトでは、マネージ コードの場合はスレッド プール上で、ネイティブ コードの場合は直接受信したスレッド上で、このハンドラーが呼び出されます。
  • InstanceNotificationOption プロパティは、セキュリティ権限を昇格させて 1 つ目のアプリケーション インスタンスを起動 (すなわち、管理者として実行) しており、2 つ目のインスタンスでは管理者ではない場合の動作を決定するために使用されます。この enum には次の 2 種類のオプションがあります。
    • NotifyAnyway (既定) - 無条件で元のアプリケーション インスタンスに通知します。
    • NotifyOnlyIfAdmin - 2 つ目のアプリケーション インスタンスも管理者として実行されている場合にのみ、1 つ目のアプリケーション インスタンスに通知します。
  • DeliveryFailureNotification は、2 つ目のプロセスから 1 つ目のプロセスへの引数の引き渡しに失敗した場合に備え、SingleInstanceManager にコールバック メソッドを提供するために使用されます。