イベントを捕まえる

システムによって通知されるイベントをフックする事によって、受信したメッセージを取得します。

予備知識

文字コード

以前にも書きましたが、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付きの関数もあります。ただしこれはtoUTF16ztoUTF32zのみで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)を持ちます。

aam.d

では今回のメインソースです。変更点は色づけしてあります。


    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;
    }
    
  

Initialize


    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へのポインタを渡しています。

Terminate


    extern (Windows)
    int Terminate()
    {
        // Unhook Events
        PluginInitInfo.UnhookEvent(toStringz(NME_SYSTEM_SESSION_RECEIVEMESSAGE), &OnReceiveMessage);
        return 0;
    }
  

プラグインがアンロードされるとき、プラグインはフックしたイベントを解放しなくてはいけません。そこでUnhookEvent関数を使います。Initialize関数でシステム関数へのポインタを納めたTNsmPluginInitInfoを保存してあるのでそこから呼び出します。

UnhookEventは第一引数にフックしたイベント名、第二引数に処理していた関数のポインタを渡します。これはInitialize関数でHookEventしたものと同じものです。

OnReceiveMessage


    // 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にして、メッセージを受信するとそれを表示するメッセージボックスが表示されます。

Download

参考リンク

Copyright © 2004 Moon moon@users.sourceforge.jp