プラグインにメニュー操作を実装するためにRegnessemのサービスの作り方を説明します。
予備知識として、D言語での文字コードについて説明します。
DのソースファイルはUTF-8で記述する必要があります。なぜならchar
型はUTF-8、wchar
型はUTF-16として扱っているからです。Cのソースファイルは対象のプラットフォームに依存した文字コードを与える方法と、ワイド文字を使う方法があります。単純にアルファベットを扱う場合はUTF-8とASCIIコードは互換性があるので問題ないのですが、例えば日本語を扱おうとすると日本語WindowsではShift_JISが対象になります。
このことからソースファイルに例えば日本語を書き込んだ場合、CとDでは次のような違いが出てしまいます。
char foo[] = "あ"; // C言語の場合 foo = { 0x82, 0xa0, '\0'} charは1バイト(Shift JIS)
char foo[] = "あ"; // D言語の場合 foo = { 0x0e3820 } UTF-8形式1文字
これを考えずに、Shift_JISのままソースに記述してコンパイルすると、当然日本語がエンコードの違いで文字化けを起こしてしまいます。DにはWYSIWYG文字列を扱うこともできますが、どうやら最新版のコンパイラではソースファイル自体がUnicodeで書かれていないとコンパイルエラーを起こすようです。ですからxyzzy等のUnicodeに対応したエディタでソースを編集して下さい。
それでは今回のファイル構成です。wstring.dというファイルが増えています。
C:\Programs +-aam +-nsmsgs | +-consts.d | +-types.d +-aam.d +-wstring.d +-aam.def
今回新たに作成するwstring.dは、wchar
型をサポートするtoStringz
のようなものを実装します。前回説明したようにPhobosにはstd.stringでC言語風文字列へ変換するtoStringz
がありましたが、残念ながらこれはwchar
型に対応していません。そこでPhobosのstd.stringを参考にtoStringz
もどきを作成します。
module wstring;
wchar* toWStringz(wchar[] string)
{
wchar* p;
wchar[] copy;
if (string.length == 0) {
return "";
}
p = &string[0] + string.length;
if (*p == 0) {
return string;
}
copy = new wchar[string.length + 1];
copy[0..string.length] = string;
copy[string.length] = '\0';
return copy;
}
この関数はwchar
型の配列を引数にもち、wchar
型のポインタを返す関数です。引数の文字列の長さが0であれば空文字を返します。次に引数の文字列の最後が0であるか、すなわち既に Null Terminate であるかを調べ、そうであればそのまま返します。そして最後に、元の文字列の長さよりも1文字多い配列を確保し、そこへ元の文字列をコピーして終端に\0を入れて返します。
wchar[] foo = "あいうえお";
wchar* bar = toWStringz(foo); // bar = "あいうえお\0"
今までC++をあつかったことがある人は少し違和感を感じるかもしれません。何故ならnew
でメモリを確保しているにも関わらずdelete
していないからです。これはDがGCをサポートしているために、明示的にdelete
しなくとも、どこからも参照される事がなくなった時点で自動的にメモリを解放してくれるからです。これによりメモリリークの心配が格段に減ります。
プラグインAPI仕様書を読むと、アドインモジュールはシステムが認識できる4つのサービスを提供することができます。
AddIn/%name%/UIService/Main/%s
AddIn/%name%/UIService/Member/%s
AddIn/%name%/UIService/Group/%s
AddIn/%name%/UIService/Tab/%s
このサービスのうち、メインメニューを扱うのはAddIn/%name%/UIService/Main/%s
です。これを今回のプラグインに適用するとなると、まず%name%
はプラグインの名前であるaam
になります。最後の%s
は任意文字列です。%s
には切り替えの意味でSwitch
と付けることにしましょう。
private const char[] NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH = "AddIn/aam/UIService/Main/Switch";
それでは今回のメインソースです。前回から変更があった部分を色づけしてあります。
module aam;
private {
import win32.ansi.windows;
import nsmsgs.consts, nsmsgs.types;
import wstring;
import std.string;
}
extern (C) {
void gc_init();
void gc_term();
void _minit();
void _moduleCtor();
void _moduleUnitTests();
}
private {
const char[][] PluginInfo = [
NSM_API_VERSION, // API Version
"AddIn/aam", // Module Name
"An Answering Machine on Regnessem", // Plugin Title
"An Answering Machine on Regnessem", // Description
"Moon", // Author
"Copyright (c) 2004 Moon", // Copyright
"0.0.2", // Version
];
const wchar[][] MainMenu = [
"留守番機能 ON",
"留守番機能 OFF",
];
static bool MenuStatus;
}
private const char[] NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH = "AddIn/aam/UIService/Main/Switch";
extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
switch (ulReason) {
case DLL_PROCESS_ATTACH:
gc_init(); // init GC
_minit(); // init module list
_moduleCtor(); // run module constructor
_moduleUnitTests(); // run unit test
break;
case DLL_PROCESS_DETACH:
gc_term();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
// multi-thread is not supported
return FALSE;
}
return TRUE;
}
// DLL Export Functions
extern (Windows)
int GetPluginInfo(int nInfoNo, LPTSTR lpBuffer, int nSize)
{
if (nInfoNo < 0 || nInfoNo > PluginInfo.length) {
// Unknown Information Number
return 0;
} else {
lstrcpyn(lpBuffer, toStringz(PluginInfo[nInfoNo]), nSize);
return lstrlen(lpBuffer);
}
}
extern (Windows)
int Initialize(PNsmPluginInitInfo lpInitInfo)
{
// Create Plugin Services
lpInitInfo.CreateService(toStringz(NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH), &DoSwitch);
return 0;
}
extern (Windows)
int Terminate()
{
return 0;
}
// Services
extern (Windows)
int DoSwitch(WPARAM wParam, LPARAM lParam)
{
HNsmConnection hConnection = cast(HNsmConnection) wParam;
PNsmUIServiceInfo lpUISvcInfo = cast(PNsmUIServiceInfo) lParam;
switch (lpUISvcInfo.nInfoKey) {
case NMUI_GETCAPTION:
lstrcpynW(
cast(wchar*) lpUISvcInfo.lpInfo.lpBuffer,
toWStringz(MainMenu[(MenuStatus ? 1 : 0)]),
lpUISvcInfo.lpInfo.nBufferSize);
break;
case NMUI_ONCLICK:
MenuStatus = !MenuStatus;
break;
default:
// Unknown Type
return 0;
}
return 1;
}
extern (Windows)
int Initialize(PNsmPluginInitInfo lpInitInfo)
{
// Create Plugin Services
lpInitInfo.CreateService(toStringz(NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH), &DoSwitch);
return 0;
}
プラグインを初期化するために呼ばれるInitialize
関数が変更になっています。Initialize
関数の引数PNsmPluginInitInfo
はTNsmInitInfo
構造体のポインタです。ここにはシステム関数のポインタが格納されています。ここからシステム関数CreateService
を呼び出してサービスを作成します。
CreateService
関数の第一引数にはサービス名の文字列を、第二引数には実際に処理を行うサービス関数へのポインタを与えます。
今回、作成するサービス名はAddIn/aam/UIService/Main/Switch
で、それを処理する関数がDoSwitch
関数なのでその関数へのポインタを与えています。
extern (Windows)
int DoSwitch(WPARAM wParam, LPARAM lParam)
{
HNsmConnection hConnection = cast(HNsmConnection) wParam;
PNsmUIServiceInfo lpUISvcInfo = cast(PNsmUIServiceInfo) lParam;
switch (lpUISvcInfo.nInfoKey) {
case NMUI_GETCAPTION:
lstrcpynW(
cast(wchar*) lpUISvcInfo.lpInfo.lpBuffer,
toWStringz(MainMenu[(MenuStatus ? 1 : 0)]),
lpUISvcInfo.lpInfo.nBufferSize);
break;
case NMUI_ONCLICK:
MenuStatus = !MenuStatus;
break;
default:
// Unknown Type
return 0;
}
return 1;
}
AddIn/aam/UIService/Main/Switch
サービスが呼ばれた時に処理する関数です。
AddIn/%name%/UIService/Main/%s
サービスの引数は第一引数がHNsmConnection
(コネクションハンドル)、第二引数がTNsmUIServiceInfo
構造体へのポインタです。そこで、呼び出されたときにまず型キャストを行っています。基本的にWPARAM,LPARAM
で呼び出されますのでそのままでは扱えないためです。
このサービスが呼ばれるときには、TNsmUIServiceInfo
構造体のnInfoKey
メンバに呼び出し理由が入っています。APIバージョン0.2.3では次の呼び出し理由があります。
値 | 定数 | 意味 |
---|---|---|
0 | NMUI_GETCAPTION | メニューに表示する項目の取得 |
1 | NMUI_ONCLICK | メニューが選択された |
システムからキャプションを取得しようとして呼ばれた時の処理が以下です。
case NMUI_GETCAPTION:
lstrcpynW(
cast(wchar*) lpUISvcInfo.lpInfo.lpBuffer,
toWStringz(MainMenu[(MenuStatus ? 1 : 0)]),
lpUISvcInfo.lpInfo.nBufferSize);
break;
lstrcpynW
は前回のlstrcpyn
関数のワイド文字版です。TNsmUIServiceInfo
のメンバlpInfo
のメンバlpBuffer
には予め領域がnBufferSize
分だけ確保されているのでそこにコピーします。
RegnessemのWideString型はUTF-16となっています。これは丁度Dのwchar
型がUTF-16なのでそのままコピーしてやればいいのです。ソースファイルはUTF-8で記述しましたが、wchar
型への代入は自動的にUTF-16にコンパイラが変更してくれます。ですので次のコード自体はUTF-8でも変数の中身はUTF-16であることが保証されます。
const wchar[][] MainMenu = [
"留守番機能 ON", // UTF-8のソースだけどwchar[]に代入された時点でUTF-16
"留守番機能 OFF",
];
ここで気になるのはMenuStatus ? 1 : 0
です。Cのプログラムを書くときに口を酸っぱくして変数は初期化しなさいと言われ続けた事とは思いますが、このソース中ではどこにもMenuStatus
変数を初期化していません。これは、Dでは変数が宣言されたときにデフォルトで初期化される値が決まっているからです。これにより初期化忘れを防ぐことが出来ます。
次にシステムがメニューをクリックしたときに呼び出される処理が以下です。
case NMUI_ONCLICK:
MenuStatus = !MenuStatus;
break;
MenuStatus
の値を反転させているだけです。ですので、最初に呼ばれたときはfalse
からtrue
に、次はその逆に…と延々繰り返すだけです。この度にシステムはまたNMUI_GETCAPTION
でこのサービスを呼び出すのでメニューの文字列が選ばれるたびに文字列が変更されるわけです。
それでは今回のファイルをコンパイルしましょう。
C:\>cd \Programs\aam C:\Programs\aam>dmd -v aam.d aam.def wstring.d nsmsgs\consts.d nsmsgs\types.d win32a.lib parse aam parse wstring parse consts parse types semantic aam semantic wstring semantic consts semantic types semantic2 aam semantic2 wstring semantic2 consts semantic2 types semantic3 aam semantic3 wstring semantic3 consts semantic3 types code aam generating code for function 'DllMain' generating code for function 'GetPluginInfo' generating code for function 'Initialize' generating code for function 'Terminate' generating code for function 'DoSwitch' code wstring generating code for function 'toWStringz' code consts code types C:\dmd\bin\..\..\dm\bin\link.exe aam+wstring+consts+types,,,win32a.lib+user32+kernel32,aam.def/noi; C:\Programs\aam>
そして出来たファイルをRegnessemのPluginsフォルダにコピーして起動すればきちんとメニューに項目が追加され、ON/OFFの切り替えが出来るようになっていると思います。