eyecatch_develop

External IME Controler

動作環境

  • Windows95日本語版
  • Windows98日本語版(Secondeditionを含む)
  • WindowsMe日本語版
  • WindowsNT4.0(Workstation,Server)日本語版
  • Windows2000(Professional,Server)日本語版

IMEは最新のパッチを当てたものを使用してください。IMEのバージョンがIMEVER_0400以降のものが対象となります。すべてのIMEおよびOS上で動作する保証はできかねます。

グローバルフックやIME操作、DLLの作成、プロセス制御、OS切り分け、DLL遅延ロード、DLL動的ロード等の参考としての教材にもご利用ください。


External IME Controler var 1.1.1.0 ダウンロード

vectorからもダウンロードできます。Vector

http://www.vector.co.jp/soft/win95/prog/se212934.html

更新履歴

version1.0.0.0 2001/09/14 正式公開

version1.1.0.0 2002/02/25

  • 95系の不備(GETMESSAGEフックの入れ忘れ)に対処
  • 変更済みメッセージ通知
  • Automation対応
  • その他コードを若干きれいに修正

version1.1.1.0 2003/04/11

  • Visual Studio .NET(VC++7)に対応

FAQ

Q1 IMEのモードが変わりません

A1 IMEを変更したいアプリケーションをアクティブにして入力可能な状態にしてください。


Q2 95でIMEのモードが変わりません

A2 95系(Win95,Win98,WinMe,NT3.51)ではWH_CALLWNDPROCRETフックでも呼び出した側のプロセスで処理されちゃうので、WH_GETMESSAGEフックを使用します。この場合Postでなければいけないので、IME変更要求のメッセージがキューに入って処理されるまでちょっと時間がかかってしまいます。なのでfImeChange()のあとすぐにvControlStop()をしてしまうと、変更する前に処理不可にされてしまうのです。

fControlStart()をアプリケーション起動時に呼び、終了される時にvControlStop()するのがいちばん簡単な方法となるでしょう。

著作権/転載など

著作権は株式会社ゼロが保持します。本ソフトウェアは日本国の著作権法、並びに国際条約に保護されています。

使用、転載、及び配布は自由です。配布の際はパックされた内容の変更のないようにお願いします。転載の際は、事前にメールを送付してください。

このプログラムの使用によって生じた損害等については作者は何も保証する義務を負いません。

サポート

電子メールによる質問は受け付けますが、仕事の具合によって返信はすぐに行えない場合もありますのでご了承ください。

ご要望があればカスタマイズもお受けしています。

再利用について

商用/非商用に関わらず、ソースを再利用することができます。ただし、再利用する場合は、ドキュメント,バージョン情報などに次の文を記載してください。

External IME Controler Copyright(c) 2000-2003 ZERO Corp.

技術解説

動作原理

IMEを制御するにはAPIのImm****系のものを使用しますが別プロセスの場合それで直接IMEを制御することはできません。

IMEは各アプリケーション毎に設定を持ちます。アプリケーションがアクティブになったときにプロセスにIME入力コンテキストをセットします。

IMEを制御するにはIMEの入力コンテキストを得てそのハンドルで制御をしなければなりません。しかし入力コンテキストはそのプロセスからは取得できますがほかのプロセスからは取得することはできません。

つまりほかのプロセスからはどうあがいても他のアプリのIMEを制御することは不可能なわけです。


しかしひとつ発想を転換してみればうまくいきそうな方法が思い浮かばないでしょうか。グローバルフックはぞれぞれのプロセスにアタッチされます。このフック内であれば別のプロセスの操作ができるのでないでしょうか。このプログラムはこの根本原理によって動作しているのです。


グローバルフックは全てにアタッチしますが、操作したいプロセスを特定するにはどうするべきでしょうか。これはユーザ定義メッセージを使用すれば解決します。WH_CALLWNDPROCRETフックならばユーザー定義メッセージを監視することが可能です。ということは操作したいアプリケーションにユーザー定義メッセージを送れば、フックにかかります。メッセージはそのアプリに対して送ったのですから、フックは操作したいアプリケーションのプロセス内であるわけです。その時ならそのアプリの入力コンテキストを得ることができます。つまりその操作したいアプリでIMEが操作できるというわけです。


WH_CALLWNDPROCRETフックをかけておきユーザー定義メッセージをSendMessageで処理された後を監視します。操作したいアプリケーションのウィンドウにユーザー定義メッセージをSendすればフックに引っかかります。しかしこれは95/98などではうまくいきません。本来はユーザー定義メッセージをフッキングしたときフックにアタッチしているプロセスが送られた側でなければならないのに送り元となっているようです。回避策としてWH_GETMESSAGEを併用しユーザー定義メッセージをPostMessageでおくります。

逆に監視をするには

逆の発想をすればIME状態を監視することも可能なはずです。

.
.
#pragma data_seg("ImeCheckshr")
HHOOK g_hHook = NULL;
HWND g_hwnd = NULL;
BOOL g_fOpen = FALSE;
#pragma data_seg()
// リンカディレクティブを埋め込み
#pragma data_seg(".drectve")
    static char szLinkDirectiveShared[] = "-section:ImeCheckshr,rws";
#pragma data_seg()
.
.
HINSTANCE g_hDLLInst;
BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
    switch (ul_reason_for_call){
        case DLL_PROCESS_ATTACH:
            g_hDLLInst = hModule;
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
extern "C" void MSGHOOK_API vSetMsgHookPostWindow(HWND hwnd)
{
    g_hwnd = hwnd;
}
extern "C" BOOL MSGHOOK_API fMsgHook(void)
{
    if( g_hHook ){
        vMsgUnhook();
    }
    g_hHook = ::SetWindowsHookEx( WH_CALLWNDPROCRET, lresGetMsgProc, g_hDLLInst, 0);
    if( !g_hHook ){
        return FALSE;
    }
    return TRUE;
}
extern "C" void MSGHOOK_API vMsgUnhook(void)
{
    ::UnhookWindowsHookEx(g_hHook);
    g_hHook = NULL;
}
LRESULT CALLBACK lresGetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    CWPRETSTRUCT* pcwp = (CWPRETSTRUCT*)lParam;

    if( (nCode >= 0) && (nCode == HC_ACTION) ){
        // IME通知メッセージ
        if( pcwp->message == WM_IME_NOTIFY ) {
            if( pcwp->wParam == IMN_SETOPENSTATUS || pcwp->wParam == IMN_OPENSTATUSWINDOW ){
                HIMC himc = ::ImmGetContext( pcwp->hwnd );
                if( himc ){
                    // 監視しているのはオープン状態のみです
                    BOOL fNowOpen = ::ImmGetOpenStatus(himc);
                    ::ImmReleaseContext( pcwp->hwnd, himc );
                    if( g_fOpen != fNowOpen ){
                        if( g_hwnd )    ::PostMessage(g_hwnd, c_uImeChangeMsg, fNowOpen, 0L);
                        g_fOpen = fNowOpen;
                    }
                }
            }
        }
    }
    return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
        

WH_CALLWNDPROCRETフックをかけておきWM_IME_NOTIFYメッセージをSendMessageで処理された後を監視します。

IMN_SETOPENSTATUSかIMN_OPENSTATUSWINDOWのときにImmGetOpenStatusでオープンしているかどうかをチェックしそれを自分のウィンドウにポストするようにしておくのです。

すべてのアプリからのON/OFF状態を受け取ることになり現在の状態を把握することが可能になります。

遅延ロードの説明

テスト用のプログラムの中にprocess.cppがありますがこのソースの中で使用されています。なぜか非常にマイナーです。


ロードをさせたくない場合に一般に使われるのはLoadLibralyを使った動的ロードという方法ですが、いちいち関数のtypedefなんかを定義するのは非常に面倒です。実行時までライブラリ名が分からないならともかく単にロードをさせないがために動的ロードをするのは面倒という方にはぴったりの方法です。


まず遅延ロード用のヘッダをインクルードします。

        #include <Delayimp.h> // For error handling & advanced features
        

そして遅延ロードのインポートライブラリをリンクします。

        // Statically link __delayLoadHelper/__FUnloadDelayLoadedDLL
        #pragma comment(lib, "Delayimp.lib")
        

遅延ロードしたいDLLを指定します。

        // Tell the linker that my DLL should be delay loaded
        #pragma comment(linker, "/DelayLoad:NTExcl.dll")
        #pragma comment(linker, "/DelayLoad:95Excl.dll")
        // Tell the linker that I want to be able to unload by DLL
        #pragma comment(linker, "/Delay:unload")
        // Tell the linker to make the delay load DLL unbindable
        // You usually want this, so I commented out this line
        //#pragma comment(linker, "/Delay:nobind")
        

こうしておけばそのOSのDLLにしかないAPIを使おうとも実際に使うまではロードされませんからエラーが出ないというわけです。(普通にlibをインポートしてやった場合は当然ながらアプリを起動した時点でエラーになります。)

これを使えばわざわざ動的ロードを使わずとも各OS専用のところを切り分けることが可能となります。


ロードしてしまった時にまたアンロードするにはこのようにします。

        // Unload the delay-loaded DLL
        // NOTE: Name must exactly match /DelayLoad:(DllName)
        __FUnloadDelayLoadedDLL("95Excl.dll");
        

(注)VC++7では
ヘルプ「リンカによる DLL の遅延読み込み」を参照してください。

#pragma comment(linker, “/****)という形もサポートされなくなっています。(WarningLNK4229で無視されます)

__FUnloadDelayLoadedDLLは__FUnloadDelayLoadedDLL2になっています。

ヘルプ「Visual C++ 6.0 以降の DLL 遅延読み込みヘルパ関数の変更点」を参照してください。

VC6とVC7を同時にサポートするには_MSC_VERがVC7は1300なのでそれで判断します。

VC7ではリンカの/DELAYLOADにて設定するか、コードからだとput_DelayLoadDLLs()を使うしかありません。

コードの説明1(receiver)

解説をはじめる前に、このExternal IME Controlerのプロジェクトは職業プログラマとしての適切と思われるコーディングをやっています。その点をふまえてコードを参考にしてもらいたいと思います。特に重要なところは赤く強調しています。

まず、すべてのコードはハンガリアン記法をさらに拡張して書かれています。(ハンガリアン記法など使わなくてもわかりやすくかけるとか、変数名が短くてもわかればいいんだいう方もいるでしょうが、「仕事として」やるかぎりそういう独りよがりな事ではいけません。基本的に自分のためにやっているので、議論としてどうのこうのと言う意見は受け付けません。変数名などはできるだけわかりやすく書くに越したことはありません。ほかの人がコードを見なければならなくなることも多いですし、自分で保守し続けていくにしてもいつまでもやったことを覚えてなどいられないのですから。)基本的には変数は型を示したプレフィックスにわかりやすく内容を表す名称をつけたものになっています。拡張しているのは、型のプレフィックスは重ならない使い方をしています。たとえばbはbyteとBOOLとに使われますが、byteはb、BOOLはfにするというようなことです。もうひとつは関数名にも戻り値のプレフィックスを必ず付けています。戻り値が何かが一発でわかりますし、違う型の変数に入れることもなくなります。

また関数ヘッダやコメントをきっちり書いています。人が見てもすぐにわかるように、自分も忘れないようにしっかり書いておくべきです。

このページにはソースコードを書き出していますし、ネット上にもいろんなサンプルコードがありますが、コードはそのまま写して何も考えないで使うのだけはやめましょう。どういうことをしているのか理解して使わないとそのうちにひどい目にあうでしょう。トラブルに巻き込まれるのは自分です。(技術者の方はCプログラミング診断室を一読してみることをお勧めします。)

講釈はさておき本題に進みましょう

このモジュールはグローバルフック用のDLLです。これがすべてのプロセスにアタッチするのが目的であるため、軽くて必要な処理のみを行うだけのものとなっています。フックでは出来だけ速く処理を終えて次のフックに渡してしまうのがベストです。


このプロジェクトではMFCは使用していません。普通のSDKのDLLの組み方です。


処理はreceiver.cppに集約されています、といってもたいしたことはやってませんから当然ですが。

misc,ImeOpr,LogFlなとのモジュールは動作実績がある汎用的なクラス群です。こういうクラスを書きためていけばほかの仕事でもそのまま再利用ができて工期の短縮が可能です。


ヘッダがreceiver.hとRCVRAPI.hとありますが、これはどういうことでしょうか。receiver.hはreceiver.cppのヘッダとして必要なものをきってありますが、DLLとして使われるのですから使用する側にエクスポートされる関数のプロトタイプや必要な宣言を書いたヘッダが当然必要となりますので、それをこのヘッダに書いてしまっても問題ないわけです。

しかしそれでは使う側には必要ない宣言などもかかれていることになり、それでいらないトラブルを招くことがあります。できるだけトラブルを避けるように避けるようにと措置をするというのは非常に重要なことです。

DLLを使う側にはAPIヘッダとして、それを使うために必要なものだけを書いたものを提供するべきです。というわけでAPIヘッダがRCVRAPI.hとして提供されており使う側はこれだけをインクルードし、receiver.hにはそれ以外のものがかかれているわけでDLL側はこの両方をインクルードするのです。


receiver.h

#ifndefと#defineで2重インクルードを防ぐのは、慣れている人ならもうあたりまえのように付けていることでしょう。

結果的にこのヘッダにはプロトタイプ宣言しかありません。fIsSendNsgTargetProcessAtattch()は完全なローカル関数なのでstaticとします。

#ifndef __RECEIVER_H
#define __RECEIVER_H

// プロトタイプ
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);
static BOOL fIsSendNsgTargetProcessAtattch(void);

LRESULT CALLBACK lresGetMsgProc1(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK lresGetMsgProc2(int nCode, WPARAM wParam, LPARAM lParam);


#endif /* !__RECEIVER_H */
        

receiver.cpp

ではコードの説明に入ります。まずpragmaで専用のセクションを定義していますが

#pragma data_seg("ExImeShr")
HHOOK g_hHook1 = NULL;
HHOOK g_hHook2 = NULL;
HWND g_hwnd = NULL;
UINT g_uImeChangeMsg = 0;
UINT g_uImeChangedMsg = 0;
#pragma data_seg()
// リンカディレクティブを埋め込み
#pragma data_seg(".drectve")
    static char szLinkDirectiveShared[] = "-section:ExImeShr,rws";
#pragma data_seg()
        

これはなぜ必要なのでしょうか。今回これはローカルフックではなくグローバルフックにしなければなりません。つまりプロセスにアタッチした際にプロセスごとに違う値になってしまっては困るものがあります。たとえばフックハンドルが別々の値になってしまうと当然別々のフックができてしまうことになるのでだめなのがわかるでしょう。システム全体で唯一ひとつでありそれをみんなが使うからこそグローバルフックなのです。

フックハンドルはすべてのプロセスが共有しており、すべてのプロセスが読み書きが可能でなくてはならないということです。べつにプロセス間で変数を共有できるのなら共有メモリでもなんでもいいですが、大掛かりな事をしなくてもプロセス間で共有できる方法がこの固有のセクションを使用する方法です。(詳しく知りたい方はADVANCED WINDOWSを読んでみて下さい)この方法を使う場合は必ず変数は初期化しなければならないことに注意してください。

リンカに対して-sectionコマンドを使ってExImeShrセクションをREAD,WRITE,SHAREDに指定することによって共有セクションとしています。


グローバル変数は使いたくありませんが、これだけならとりあえず我慢できます。

HINSTANCE g_hDLLInst = NULL;
        

C++のコンパイラを使っているのですから#defineなどを使わず固定値を宣言しましょう。置き換えではなく値がちゃんと型を持ちますからしっかりコンパイラにエラーを出させることができます。

const PSTR c_pszMsgImeChangeMsg = "WM_IMECHANGEMSG";
const PSTR c_pszMsgImeChangedMsg = "WM_IMECHANGEDMSG";
const UINT c_uImeChangeMessageDefault = WM_USER + 1024;
const UINT c_uImeChangedMessageDefault = WM_USER + 1025;
const PSTR c_pszLogFileName = "c:\\receiver.log";
        

エントリポイントはごく普通のDLLのものです。インスタンスハンドルはフックする際に必要になるので保持しておきます。

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD  ul_reason_for_call, LPVOID /*lpReserved*/)
{
    switch (ul_reason_for_call){
        case DLL_PROCESS_ATTACH:
            g_hDLLInst = hModule;
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
        

DLLがエクスポートする関数はすべてCからもC++からも同じように使えるようにextern”C”宣言をしてあります。RCVR_APIはなにかというとRCVRAPI.hに書いてあります。(これはまんまVCがはきだしたものですが)

DLL側は__declspec(dllexport)宣言を行う必要があり、使う側は__declspec(dllimport)を宣言しなければならないことはご存知だと思います。RECEIVER_EXPORTSを宣言すれば__declspec(dllexport)となりますからDLL側は宣言をいれて、使う側はそのままRCVRAPI.hを使えば自動的に__declspec(dllimport)となるわけです。__declspecをそのまま書くより断然うまい方法ですね。

OSによってWH_CALLWNDPROCRETとWH_GETMESSAGEを切り分けます。(なぜかはFAQを参照)

フックする際にはフックをはずす処理を入れます。既にフックをされていた場合に備えるためです。できる限り起こりえる問題に対処しておきましょう

extern "C" BOOL RCVR_API fMsgHook(void)
{
    if( fIsSendNsgTargetProcessAtattch() ){// NT系の場合
        vMsgUnhook();
        g_hHook1 = ::SetWindowsHookEx( WH_CALLWNDPROCRET, (HOOKPROC)lresGetMsgProc1, g_hDLLInst, 0);
        if( NULL == g_hHook1 ){
            return FALSE;
        }
        return TRUE;
    }
    else{
        vMsgUnhook();
        g_hHook2 = ::SetWindowsHookEx( WH_GETMESSAGE, (HOOKPROC)lresGetMsgProc2, g_hDLLInst, 0);
        if( NULL == g_hHook2 ){
            return FALSE;
        }
        return TRUE;
    }
}
        

フックハンドルがNULLのときはフックされていませんからUnhookWindowsHookExをする必要はありません。もちろんAPIにNULLを渡してしまわない(アンフック処理をさせない)措置でもあります。

extern "C" void RCVR_API vMsgUnhook(void)
{
    if( fIsSendNsgTargetProcessAtattch() ){// NT系の場合
        if( NULL != g_hHook1 ){
            ::UnhookWindowsHookEx(g_hHook1);
        }
        g_hHook1 = NULL;
    }
    else{
        if( NULL != g_hHook2 ){
            ::UnhookWindowsHookEx(g_hHook2);
        }
        g_hHook2 = NULL;
    }
}
        

あらゆるアプリを対象としているためWM_USER値ではなく、より安全なRegisterWindowMessageを使っています。確率は低いですがWM_USER+?がぶつかる可能性を危惧してのことです。RegisterWindowMessageが失敗したときはWM_USER値を使用していますが実際はまず失敗することはないはずです。

extern "C" BOOL RCVR_API fSetUserMessage(UINT *puImeChangeMsgId,UINT *puImeChangedMsgId)
{
    *puImeChangeMsgId = 0;
    g_uImeChangeMsg = ::RegisterWindowMessage(c_pszMsgImeChangeMsg);
    if( 0 == g_uImeChangeMsg ){
        g_uImeChangeMsg = c_uImeChangeMessageDefault;// 登録できなかったときWM_USER固定値を使用する
    }
    *puImeChangeMsgId = g_uImeChangeMsg;

    *puImeChangedMsgId = 0;
    g_uImeChangedMsg = ::RegisterWindowMessage(c_pszMsgImeChangedMsg);
    if( 0 == g_uImeChangedMsg ){
        g_uImeChangedMsg = c_uImeChangedMessageDefault;// 登録できなかったときWM_USER固定値を使用する
    }
    *puImeChangedMsgId = g_uImeChangedMsg;
    return TRUE;
}
        

これはIMEを変更した際にメッセージ送信する相手を保持するためのものです。

extern "C" void RCVR_API vSetNotification(HWND hwnd)
{
    g_hwnndPost = hwnd;
}
        

典型的なフックのコードです。メッセージがきたときにIMEを切り替えるだけです。

フックが正常に動作するように、確実に処理すべきときに処理し、フックの次のチェーンにわたすということをしなければなりません。

きっちり以下のようにAPIの説明どおりにします。グローバルフックではシステム全体に影響を及ぼすのでいいかげんに作るのは禁物です。


nCode パラメータの値が HC_ACTION の場合、このフックプロシージャはメッセージを処理しなければなりません。nCodeパラメータの値が 0 未満の場合、このフックプロシージャはメッセージを処理せずにそのメッセージをCallNextHookEx 関数へ渡し、その関数の戻り値を返さなければなりません。 nCode パラメータの値が 0 以上の場合も、CallNextHookEx 関数を呼び出し、その関数の戻り値を返すことを強く推奨します。CallNextHookEx関数を呼び出さないと、WH_CALLWNDPROCRET フックをインストールしたほかのアプリケーションがフックの通知を受け取れず、誤動作する可能性があります。このフックプロシージャがCallNextHookEx 関数を呼び出さない場合、0 を返すべきです。
LRESULT CALLBACK lresGetMsgProc1(int nCode, WPARAM wParam, LPARAM lParam)
{
    CWPRETSTRUCT* pcwp = (CWPRETSTRUCT*)lParam;

    if( (nCode >= 0) && (nCode == HC_ACTION) ){
        if( 0 != g_uImeChangeMsg ){
            // USERメッセージをトリガーにしてIMEの変更を行う
            if( pcwp->message == g_uImeChangeMsg ) {
                CImeOperate cimopIME;
                if( cimopIME.fSetIMEMode((IMEMODETYPE)pcwp->wParam,(IMEINPUTMODETYPE)pcwp->lParam) ){
                    if( NULL != g_hwnndPost ){
                        ::PostMessage(g_hwnndPost ,g_uImeChangedMsg,0,0L);
                    }
                }
            }
        }
    }
    return ::CallNextHookEx(g_hHook1, nCode, wParam, lParam);
}
LRESULT CALLBACK lresGetMsgProc2(int nCode, WPARAM wParam, LPARAM lParam)
{
    MSG* pmsg = (MSG*)lParam;

    if( (nCode >= 0) && (nCode == HC_ACTION) ){
        // USERメッセージをトリガーにしてIMEの変更を行う
        if( 0 != g_uImeChangeMsg ){
            // USERメッセージをトリガーにしてIMEの変更を行う
            if( pmsg->message == g_uImeChangeMsg ) {
                CImeOperate cimopIME;
                if( cimopIME.fSetIMEMode((IMEMODETYPE)pmsg->wParam,(IMEINPUTMODETYPE)pmsg->lParam) ){
                    if( NULL != g_hwnndPost ){
                        ::PostMessage(g_hwnndPost ,g_uImeChangedMsg,0,0L);
                    }
                }
            }
        }
    }
    return ::CallNextHookEx(g_hHook2, nCode, wParam, lParam);
}
        

コードの説明2(ExImeCtl)

このモジュールはコントロールする側のプロセスが使うためのDLLです。

RcvrApi.hをインクルードしてreceiver.libをリンクしていますので、エクスポートされている関数をそのまま呼び出すことができます。


今度はMFC使用のDLLを使ったやりかたを示しています。

コントロールする側はフック側と違って軽くてシンプルなということは気にしなくてもいいので、便利で早く作成できるというほうに選択眼をおきます。


APIヘッダと内部ヘッダの切り分けは同様にして行います。


MFCはスタティックにリンクします。共有DLLを使うことはまずありません。

ほんのちょっとのサイズやスピードを抑えたところでなんになるというのでしょう。そんなことをしても多くのトラブル種を抱えるだけです。システムDLLがなくても、「確実に動く」という方を選択します。

エクスポートするものも、たとえレギュラーDLLで作らなかったとしても普通のDLLの場合の範疇を超えないもの(つまりクラスとかをエクスポートしない)にした方が無難です。

実際そのようなことをする必要があるのは非常にまれであるはずです。

今回の場合は、使う側がVCだけではなくあらゆるものから使えるという仕様なのですからそもそもできません。

VCが吐き出したメモどおり各関数ではAFX_MANAGE_STATEを呼び出すのを忘れないでください。


コンストラクタでクラスのメンバの初期化を行います。receiverで取得するメッセージIDとフック状態フラグを保持します。

Start,EndとかコメントがありますがこれはMFCの吐き出したものに自分が加えた部分を把握できるようにするためのものです。

CExImeCtlApp::CExImeCtlApp()
{
// Start T.S --------------
    m_uImeChangeMsg = 0;
    m_uImeChangedMsg = 0;
    m_fInit = FALSE;
// End --------------
}
        

デストラクタにいれて必ずフックを解除できるようにします。

// Start T.S --------------
/*virtual*/CExImeCtlApp::~CExImeCtlApp()
{
    // フックをはずす
    vMsgUnhook();
}
// End --------------
        

vMsgUnhook()はたとえフックがかかってなくても呼び出せるように作ったあったことを思い出してください。

ここやデストラクタで呼び出して安全に解除処理を行うことができます。

MFCで作成したものには CExImeCtlApp theApp のようなアプリケーションクラスが宣言されています。

これを最大の寿命範囲のクラスとして唯一グローバルとして存在し、ほかのクラスはメンバなどに宣言していくことになり一切のグローバル変数をなくすことができます。

AfxGetApp()はアプリケーションクラスのポインタをどこからでも得ることができます。たとえこのようなDLLのエクスポート関数からもです。

extern "C" BOOL EXIMECTLAPI fControlStart(void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    CExImeCtlApp* pApp = (CExImeCtlApp*)AfxGetApp();

    pApp->m_fInit = FALSE;
    // 念のためフックをはずす
    vMsgUnhook();

    // ユーザー定義メッセージの登録
    if( (0 == pApp->m_uImeChangeMsg) || (0 == pApp->m_uImeChangedMsg) ){
        if( !fSetUserMessage(&pApp->m_uImeChangeMsg,&pApp->m_uImeChangedMsg) ){
            return FALSE;
        }
    }
    // メッセージフックをかける
    if( !fMsgHook() ){
        return FALSE;
    }
    // OK
    pApp->m_fInit = TRUE;
    return TRUE;
}
        

コードの説明3(imetest)

imetestはExImeCtlの使用方法を示すテストプログラムです。

imetestディレクトリは普通に静的にリンクする方法です。

imetest2はCExtImeクラスを使った(CExtIme.cpp,CExtIme.h)動的ロードの例を示しています。


ExImeCtlのライブラリを提供された人が実際に使う場合の想定をしてDLLというサブディレクトリにExImeCtlのライブラリを置くということにしてみました。

commonには汎用クラス群を置いています。


メインウィンドウはCTrayWnd(TrayWnd.cpp,TrayWnd.h)からの派生クラスとなっています。これはタスクトレイ用のウィンドウのベースとなるクラスです。

これはVisualC++5パワフルテクニック大全集のトレイアイコンのためのCustumAppWizardから生成したものですが、非常に便利なので活用しない手はありません。


fControlStart()はOnCreateで呼んでいますが、NT系(NT4.0,Win2000,Xp)ならIMEを切り替えるときに呼んでvControlStop()しても差し支えありません。(何度も書きますが95系はFAQ参照)


IMEの変更済み通知を受け取るためON_REGISTERED_MESSAGEを使用しています。そのためしょうがなくグローバルg_uImeChangedMsgを使用しています。

g_uImeChangedMsgにはuGetImeChangedMsgId()で得られる通知用ユーザー定義メッセージのID値をセットしそれをハンドリングします。

変更済み通知はvSetImeChangedPostWindow()でセットしたウィンドウに対して送られるので、当然ながらそのウィンドウプロシージャでハンドリングする必要があります。