基本的にはWindows Mobileサンプルプログラムとプロジェクト作成方法は変わりませんが、アプリケーションの種類をDLLにしておきましょう。
シンボルは後で、defファイルを使ってエクスポートするので、チェックはいらないです。
これで、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も追加しておきましょう。
プリコンパイルヘッダ(stdafx.h)の一番下のTODO以下でいいと思います。
// TODO: プログラムに必要な追加ヘッダーをここで参照してください。 #include <todaycmn.h>
InitializeCustomItemは、これから、today画面に表示するWindowを作成して、そのWindowハンドルを返すことになります。
CustomItemOptionsDlgProcは、設定ダイアログを作った場合、そのダイアログのメッセージ処理を書きます。
ダイアログプロシージャは、メッセージを処理した場合にTRUEを返すので、とりあえず今はFALSEで。
次に、追加した関数を、DLLの外から見えるようにしてやる必要があります。
しかも、これらの関数は、外からコールする際の関数の番号(序数)を240番と241番という値に固定してやらなければなりません。
このために、defファイルを使用したエクスポートの設定をしてやります。
まずは、ソリューションエクスプローラの追加したい箇所で右クリックして、追加→新しい項目を選びます。
ソースにでも追加しておきますかね。
で、コードのモジュール定義ファイルを選んで、適当に名前を入力します。
出来たdefファイルには、ライブラリ名だけが記述されていますので、EXPORTS指定をして、先程の2関数を序数指定で追加します。
LIBRARY "todaytest" EXPORTS InitializeCustomItem @240 CustomItemOptionsDlgProc @241
上記手順で作れば、自動的にプロジェクトのプロパティに、モジュール定義ファイルとして設定されていると思いますので、確認してみてください。
手動でファイルを追加した場合は、モジュール定義ファイルの項目に、追加したファイルを設定してやる必要があります。
この状態でビルドしてやれば、dllとexp(エクスポートファイル)と、インポートライブラリ(lib)ファイルが生成されるはずです。
とはいえ、シェルはダイナミックにdllをロードして、序数指定で関数をコールするので、このインポートライブラリを使う人はいませんが。
InitializeCustomItemで、表示するWindowを作成するために、まずはウィンドウクラスを登録する必要があります。
登録は普通のWin32プログラムとほとんど変わらないのですが、DLLでの登録なので、シェルがDLLを呼び出してる間だけ、ウィンドウクラスが有効である必要があります。
InitializeCustomItem内でウィンドウクラスの登録を行なおうとすると、初回はいいのですが、何らかのタイミングで一旦todayプラグインのWindowをつくり直す契機があった場合、シェルはInitializeCustomItemで作ったWindowを一旦Destroyして、再度InitializeCustomItemをコールし、ウィンドウの再作成をすることがあるという作りらしいので、2回目以降にコールされたとき、既にウィンドウクラスが登録されている状態になってしまいます。
これを防ぐために、DllMainで、プロセスにアタッチされた時に、ウィンドウクラスの登録やリソースをロードし、プロセスからデタッチされた時に、これらを解放するという仕組みが、正攻法のようです。
ウィンドウクラスに登録する名前とかは、定数でもいいんですけど、リソースのストリングテーブルとかに持ってたほうがいいかもしれませんね。
どうせ後で、他のことにもリソースは使いそうなので、リソースファイルを追加しておきますか。
というわけで、先ほどと同じように、リソースに追加します。
で、リソースのリソースファイルを選んで、適当に名前を入力します。
リソースを追加すると、リソースのIDが記述されるresource.hがプロジェクトに追加されます。
使用するソースでは、こいつをincludeしてやる必要がありますので、DllMainの前の、他のincludeの後に追加しておきます。
#include "resource.h"
ちなみに、DllMainの前にincludeされているwindows.hとcommctrl.hですが、この記述はstdafx.hの方に移してしまっていいと思います。
というか、windows.hは既にstdafx.h内に記述されているので、消してしまってもいいです。
基本的に変更されることがないヘッダファイルは、プリコンパイルヘッダ(stdafx.h)中に記述して、そうでないヘッダファイルは、実際に使用するファイルがincludeするようにするといいでしょう。
次に、リソースファイルを作ったので、ここにストリングテーブルを追加します。
右クリックでリソースの追加を選んで……。
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です。
LoadStringは、WindowsMobile版では、文字列バッファにNULLを設定すると、戻り値としてリソース直のポインタが返ってくるらしいですが、単語の境界がよくわからなかったので、普通の使い方してます。
RegisterClassは、通常のWin32プログラムとは違って、結構意味をなさないメンバがいそうです。
まぁ、必要なものだけ設定という感じで。
InitializeCustomItemが呼ばれたときにWindowを作成しますが、まずは引数を見てみますか。
hwndParentの方はまぁ、親Windowのハンドルということで、Today画面自体かなんかのウィンドウハンドルが渡されてくるわけですね。
自分が作成するWindowは、これの子Windowとして作成すると。
リモートスパイを立ち上げて、Window構成を見てみると、DesktopWindowの下に2つくらいかまして、TodayプラグインのWindowがあるように見えますね。
しかし、Todayプラグインは時計を含めて4つ表示しているはずなんだけど、時計らしきものがないですね……。
Todayプラグインの情報が入っているレジストリの場所「HKEY_LOCAL_MACHINE\Software\Microsoft\Today\Items」を見ても、時計らしいものがないので、別扱いなのかな。
もう一つの引数は、TODAYLISTITEMという構造体のポインタですね。
この構造体は、以下のようなメンバとなっているらしいですが、InitializeCustomItemに渡される時には、szName、tlit、dwOrder、fEnabled、fOptions、grfFlagsが埋められていて、fEnabledの値がFALSEの時には、NULLを返さないとならないらしいです。
埋められているってのは、基本的にはレジストリに書かれている値が入ってくるという意味っぽいですね。
レジストリ内容については、また後日。
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のスタイル指定となっています。
位置とサイズが全部CW_USEDEFAULTになっていますが、ウィンドウ作成後、位置、サイズはシェルによって調整されるようなので、とりあえずはこれで問題ないようです。
(高さだけは、後で書くWM_TODAYCUSTOM_QUERYREFRESHCACHEメッセージ処理時に設定するようですが)
あとは、パラメータとしてウィンドウ管理クラスのポインタを渡しているので、WM_CREATEでSetWindowLong(GWL_USERDATA)を使用してウィンドウにクラスを関連付けておき、WM_DESTROY時にクラスをdeleteすればいいでしょうかね。
ウィンドウの管理クラスポインタをグローバルにすると、上位のシェル動作次第ですが、WM_DESTROY前に再度InitializeCustomItemが呼ばれたりすると困るので、ウィンドウに紐付けておくのがよさそうです。
デフォルトのウィンドウメッセージは、<windowsx.h>をincludeしておいて、メッセージをハンドルするのが見やすくていい感じです
。
stdafx.hの、windows.hのincludeの後にでも追加しておくといいでしょう。
あらかじめ、windowsx.h内の定義に従って、WM_CREATEとWM_DESTROY用の関数を用意しておいて……。
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; } }
ここまでいっても、まだちゃんと動くところまでいっていないので、本当に動くのかどうかわかりませんw