ユーザ用ツール

サイト用ツール


todayプラグインの作成

Todayプラグインの作成

Todayプラグインとして実装すべきもの

Todayプラグインは、Today本体が適切にイベントを送り、また、画面を破綻させないように、定められた作法で作る必要がある。 WindowsCEの初期のものから、PocketPCを経るうちに拡張されたものもあり、これらの情報が散在していて探すのが面倒である。

ここに、USB Select Todayを実装するにあたって、調べた事項をまとめておく。

エントリポイント

Todayプラグインは、DLLとして実装しなければならない。 このため、基本的には、C++/Win32アプリとして実装しなければならない。1)

Visual Studio 2005の場合、C++でスマートデバイスアプリを選んで、Win32のDLLを作成するプロジェクトを作る。

リソースは、普通に作成することが出来るが、resource.hの内容は自動的には includeされないので、stdafx.hの最後に、

#include "resource.h"

を追記しておく。.cppファイルの中で #include “resource.h”を指定しても、何故か、全く読み込まれないので、注意が必要である。2)

さらに、Todayプラグインのお約束としては、次の内容の、defファイルを作る必要がある。

EXPORTS
InitializeCustomItem @ 240 NONAME
CustomItemOptionsDlgProc @ 241 NONAME

InitializeCustomItemとCustomItemOptionsDlgProcは、Today本体がプラグインを呼び出すためのエントリである。.cppの中にエントリだけ作っても、.defファイルを作成して、この内容を記述していないと、プラグインの一覧にさえ現れないので、忘れないようにする。

DllMain

BOOL APIENTRY DllMain(
  HANDLE hModule,
  DWORD ul_reason_for_call,
  LPVOID lpReserved
);
[in] hModule
  これはインスタンスへのハンドル。必要に応じて大域変数にでも保存しておく。
[in] ul_reason_for_call
  呼び出された理由。DLL_PROCESS_ATTACHとDLL_PROCESS_DETACHが想定される。
[in/out?]  lpReserved
  予約。使われていないはず。

DllMainは、全てのDLLが備えていなければならないエントリポイントである。

ここでは、モジュールのインスタンスハンドルを取得したり、アイコンなどのリソースを用意したり、WNDCLASSを登録したりするのがDLL_PROCESS_ATTACH時に、またこれらの解放を行なうのがDLL_PROCESS_DETACH時に求められる動作である。

ここで、WndProc()を登録しないと、イベントがやってこないので、確実に行なう。

// IDS_TODAY_XXX_APPNAME は適当な文字列リソースを作っておくこと。
// 別に直接、L"SAMPLE" などとして、文字列を埋め込んでもいいだろうが
// あまり美しくないと思う。
// WndProcは、LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM) で
// 別途作成しておくこと。
WNDCLASS wc;
memset(&wc, 0, sizeof(wc));

wc.style         = 0;
wc.lpfnwndProc   = (WNDPROC)WndProc;
wc.hInstance     = hModule;
wc.hIcon         = 0;
wc.hCursor       = 0;
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszClassName = (LPCTSTR)LoadString(hModule, IDS_TODAY_XXX_APPNAME, 0, 0);

if (!RegsterClass(&wc))
{
  // 登録失敗
}

DLL_PROCESS_DETACHが来たら、確保したリソースなどを解放するとともに、クラスの登録を削除する。

// g_hInst は ATACHした際に、hModuleをコピーしておいた大域変数
// IDS_TODAY_XXX_APPNAMEは、上を参照。
UnregisterClass((LPCTSTR)LoadString(g_hInst, IDS_TODAY_XXX_APPNAME, 0, 0), g_hInst);

InitializeCustomItem

HWND APIENTRY InitializeCustomItem(
  TODAYLISTITEM *ptlistItem,
  HWND hwndParent
);
[in] ptlistItem
  szName, tlit, dwOrder, fEnabled, fOptions,およびgrfFlagsが設定されている。
  fEnabledがFALSEなら、即座にNULLを返して終了すること。
[out] hwndParent

目に見えない初期化は、DllMainがDLL_PROCESS_ATTACHを受け取ったときに、行なわれている。ここでは、目に見える部分の初期化を行なう。即ち、ウィンドウを作成し、ウィジェットを適切に配置して、パネルの表示を行なう。

このとき、ptlistItem→grfFlagsに、オプション設定が渡ってきている。オプション設定画面を持つパネルの場合には、この情報に基づいて、表示や動作のカスタマイズを行なう。

ウィンドウは高さを0で作成しておく。WndProc()で、WM_TODAYCUSTOM_QUERYREFRESHCACHEを受け取ったときに、ptlistItem→cypを検査し、これが0ならば、適切な高さを設定し、描画イベントが発生する用に TRUEを返す。これが、初期化動作ということになる。

// g_hInst は、インスタンスハンドル
// g_hWnd は、作成したウィンドウハンドルを格納する大域変数
// IDS_TODAY_XXX_APPNAME は、文字列リソースのID値
HWND APIENTRY InitializeCustomItem(TODAYLISTITEM *ptlistItem, HWND hwndParent)
{
  LPCTSTR appName = (LPCTSTR)LoadString(g_hInst, IDS_TODAY_XXX_APPNAME, 0, 0);
  // Windowの高さは '0'で作る。
  g_hWnd = CreateWindow(appName, appName, WS_VISIBLE | WS_CHILD,
      CW_USEDEFAULT, CW_USEDEFAULT, 240, 0, hwndParent, NULL, g_hInst, NULL);
  // 他に必要なリソースがあればここで作成しておく
  // ウィンドウハンドラを登録する
  SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG)WndProc);
  // ウィンドウを表示する
  ShowWindow(g_hWnd, SW_SHOWNORMAL);
  UpdateWindow(g_hWnd);
  
  return g_hWnd;
}

CustomItemOptionsDlgProc

BOOL APIENTRY CustomItemOptionsDlgProc(
  HWND hDlg,
  UINT message,
  UINT wParam,
  LONG lParam
);
[in] hDlg
  ダイアログのハンドル
[in] message
  メッセージID
[in] wParam
  16bitパラメータ(メッセージに依存)
[in] lParam
  32bitパラメータ(メッセージに依存)

レジストリに、Options == 1をセットした場合には、このエントリに中身を書いてやらなければならない。0なら、ただリターンするだけの関数でもいいだろう。

このハンドラに対応するダイアログは、IDD_TODAY_CUSTOM (500)をIDとして作成されていなければならない…あんまりちゃんと書いてないけれど。3)

オプションは、DWORD TODAYLISTITEM::grfFlagsを介して、パネル本体とやり取りされる。32bitのデータ幅なので、大抵の場合は、単純なON/OFFや、機能番号などを渡すのに用いるのだろう。他の用い方があるかどうかは調べていない。USB Select Todayは、5bitを受け渡しに使っている。

 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+-----------------------------------------------------+-+-+-+-+-+
|あき                                                 |I|3|2|1|0|
+-----------------------------------------------------+-+-+-+-+-+
I: 初期化フラグ 0なら初期状態 0〜3は不定と見なし、全て1に初期化される
0〜3: ActiveSync(NDIS), Modem, Mass Storage, Serialの各アイコンを表示するか否か。

WM_INITDIALOG中で、ptlistItem == NULLのケースが初期状態ではないことに注意する。初回からちゃんと、ptlistItemは割り当てられている。ので、上のような初期化フラグを1bit設けた。別のやり方も勿論あるだろう。

BOOL APIENTRY CustomItemOptionsDlgProc(HWND hDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
)
{
  static TODAYLISTITEM *s_ptlistItem = NULL;
  switch (message)
  {
  case WM_INITDIALOG:
    {
      SHINITDLGINFO shidi;
      // ダイアログを初期化
      shidi.dwMask  = SHIDIM_FLAGS;
      shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN;
      shidi.hDlg = hDlg;
      SHInitDialog(&shidi);
      // ptlistItemを取得
      s_ptlistItem = (TODAYLISTITEM*)lParam;
      if (s_ptlistItem)
      {
        // 例えばこれはUSB Select Todayの初期化シーケンス
        if ((s_ptlistItem->grfFlags & 0x10) == 0)
        {
          s_ptlistItem->grfFlags |= 0x1f;
        }
      }
      else
      {
        // これは異常系、どうするかは決めてください。
      }
      // チェックボタンなどのウィジェットの状態を初期化
    }
    return TRUE;
  case WM_COMMAND:
    if (LOWORD(wParam) == IDOK)
    {
      // ウィジェットの値を読み取って、grfFlagsに設定する。
      // ダイアログを閉じる
      EndDialog(hDlg, LOWORD(wParam));
      return TRUE;
    }
    break;
  case WM_DESTROY:
    s_ptlistItem = NULL;
    break;
  }
  return FALSE;
}

メッセージハンドリング

WM_CUSTOMTODAY_QUERYREFRESHCACHE

WM_CUSTOMTODAY_CLEARCACHE

WM_CUSTOMTODAY_RECEIVEDSELECTION

WM_CUSTOMTODAY_LOSTSELECTION

WM_CUSTOMTODAY_USERNAVIGATION

WM_CUSTOMTODAY_ACTION

WM_LBUTTONUP

WM_PAINT

WM_ERASEBKGND

API

TODAYM_GETCOLOR

TODAYM_DRAWWATERMARK

TODAYM_TOOKSELECTION

レジストリ

DLL

Type

Options

Selectability

オプションダイアログ

リンク

1)
C#.NETで作る方法もあるが、どの程度実用的なのか不明。性能的には疑問符が付く。
2)
この問題で半日悩んだのは秘密。
3)
最初適当なIDで作ったらやっぱり表示されなかった。どこにも、ダイアログを表示するコードフラグメントがないのだから、どっかでシステムが決めウチで面倒を見てくれているのだろう。
todayプラグインの作成.txt · 最終更新: 2015/04/10 08:27 by 127.0.0.1