Today プラグインサンプルプログラム †手順 †基本的にはWindows Mobileサンプルプログラムとプロジェクト作成方法は変わりませんが、アプリケーションの種類をDLLにしておきましょう。 これで、DllMainのみのソースが作成されますので、ここに必要なものを追加していきましょう。 シェルからコールされる関数のエクスポート †Todayプラグインは、シェルからロードされる時に呼び出される関数「InitializeCustomItem」と、設定画面を作る場合は、その設定ダイアログ用のプロシージャ「CustomItemOptionsDlgProc」を、定義して外部から参照できるようにする必要があります。 まずはソース(DllMainの下にでも)に、以下を追加します。 HWND APIENTRY InitializeCustomItem( TODAYLISTITEM *ptli, HWND hwndParent ) { return NULL; } BOOL APIENTRY CustomItemOptionsDlgProc( HWND hDlg, UINT message, UINT wParam, LONG lParam ) { return FALSE; } TODAYLISTITEMの定義が、デフォルトでは参照できないので、以下のincludeも追加しておきましょう。 // TODO: プログラムに必要な追加ヘッダーをここで参照してください。 #include <todaycmn.h> InitializeCustomItemは、これから、today画面に表示するWindowを作成して、そのWindowハンドルを返すことになります。 次に、追加した関数を、DLLの外から見えるようにしてやる必要があります。 まずは、ソリューションエクスプローラの追加したい箇所で右クリックして、追加→新しい項目を選びます。 で、コードのモジュール定義ファイルを選んで、適当に名前を入力します。 出来たdefファイルには、ライブラリ名だけが記述されていますので、EXPORTS指定をして、先程の2関数を序数指定で追加します。 LIBRARY "todaytest" EXPORTS InitializeCustomItem @240 CustomItemOptionsDlgProc @241 上記手順で作れば、自動的にプロジェクトのプロパティに、モジュール定義ファイルとして設定されていると思いますので、確認してみてください。 この状態でビルドしてやれば、dllとexp(エクスポートファイル)と、インポートライブラリ(lib)ファイルが生成されるはずです。 ウィンドウクラス登録 †InitializeCustomItemで、表示するWindowを作成するために、まずはウィンドウクラスを登録する必要があります。 InitializeCustomItem内でウィンドウクラスの登録を行なおうとすると、初回はいいのですが、何らかのタイミングで一旦todayプラグインのWindowをつくり直す契機があった場合、シェルはInitializeCustomItemで作ったWindowを一旦Destroyして、再度InitializeCustomItemをコールし、ウィンドウの再作成をすることがあるという作りらしいので、2回目以降にコールされたとき、既にウィンドウクラスが登録されている状態になってしまいます。 これを防ぐために、DllMainで、プロセスにアタッチされた時に、ウィンドウクラスの登録やリソースをロードし、プロセスからデタッチされた時に、これらを解放するという仕組みが、正攻法のようです。 ウィンドウクラスに登録する名前とかは、定数でもいいんですけど、リソースのストリングテーブルとかに持ってたほうがいいかもしれませんね。 で、リソースのリソースファイルを選んで、適当に名前を入力します。 リソースを追加すると、リソースのIDが記述されるresource.hがプロジェクトに追加されます。 #include "resource.h" ちなみに、DllMainの前にincludeされているwindows.hとcommctrl.hですが、この記述はstdafx.hの方に移してしまっていいと思います。 次に、リソースファイルを作ったので、ここにストリングテーブルを追加します。 String Tableを選択して、新規作成。 作成できたら、ウィンドウクラス名を登録するIDと文字列を入力します。 ストリングテーブルの他に、ウィンドウクラスには、そのウィンドウクラスを指定して作成したWindowのメッセージを処理する、ウィンドウプロシージャが必要です。 LRESULT CALLBACK WndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { return ::DefWindowProc( hwnd, uiMsg, wParam, lParam ); } これで、下準備は出来ました。 というわけで、出来たクラスと使用側のDllMainがこんな感じでしょうか。 使用APIはLoadStringとRegisterClassとUnregisterClassです。 ウィンドウ作成 †InitializeCustomItemが呼ばれたときにWindowを作成しますが、まずは引数を見てみますか。 HWND APIENTRY InitializeCustomItem( TODAYLISTITEM *ptli, HWND hwndParent ); hwndParentの方はまぁ、親Windowのハンドルということで、Today画面自体かなんかのウィンドウハンドルが渡されてくるわけですね。 リモートスパイを立ち上げて、Window構成を見てみると、DesktopWindowの下に2つくらいかまして、TodayプラグインのWindowがあるように見えますね。 もう一つの引数は、TODAYLISTITEMという構造体のポインタですね。 typedef struct _TODAYLISTITEM { TCHAR szName[MAX_ITEMNAME]; TODAYLISTITEMTYPE tlit; DWORD dwOrder; DWORD cyp; BOOL fEnabled; BOOL fOptions; DWORD grfFlags; TCHAR szDLLPath[MAX_PATH]; HINSTANCE hinstDLL; HWND hwndCustom; BOOL fSizeOnDraw; BYTE * prgbCachedData; DWORD cbCachedData; } TODAYLISTITEMTYPE; fEnabledがFALSEだったらNULLを返し、そうでない場合は、適当にウィンドウを管理するクラスでも作って、ウィンドウを作成しましょう。 m_hwnd = ::CreateWindow( lpCApp->getClassName(), lpCApp->getClassName(), WS_VISIBLE | WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, lpCApp->getInstance(), ( LPVOID )this ); ウィンドウタイトルはとりあえずクラス名と同じにしておくとして、シェルのTodayウィンドウの子ウィンドウとして、それぞれのアイテムウィンドウを作成するため、WS_CHILDのスタイル指定となっています。 あとは、パラメータとしてウィンドウ管理クラスのポインタを渡しているので、WM_CREATEでSetWindowLong(GWL_USERDATA)を使用してウィンドウにクラスを関連付けておき、WM_DESTROY時にクラスをdeleteすればいいでしょうかね。 デフォルトのウィンドウメッセージは、<windowsx.h>をincludeしておいて、メッセージをハンドルするのが見やすくていい感じです
。 BOOL Today_OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct ); // WM_CREATE処理 void Today_OnDestroy( HWND hwnd ); // WM_DESTROY処理 ウィンドウプロシージャの記述を、以下のように。 LRESULT CALLBACK WndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ){ HANDLE_MSG( hwnd, WM_CREATE, Today_OnCreate ); HANDLE_MSG( hwnd, WM_DESTROY, Today_OnDestroy ); } return ::DefWindowProc( hwnd, uiMsg, wParam, lParam ); } 実際の処理は、これでいいかな……? BOOL Today_OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct ) { LPCTodayWnd lpCTodayWnd = ( LPCTodayWnd )lpCreateStruct->lpCreateParams; // Todayウィンドウクラス if( NULL == lpCTodayWnd ){ return FALSE; } if( !::SetWindowLong( hwnd, GWL_USERDATA, ( LONG )lpCTodayWnd )){ return FALSE; } return TRUE; } void Today_OnDestroy( HWND hwnd ) { LPCTodayWnd lpCTodayWnd = NULL; // Todayウィンドウクラス lpCTodayWnd = ( LPCTodayWnd )::GetWindowLong( hwnd, GWL_USERDATA ); if( NULL != lpCTodayWnd ){ ::SetWindowLong( hwnd, GWL_USERDATA, NULL ); delete lpCTodayWnd; lpCTodayWnd = NULL; } } さて、作ったウィンドウを表示させるには、あと一つやらなければならないことがあります。 WM_TODAYCUSTOM_QUERYREFRESHCACHE wParam = (WPARAM) lptli; lParam = (LPARAM) res; このメッセージは、WPARAMとして、TODAYLISTITEM構造体のポインタを受け取ります。 あと、WindowsMobileSDKのTodayプラグインサンプルを見ると、シェルAPIの準備が出来ているかどうかというのを、WaitForSingleObjectでチェックしているようです。 では実際のソース。 /* BOOL Cls_OnTCQueryRefreshCache(HWND hwnd, TODAYLISTITEM* lptli) */ #define HANDLE_WM_TODAYCUSTOM_QUERYREFRESHCACHE(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (TODAYLISTITEM*)(wParam))) #define FORWARD_WM_TODAYCUSTOM_QUERYREFRESHCACHE(hwnd, lptli, fn) \ (BOOL)(DWORD)(fn)((hwnd), WM_TODAYCUSTOM_QUERYREFRESHCACHE, (WPARAM)(TODAYLISTITEM*)(lptli), 0L) ソースの方にプロトタイプも追加して……。 BOOL Today_OnTCQueryRefreshCache( HWND hwnd, TODAYLISTITEM* lptli ); // WM_TODAYCUSTOM_QUERYREFRESHCACHE処理 ウィンドウプロシージャにメッセージのハンドリングを追加して。 LRESULT CALLBACK WndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ){ HANDLE_MSG( hwnd, WM_CREATE, Today_OnCreate ); HANDLE_MSG( hwnd, WM_DESTROY, Today_OnDestroy ); HANDLE_MSG( hwnd, WM_TODAYCUSTOM_QUERYREFRESHCACHE, Today_OnTCQueryRefreshCache ); } return ::DefWindowProc( hwnd, uiMsg, wParam, lParam ); } 実際の処理は以下のように。 BOOL Today_OnTCQueryRefreshCache( HWND hwnd, TODAYLISTITEM* lptli ) { if(( NULL == lptli ) || ( WAIT_TIMEOUT == ::WaitForSingleObject( SHELL_API_READY_EVENT, 0 ))){ return FALSE; } if( 0 == lptli->cyp ){ lptli->cyp = 40; return TRUE; } return FALSE; } 今回は暫定的に、アイテムの高さを40ピクセルに指定しています。 配置 †さて、作ったソフトは、実行する前にインストールの必要があります。 というわけで、ソリューションに新規プロジェクトを追加します。 「セットアップと配置」から、「スマートデバイスCABプロジェクト」を選択します。 プロジェクトを選択した状態で、プロパティウィンドウを開き、ManufacturerとProductNameを設定します。 プロジェクトの右クリックメニューからのプロパティも開いて、出力ファイル名も必要があれば変更しておきましょう。 そして、作成したDLLを、CABに含めるように設定します。 DLL出力プロジェクトの、プライマリ出力を選択します。 これで、出力されたDLLがCABに含まれます。 他に、マクロ文字列は以下のような場所を指しているようですので、覚えておくと後々役に立つかもしれません。 %CE1% \Program Files %CE2% \Windows %CE4% \Windows\StartUp %CE5% \My Documents %CE8% \Program Files\Games %CE11% \Windows\Start Menu\Programs %CE14% \Windows\Start Menu\Programs\Games %CE15% \Windows\Fonts %CE17% \Windows\Start Menu そうそう、このままではスマートデバイスCABプロジェクトのビルドが行われませんので、ソリューションから「構成マネージャ」を選択し、ビルドのチェックを入れておきましょう。 最後に、インストール時に登録するレジストリの設定をしましょう。 HKEY_LOCAL_MACHINE以下にキーを追加していき、「HKEY_LOCAL_MACHINE\Software\Microsoft\Today\Items\製品名(ProductName)」まで作りましょう。 そしてこの中に、以下の値を設定する必要があります。
Typeは通常、4(カスタムプラグイン)を指定しておけばよいです。 Enabledは、インストール直後に表示させたくなければ0にしておくといいでしょう。 Optionsは、設定ダイアログを持つ場合は1にする必要があります。 DLLは、そのまんま、DLLのパスを指定します。 Selectabilityは、そのアイテムを選択できるかどうか、選択できるようにした場合、どういう動作をさせるかという設定です。 というわけで、ここまでできたらビルドしてみましょう。 インストールが終了したら、ActiveSyncのエクスプローラからでも、指定した場所にちゃんとインストールされているか確認しておきましょう。 ここまでできたら、実際にプラグインを動かせますが、毎回ソースを修正するたびにいちいちCABファイルを作成してインストールを繰り返すのは、とても手間です。 DLLのプロジェクトのプロパティを開き、構成プロパティの「配置」を選択します。 プログラムをビルド後、メニューからソリューションの配置(プロジェクトの配置でもいいと思います)を行うと、ActiveSync経由でdllが上書きされます。 以上で、配置については終了です。 ここまでのソリューション、プロジェクトとソースは、こんな感じで。 これを実際に動かしてみると、表示は下図のようになります。 Optionsを無効にしているので、設定ボタンもちゃんと無効になっています。 Today画面で黒く表示されている部分が、今回作成したアイテムになります。 デバッグ †基本的には、上記の配置を使って、以下のような順番でデバッグをしていきます。 まず、ActiveSyncでPCと実機を繋ぎ、プラグインは無効状態にしておきます。 トランスポートをスマートデバイスにして、修飾子の参照から、「Windows Mobile 6 Professional Device」を選択します。 すると、デバッガが起動しますので、DLL_PROCESS_ATTACHの処理と、InitializeCustomItemの先頭にでも、ブレークポイントを置いてみましょう。 一通りアタッチの処理をデバッグしたら、再度実行します。 原因はよくわかっていませんが、shell32.exeがDLLをロードする過程で、メモリ上のDLLの再配置が行われ、デバッガがそれをうまく捕まえられていないのではないかと思います。 というわけで、苦肉の策。 HWND APIENTRY InitializeCustomItem( TODAYLISTITEM *ptli, HWND hwndParent ) { LPCTodayWnd lpCTodayWnd = NULL; // Todayウィンドウクラス ::Sleep( 10000 ); if( FALSE == ptli->fEnabled || NULL == g_lpCApp ){ return NULL; } 上の例では10000msで10秒Sleepさせています。 ウィンドウメッセージは、CreateWindow内でのWM_CREATE発行が最初に飛んできますので、InitializeCustomItemで安定して捕まえられた後であれば、ウィンドウメッセージも全て捕まえられるものと思います。 というわけで、一通り作って動作、デバッグまでの流れを書いてみました。 |