システムによって通知されるイベントをフックする事によって、受信したメッセージを取得します。
以前にも書きましたが、Dのchar
型はUTF-8、wchar
型はUTF-16です。それを相互変換するためにPhobosにはstd.utfモジュールがあります。Win32環境で、Cのwchar_t
型はUTF-16になり、Dのwchar
型に丁度対応します。Linux環境でのCのwchar_t
型はUTF-32となりDのdchar
型に対応します。
更にC言語文字列(null terminated)に一緒に変換してくれるz付きの関数もあります。ただしこれはtoUTF16z
とtoUTF32z
のみでtoUTF8z
はありません。
char[] utf8 = "abc"; // UTF-8
wchar[] utf16 = "abc"; // UTF-16
dchar[] utf32 = "abc"; // UTF-32
char[] conv_utf8 = toUTF8(utf16); // 引数はwchar[]でもdchar[]でもOK
wchar[] conv_utf16 = toUTF16(utf8); // 引数はchar[]でもdchar[]でもOK
dchar[] conv_utf32 = toUTF32(utf16); // 引数はchar[]でもwchar[]でもOK
wchar[] c_conv_utf16 = toUTF16z(utf16); // 最後に\0を付与したUTF-16文字列に変換
Regnessemではプロトコルプラグインによって提供される接続をコネクション、プロトコルプラグインによって提供される会話をセッションと呼びます。
メインウィンドウに表示されるメンバリストがコネクション、会話ウィンドウがセッションとなり、それぞれ一意のコネクションハンドル(HNsmConnection
)とセッションハンドル(HNsmSession
)を持ちます。
では今回のメインソースです。変更点は色づけしてあります。
module aam;
private {
import win32.ansi.windows;
import nsmsgs.consts, nsmsgs.types;
import wstring;
import std.string, std.utf;
}
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.3", // Version
];
const wchar[][] MainMenu = [
"留守番機能 ON",
"留守番機能 OFF",
];
static bool MenuStatus;
TNsmPluginInitInfo PluginInitInfo;
}
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)
{
PluginInitInfo = *lpInitInfo;
// Create Plugin Services
PluginInitInfo.CreateService(toStringz(NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH), &DoSwitch);
// Hool Events
PluginInitInfo.HookEvent(toStringz(NME_SYSTEM_SESSION_RECEIVEMESSAGE), &OnReceiveMessage);
return 0;
}
extern (Windows)
int Terminate()
{
// Unhook Events
PluginInitInfo.UnhookEvent(toStringz(NME_SYSTEM_SESSION_RECEIVEMESSAGE), &OnReceiveMessage);
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;
}
// Event
extern (Windows)
int OnReceiveMessage(WPARAM wParam, LPARAM lParam)
{
HNsmSession hSession = cast(HNsmSession) wParam;
PMessageInfo lpMesInfo = cast(PMessageInfo) lParam;
if (MenuStatus) {
char[] from = toString(lpMesInfo.lpFrom); // for D-String
wchar[] wfrom = toUTF16z(from);
MessageBoxW(null, lpMesInfo.lpBody, wfrom, MB_OK);
}
return 0;
}
extern (Windows)
int Initialize(PNsmPluginInitInfo lpInitInfo)
{
PluginInitInfo = *lpInitInfo;
// Create Plugin Services
PluginInitInfo.CreateService(toStringz(NMS_ADDIN_AAM_UISERVICE_MAIN_SWITCH), &DoSwitch);
// Hook Events
PluginInitInfo.HookEvent(toStringz(NME_SYSTEM_SESSION_RECEIVEMESSAGE), &OnReceiveMessage);
return 0;
}
前回は渡された引数をそのまま使ってサービスを作りましたが、TNsmPluginInitInfo構造体は後々必要になるので一度保存しています。
そして今回はイベントをフックするのでHookEvent
関数を使います。これは第一引数にフックしたいイベント名、第二引数に処理する関数へのポインタを渡します。
フックしたいのはメッセージを受信したときです。そこでAPI仕様書を読むと、System/Session/OnReceiveMessage
というイベントが見つかります。これはnsmsgs.constsモジュールでNME_SYSTEM_SESSION_RECEIVEMESSAGE
で定義されているので、これを第一引数に渡し、それを処理する関数OnReceivedMessage
へのポインタを渡しています。
extern (Windows)
int Terminate()
{
// Unhook Events
PluginInitInfo.UnhookEvent(toStringz(NME_SYSTEM_SESSION_RECEIVEMESSAGE), &OnReceiveMessage);
return 0;
}
プラグインがアンロードされるとき、プラグインはフックしたイベントを解放しなくてはいけません。そこでUnhookEvent
関数を使います。Initialize
関数でシステム関数へのポインタを納めたTNsmPluginInitInfo
を保存してあるのでそこから呼び出します。
UnhookEvent
は第一引数にフックしたイベント名、第二引数に処理していた関数のポインタを渡します。これはInitialize
関数でHookEvent
したものと同じものです。
// Event
extern (Windows)
int OnReceiveMessage(WPARAM wParam, LPARAM lParam)
{
HNsmSession hSession = cast(HNsmSession) wParam;
PMessageInfo lpMesInfo = cast(PMessageInfo) lParam;
if (MenuStatus) {
char[] from = toString(lpMesInfo.lpFrom); // for D-String
MessageBoxW(null, lpMesInfo.lpBody, toUTF16z(from), MB_OK);
}
return 0;
}
System/Session/OnReceiveMessage
は第一引数にHNsmSession
(セッションハンドル)、第二引数にTMessageInfo
構造体へのポインタを渡します。そこでまず最初に型キャストし、保存しています。
次に留守番機能がONのとき、受信したメッセージを、アカウントがタイトルのメッセージボックスに表示します。まず、渡されたTMessageInfo
構造体のlpFrom
(アカウント名)にはC言語文字列が入っているので、それを一度D言語文字列に変換します。C言語の文字列はchar*
であり、D言語の文字列はchar[]
のため、そのままでは使えないからです。D言語文字列への変換には、toString
関数を使います。これはstd.stringで定義されています。
MessageBox
関数はWin32APIです。ワイド文字版なので最後にWを付け、MessageBoxW
となります。第一引数に親ウィンドウのハンドル、第二引数に表示内容、第三引数にタイトル、第四引数にボタンの種類を入れます。MB_OK
はOKボタンのみを持ちます。他にもMB_YESNO
等の定数を指定できます。
// 非UnicodeならAscii版、UnicodeならWideString版を呼ぶ
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); // Ascii版
int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); // WideString版
この引数の型LPCTSTR
はC言語文字列のことで、UNICODE環境であればLPCWSTR
となります。LPCWSTR
は\0
で終わるwchar_t
型へのポインタ、即ちワイド文字列となります。上記はC環境の場合の関数定義なので、Dの場合は以下のようになります。
int MessageBoxW(HWND hWnd, wchar* lpText, wchar* lpCaption, UINT uType);
ではいつものようにコンパイル。
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'
generating code for function 'OnReceiveMessage'
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>
できたDLLをRegnessemのPluginsフォルダにコピーし、起動します。
メニューから留守番機能をONにして、メッセージを受信するとそれを表示するメッセージボックスが表示されます。