コントロールパネル作成の足がかり
SPECIAL
専用の設定ソフトもいいけど、やっぱりコントロールパネルとして並べてみたい。
ってなわけで、どうしたら自作できるかの研究開始です。
コントロールパネル
Windows 系の OS では、コントロールパネルとして、いろいろな設定用のプログラムがまとめられています。
このコントロールパネル、スタートメニューからたどることができるし、なんだか Windows に公認されたソフトウェアみたいな感じがして、なんだか作りたいなぁ…。
そんなわけでちょっと調べてみることにしました。
コントロールパネルは、拡張子が cpl のファイルです。
調べてみたところ、コントロールパネルファイルは CPlApplet という名前のエントリを持った DLL を作成して、拡張子を cpl へと変更すればいいとのこと。
インターネットを散策していたら、コントロールパネルを作成する記事 が見つかりました。
これは Borland 社の Delphi という開発環境がありましてそれを想定した記事なのですが、とても参考になりました。しかしながら僕は Delphi を持っていないので Microsoft Visual C++ 6.0 で挑戦です。
コントロールパネルの下調べ
DLL エントリポイント
大きな手がかりが見つかったので、さっそく MSDN ライブラリ 2000/10 を調べてみました。
まず、エントリするべき関数のプロトタイプはこのような感じだそうです。
LONG APIENTRY CPlApplet
(
HWND hwndCPl,
UINT uMsg,
LONG lParam1,
LONG lParam2
);
hwndCPl はコントロールパネルのウィンドウハンドル、uMsg は要求の種類をあらわす番号、lParam1 と lParam2 はメッセージごとの追加情報だそうです。
CPL メッセージ
MSDN を見てみたら、uMsg に渡されるメッセージの一覧表がありました。
1 / CPL_INIT
lParam1 | Undefined |
---|---|
lParam2 | Undefined |
コントロールパネル DLL がロードされて、CPlApplet エントリが見つかったことを知らせるメッセージです。lParam1, lParam2 は特に使用しません。
戻り値として TRUE または FALSE を返す必要があるそうです。
2 / CPL_GETCOUNT
lParam1 | Undefined |
---|---|
lParam2 | Undefined |
このコントロールパネル DLL の中に、いくつのアプレットが用意されているかを要求するメッセージです。lParam1, lParam2 は特に使用しません。
戻り値として、DLL の中で用意されているアプレットの数を返します。
3 / CPL_INQUIRE
lParam1 | UINT |
---|---|
lParam2 | LPCPLINFO |
コントロールパネル DLL 内のアプレットの情報を要求するメッセージです。
lParam1 には、要求するアプレットの番号が 0 から始まるインデックスで渡されます。そして lParam2 はそのアプレットの情報を保存するための CPLINFO 構造体を示すポインタが渡されます。
戻り値は特に指定されていないようですが、MSDN によると 0 を返すべきだそうです。
8 / CPL_NEWINQUIRE
lParam1 | UINT |
---|---|
lParam2 | LPNEWCPLINFO |
機能的には CPL_INQUIRE と同じ。
異なる点は lParam2 に対して NEWCPLINFO 構造体が渡ってくるという点のようですが、細かいところは後で調査することにします。また、CPL_INQUIRE の方はキャッシュが有効になるので応答が早くなるのだとか…。
5 / CPL_DBLCLK
lParam1 | UINT |
---|---|
lParam2 | LONG |
コントロールパネルフォルダに表示されているアイコンをダブルクリックすると通知されるメッセージです。
lParam1 には選択されたアプレットの番号が 0 から始まるインデックスで渡されます。また lParam2 には、CPL_INQUIRE / CPL_NEWINQUIRE のときに設定した lData の値が渡されます。
戻り値は MSDN によると成功時には 0 を返せばいいそうです。
6 / CPL_STOP
lParam1 | UINT |
---|---|
lParam2 | LONG |
コントロールパネル DLL が開放されるときに通知されるメッセージです。
lParam1 には選択されたアプレットの番号が 0 から始まるインデックスで渡されます。また lParam2 には、CPL_INQUIRE / CPL_NEWINQUIRE のときに設定した lData の値が渡されます。
戻り値は MSDN によると成功時には 0 を返せばいいそうです。
7 / CPL_EXIT
lParam1 | Undefined |
---|---|
lParam2 | Undefined |
コントロールパネル DLL が開放されるときに通知されるメッセージです。lParam1 と lParam2 は特に使用されず、戻り値は正常終了時に 0 を返せばいいそうです。
9 / 10 / CPL_STARTWPARMS
lParam1 | UINT |
---|---|
lParam2 | LPCTSTR |
10/CPL_STARTWPARMSA と 9/CPL_STARTWPARMSW とが、UNICODE 環境であるかどうかで入れ替わります。
これは CPL_DBLCLK に付加情報がついたようなものだそうで、shell32.dll のバージョン 5 以降でサポートされているメッセージだそうです。
lParam1 には選択したアプレットの番号が 0 から始まるインデックスで渡されます。lParam2 には CPL_DBLCLK のときとは違って文字列が渡されるそうですが、この文字列はどこから…??
なお、戻り値としては、このメッセージをハンドルした場合は TRUE を、そうでなければ FALSE を返す必要があるそうです。
200 / CPL_SETUP
これは MSDN に細かな記載がなく、cpl.h にも引数がどうとかいうお話がなかったのですが、コメントを見る限りでは、インストール時にコマンドラインからアプレットが呼び出された場合に発行されるメッセージだとか…。
なお cpl.h には、4番の CPL_SELECT というメッセージも定義されていますが、これは使用されていないそうです。
構造体
CPLINFO
コントロールパネルの情報を記録するための構造体です。
typedef struct tagCPLINFO
{
int idIcon;
int idName;
int idInfo;
LONG lData;
} CPLINFO;
idIcon は、表示させるアイコンのリソース ID です。idName はアイコンの下に表示される文字列を示すリソース ID です。そして idInfo は、アイコンを選択した際に表示される説明文を記録した文字列を示すリソース ID です。
lData は、CPL_DBLCLK と CPL_STOP 時に添えられる、任意のデータです。
NEWCPLINFO
コントロールパネルの情報を記録するための構造体です。
typedef struct tagNEWCPLINFO
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwHelpContext;
LONG lData;
HICON hIcon;
TCHAR szName[32];
TCHAR szInfo[64];
TCHAR szHelpFile[128];
} NEWCPLINFO;
hIcon は、表示させるアイコンの ID です。szName はアイコンの下に表示される文字列です。そして idInfo は、アイコンを選択した際に表示される説明文を記録した文字列です。
dwSize は NEWCPLINFO 構造体のサイズを渡します。なので、sizeof(NEWCPLINFO) といった感じでいいのでしょうか…。
dwFlags と dwHelpContext、そして szHelpFile は使用しないそうなので、0 なり空文字を設定します。
lData は、CPL_DBLCLK と CPL_STOP 時に添えられる、任意のデータです。
コントロールパネル製作実験
さて、下調べがある程度終わったところで、メッセージがどのような感じで呼び出されるかのチェックを行ってみようと思います。
とりあえず Visual C++ 6.0 で、MFC DLL を作成して、次のようなコードをグローバルに埋め込んでみました。
LONG APIENTRY CPlApplet(HWND hwndCPl, UINT uMsg, LONG lParam1, LONG lParam2)
{
LONG result;
ofstream m_debug("C:\\Documents and Settings\\Owner\\デスクトップ\\CPLTest.txt", ios::out | ios::app);
switch(uMsg)
{
case CPL_INIT:
m_debug << endl << "CPL_INIT" << endl;
result = TRUE;
break;
case CPL_GETCOUNT:
m_debug << "CPL_GETCOUNT" << endl;
result = 3;
break;
case CPL_INQUIRE:
m_debug << "CPL_INQUIRE(" << (UINT)lParam1 << ")" << endl;
((LPCPLINFO)lParam2)->idIcon = IDI_ICON1;
((LPCPLINFO)lParam2)->idInfo = IDS_STRING2;
((LPCPLINFO)lParam2)->idName = IDS_STRING1;
((LPCPLINFO)lParam2)->lData = 0;
result = 0;
break;
case CPL_NEWINQUIRE:
m_debug << "CPL_NEWINQUIRE(" << (UINT)lParam1 << ")" << endl;
((LPNEWCPLINFO)lParam2)->dwSize = sizeof(NEWCPLINFO);
((LPNEWCPLINFO)lParam2)->dwFlags = 0;
((LPNEWCPLINFO)lParam2)->dwHelpContext = 0;
((LPNEWCPLINFO)lParam2)->lData = 0;
((LPNEWCPLINFO)lParam2)->hIcon = LoadIcon(theApp.m_hInstance, _T("IDI_ICON2"));
_tcscpy(((LPNEWCPLINFO)lParam2)->szName, _T("CPLTest NI"));
_tcscpy(((LPNEWCPLINFO)lParam2)->szInfo, _T("Contron Panel Test Project of NI"));
_tcscpy(((LPNEWCPLINFO)lParam2)->szHelpFile, _T(""));
result = 0;
break;
case CPL_DBLCLK:
m_debug << "CPL_DBLCLK(" << (UINT)lParam1 << ")(" << LONG(lParam2) << ")" << endl;
result = 0;
break;
case CPL_STOP:
m_debug << "CPL_STOP(" << (UINT)lParam1 << ")" << endl;
result = 0;
break;
case CPL_STARTWPARMS:
m_debug << "CPL_STARTPARMS(" << (UINT)lParam1 << ")(" << (LPCTSTR)lParam2 << ")" << endl;
result = TRUE;
break;
case CPL_SETUP:
m_debug << "CPL_SETUP" << endl;
result = 0;
break;
case CPL_EXIT:
m_debug << "CPL_EXIT" << endl;
result = 0;
break;
default:
m_debug << "UNKNOWN" << endl;
break;
}
m_debug.close();
return result;
}
非常に汚いプログラムではありますけど、このようなプログラムを組んでメッセージの流れを調べてみたところ、次のような結果になりました。
ちなみに実行は、出来上がった DLL ファイルの拡張子を CPL に変更してダブルクリックしてみたものです。もちろん、Visual C++ が生成した .DEF ファイルの末尾に CPlApplet @1 という行を追加して、CPlApplet 関数をエントリしてあります。
CPL_INIT
CPL_GETCOUNT
CPL_INQUIRE(0)
CPL_NEWINQUIRE(0)
CPL_INQUIRE(1)
CPL_NEWINQUIRE(1)
CPL_INQUIRE(2)
CPL_NEWINQUIRE(2)
CPL_STOP(0)
CPL_STOP(1)
CPL_STOP(2)
CPL_EXIT
このような順番でメッセージが流れていくようです。
とりあえず、大体はいい感じですね。
そのままシステムフォルダへ投げ込んでもちゃんとアイコンは表示されるし…。あとは、CPL_DBLCLK でダイアログを表示させればよさそうです。
ただ、上記のリストを見ると CPL_DBLCLK が呼び出されていないような…。
まぁ、まだ完全な調査とはいえませんけど、コントロールパネル製作の実現まではたどり着けそうな感じなので、とりあえずよしとしましょう。