目次

Text Services Framework

since 2010-03-01

nvdajp pyaa msaa python_windows windows 等に関する調査。

情報収集ふたたび

since 2013-05-22

情報収集したのでメモしておく。

http://msdn.microsoft.com/ja-jp/library/windows/desktop/ms629013%28v=vs.85%29.aspx

GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE

http://msdn.microsoft.com/ja-jp/library/windows/desktop/aa380396%28v=vs.85%29.aspx

TF_SENTENCEMODE_NONE 	0x0000	No information for sentence.
TF_SENTENCEMODE_PLAURALCLAUSE 	0x0001	The IME uses plural clause information to carry out conversion processing.
TF_SENTENCEMODE_SINGLECONVERT 	0x0002	The IME carries out conversion processing in single-character mode.
TF_SENTENCEMODE_AUTOMATIC 	0x0004	The IME carries out conversion processing in automatic mode.
TF_SENTENCEMODE_PHRASEPREDICT 	0x0008	The IME uses phrase information to predict the next character.
TF_SENTENCEMODE_CONVERSATION 	0x0010	The IME uses conversation mode. This is useful for chat applications.
This is equivalent with IME_SMODE values for IMM32

http://blogs.msdn.com/b/tsfaware/archive/2007/05/30/what-is-a-keyboard.aspx

ブックマーク

Windows Input Method の歴史 http://d.hatena.ne.jp/NyaRuRu/20070309/p1

IME (Input Method Editor) と Text Services Framework のアクセシビリティ

TSF (コードネーム Cicero)

IMM32 : TSFよりも古い時代のAPIで、Vista からは TSF に本格移行(??)

SDK の定義

C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include にあるもの
msctf.h
msctf.idl

Pythonで叩くには

調査中

from comtypes.client import GetModule
GetModule("shdocvw.dll")
GetModule(["{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 1, 1])

のようなことができるらしいが、msctf.dll を GetModule するのはうまくいかなかったので、気持ちを切り替える。

nvda の generate.py が typelibs の中身から comInterfaces の中身を作っている。

http://www.python.jp/doc/contrib/ctypes/sum_sample_jp.html

によれば

midl コンパイラを midl sum.idl /tlb sum.tlb として動作させると, 型ライブラリ sum.tlb が生成されます.

となっている。midl は Visual Studio 2008 に入っている。

が、midl の実行で躓く。msctf.idl を SDK のディレクトリからコピーしてきて midl msctf.idl /tlb mctf.tlb を実行したが何も出力されない。

aa366839(VS.85).aspx

COMプログラミング : IDL 超入門「タイプライブラリは IDL をバイナリトークンにしたもの」

NVDA の generate.py を真似る

generate.py をコピーして改変する。

#a test code of TSF (based on NVDA code) by nishimotz
#
#generate.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
 
import comtypes.client
comtypes.client.gen_dir='.\\comInterfaces'
import comtypes
import sys
sys.modules['comtypes.gen']=comtypes.gen=__import__("comInterfaces",globals(),locals(),[])
 
import os
import sys
from glob import glob
 
COM_INTERFACES = (
	("sum", comtypes.client.GetModule, "sum.tlb"),
)
 
def main():
	print "COM interfaces:"
	for desc, func, interface in COM_INTERFACES:
		print "%s:" % desc,
		try:
			func(interface)
			print "done."
		except:
			print "not found."
	print
 
if __name__ == "__main__":
	main()
	from comInterfaces.SumLib import _1F2B08DF_E59C_4F10_98D9_16078342F74C_0_1_0
	o = _1F2B08DF_E59C_4F10_98D9_16078342F74C_0_1_0.ITfThreadMgr() 
	print "ITfThreadMgr:"
	print dir(o)
	p = _1F2B08DF_E59C_4F10_98D9_16078342F74C_0_1_0.ITfThreadMgrEventSink()
	print "ITfThreadMgrEventSink:"
	print dir(p)

comInterfaces ディレクトリを作り、その中に init.py という名前の空ファイルを作る。 (nvdaのソースの真似)

ここにある sum.idl を改変する。

/* http://www.python.jp/doc/contrib/ctypes/sum_sample_jp.html */
/* A TypeLibrary, compiled to sum.tlb */

import "oaidl.idl";
import "comcat.idl";
import "textstor.idl";
import "ctfutb.idl";

[
	uuid(1F2B08DF-E59C-4f10-98D9-16078342F74C), // generated by nishimotz
	version(1.0),
	helpstring("Sum 1.0 Type Library")
]
library SumLib
{
	importlib("stdole2.tlb");
	[
	  object,
	  uuid(aa80e801-2021-11d2-93e0-0060b067b86e),
	  pointer_default(unique)
	]
	interface ITfThreadMgr;
	
	[
	  object,
	  uuid(aa80e80e-2021-11d2-93e0-0060b067b86e),
	  pointer_default(unique)
	]
	interface ITfThreadMgrEventSink;
};

どうやら上記の4つのincludeでインタフェースの定義は終わっていて、library 宣言だけやればよいらしい。

名前は sum のままだが guidgen を実行してタイプライブラリの uuid は作り直した。 Interface の uuid は msctf.idl からコピーした。

Visual Studio 2008 のコマンドプロンプトで下記を実行:

>midl sum.idl
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0500
Copyright (c) Microsoft Corporation 1991-2006. All rights reserved.
Processing .\sum.idl
sum.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\objidl.idl
objidl.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\wtypes.idl
wtypes.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\basetsd.h
basetsd.h
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\guiddef.h
guiddef.h
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\comcat.idl
comcat.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\textstor.idl
textstor.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\ctfutb.idl
ctfutb.idl
Processing .\msctf.idl
msctf.idl
Processing C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\oaidl.acf
oaidl.acf

>python generate.py
COM interfaces:
sum: # Generating comtypes.gen._1F2B08DF_E59C_4F10_98D9_16078342F74C_0_1_0
# Generating comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0
# Generating comtypes.gen.stdole
# Generating comtypes.gen.SumLib
done.

ITfThreadMgr:
['Activate', 'AddRef', 'AssociateFocus', 'CreateDocumentMgr', 'Deactivate', 'Enu
mDocumentMgrs', 'EnumFunctionProviders', 'GetFocus', 'GetFunctionProvider', 'Get
GlobalCompartment', 'IsThreadFocus', 'QueryInterface', 'Release', 'SetFocus', '_
AddRef', '_ITfThreadMgr__com_Activate', '_ITfThreadMgr__com_AssociateFocus', '_I
TfThreadMgr__com_CreateDocumentMgr', '_ITfThreadMgr__com_Deactivate', '_ITfThrea
dMgr__com_EnumDocumentMgrs', '_ITfThreadMgr__com_EnumFunctionProviders', '_ITfTh
readMgr__com_GetFocus', '_ITfThreadMgr__com_GetFunctionProvider', '_ITfThreadMgr
__com_GetGlobalCompartment', '_ITfThreadMgr__com_IsThreadFocus', '_ITfThreadMgr_
_com_SetFocus', '_IUnknown__com_AddRef', '_IUnknown__com_QueryInterface', '_IUnk
nown__com_Release', '_QueryInterface', '_Release', '__class__', '__delattr__', '
__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__',
'__map_case__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduc
e_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', '_case_insensitive_', '_idlflags_', '_iid_', '_methods_']
ITfThreadMgrEventSink:
['AddRef', 'OnInitDocumentMgr', 'OnPopContext', 'OnPushContext', 'OnSetFocus', '
OnUninitDocumentMgr', 'QueryInterface', 'Release', '_AddRef', '_ITfThreadMgrEven
tSink__com_OnInitDocumentMgr', '_ITfThreadMgrEventSink__com_OnPopContext', '_ITf
ThreadMgrEventSink__com_OnPushContext', '_ITfThreadMgrEventSink__com_OnSetFocus'
, '_ITfThreadMgrEventSink__com_OnUninitDocumentMgr', '_IUnknown__com_AddRef', '_
IUnknown__com_QueryInterface', '_IUnknown__com_Release', '_QueryInterface', '_Re
lease', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__geta
ttribute__', '__hash__', '__init__', '__map_case__', '__metaclass__', '__module_
_', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__size
of__', '__str__', '__subclasshook__', '__weakref__', '_case_insensitive_', '_idl
flags_', '_iid_', '_methods_']

メソッドの名前を拾えているということは、うまくいくかも。。。と思ったのだが。。。

ITfThreadMgrEventSink をどうやって登録すればいいのか分からない。

ReadCompを理解する

方針を変えて、ReadComp の tsf.{cpp,h} を眺めてみることにしたい。

CReadCompTSF::_AppendCompositionText() に文字列を取り出す処理がある。

ようするに ITfRange::GetText() メソッドがゴールだ。

readcomp ソリューション

ソリューション readcomp は2つのプロジェクト rcomplib (Win32 DLL) と readcomp (Win32 EXE) から構成される。

VC++2008 でビルドするときに static 変数名 = 値 という箇所がエラーになったので static int に修正。

実行するためには、両プロジェクトの Release ディレクトリに作られる rcomplib.dll と readcomp.exe を同じディレクトリにコピー。

ReadComp プログラムを実行し、別途 notepad.exe を実行すると、IMM と TSF の情報が画面右下に表示される。

MS-IME 2002 の場合は IMM と TSF の両方で表示される。ATOK 2009 の場合は IMM しか表示されない。

readcomp を WinMain からさかのぼる

readcomp.cpp の WinMain() の中身:

InitInstance() は CreateWindow(), ShowWindow(), UpdateWindow() を呼び出す。いずれも Win32 API である。

InitApp() はやはり readcomp.cpp の中にあり、InitReadComp() をコールする。だがこの BOOL WINAPI InitReadComp() は rcomplib.cpp の中で実装されており、EXE から DLL の呼び出しになっている。

readcomp.cpp の先頭には #include "..\inc\rcomplib.h" という記述があり、この rcomplib.h に

BOOL WINAPI InitReadComp();
BOOL WINAPI UninitReadComp();

が宣言されている。

また readcomp プロジェクトのプロパティによると、リンカの引数に "..\dll\release\rcomplib.lib" がある。

DLL 側の処理

BOOL WINAPI InitReadComp() の重要な仕事は SetWindowsHookEx(WH_GETMESSAGE, …) を呼ぶこと。

http://msdn.microsoft.com/ja-jp/library/cc430103.aspx

HHOOK SetWindowsHookEx(
  int idHook,        // フックタイプ
  HOOKPROC lpfn,     // フックプロシージャ
  HINSTANCE hMod,    // アプリケーションインスタンスのハンドル
  DWORD dwThreadId   // スレッドの識別子
);

WH_GETMESSAGE については 「メッセージキューへポストされたメッセージを監視する 1 個のフックプロシージャをインストールします。詳細については、GetMsgProc フックプロシージャの説明を参照してください。」 とされている。

http://msdn.microsoft.com/ja-jp/library/cc429822.aspx

LRESULT CALLBACK GetMsgProc(
  int code,       // フックコード
  WPARAM wParam,  // 削除オプション
  LPARAM lParam   // メッセージ
);

フックプロシージャと ImmGet*** 呼び出し

rcomplib.cpp で SysGetMsgProc() というフックプロシージャが定義される。

細かいところを飛ばすと、さらに UINT _SysGetMsgProc(WPARAM wParam, LPARAM lParam) が呼ばれる。

    CReadCompTSF::Init();
    IncrementDllRefCount();
 
    switch (uMsg)
    {
        case WM_IME_STARTCOMPOSITION:
        case WM_IME_ENDCOMPOSITION:
        case WM_IME_COMPOSITION:
        case WM_KEYDOWN:
        case WM_KEYUP:
            UpdateIMMCompositionString();
            break;
        default:
            break;
    }

ということで、やっと CReadCompTSF が出てきたが、けっきょくフックは IME がらみのキーイベントを引っかけている。

UpdateIMMCompositionString() の中身を見る。

ここでは GetFocus() して ImmGetContext() して ImmGetCompositionStringW() している。

Imm系のAPIは時代遅れという話だったが、ATOK2009 は Imm 系にしか対応していない。

いずれにせよ TSF 系の処理は絡んでいない。TSF を使うだけならウィンドウメッセージのフックは不要? と一瞬思ったが、大事なことが CReadCompTSF::Init() にあるはずなので後でじっくり見る。

TLS:スレッドローカル記憶域

TLS というクラスが登場する。tls.h で static メソッドを実装している。 TlsAlloc() や TlsGetValue() などが呼ばれている。

以下のメンバ変数が TLS クラスにある。

    CReadCompTSF *_pReadCompTSF;
    CCompositionPopup *_pCompositionPopup;

http://msdn.microsoft.com/ja-jp/library/cc429388.aspx

TLS とはスレッドローカル記憶域のこと。関連APIは

ヘッダ : winbase.h で宣言されています。
インポートライブラリ : kernel32.lib とリンクします。

とのこと。

tls.cpp には BOOL TLS::InternalDestroyTLS() メソッドの定義がある。

TlsGetValue() の返り値を TLS* ptls に cast している。 そして ptls→_pReadCompTSF を Release() したり ptls→_pCompositionPopup を delete したり。。 最後に LocalFree(ptls) を実行。

TLSの解説(英語) http://support.microsoft.com/?scid=kb;en-us;94804&x=12&y=12

スレッドとTLSの参考資料 http://www7a.biglobe.ne.jp/~tsuneoka/win32tech/19.html

「スレッドはプロセスの仮想アドレス空間とグローバル変数を共有します. しかし,場合によっては各スレッドにローカルな静的記憶域があった方がよいことも あります」

「TLS(スレッドローカル記憶域)を利用して, プロセスの任意のスレッドがスレッドごとに異なる値を格納・取得できるようにする ことができます」

「(2) TLSインデックスを使わなければならないスレッドは,動的記憶域を割り当てて から,Win32 API TlsSetValueで,その記憶域を指すポインタとインデックスを 関連付ける」

「(3) スレッドは,記憶域をアクセスするとき,TLSインデックスを指定して Win32 API TlsGetValue を呼び出してポインタを取得する」

CCompositionPopup クラスは Imm 系APIで得た情報をウィンドウに表示するクラスと思われる。 IME側のスレッドからフックされて呼ばれるので readcomp 側のスレッドとの記憶域の分離が必要なのだろう。。

なお、最近の VC++ では __declspec(thread) という記述で TLS を簡単に扱えるらしい。

見落としていたことがあったので _SysGetMsgProc(WPARAM wParam, LPARAM lParam) に戻る。

CReadCompTSFの初期化

_SysGetMsgProc(WPARAM wParam, LPARAM lParam) で先に呼ばれていた CReadCompTSF::Init() を確認。VC++2008の右クリックメニューで追っていく。

tsf.h では static BOOL Init(); として宣言されている。

tsf.cpp に BOOL CReadCompTSF::Init() がある。

やはり最初に TLS::GetTLS() を呼んでいた。

返り値の _pReadCompTSF がもし NULL であれば ptls→_pReadCompTSF = new CReadCompTSF; を実行して初期化。

その直後にやっと出てくるのがテキストサービスのCOMインタフェースだ。TSFスレッドマネージャのオブジェクトを作成している。 生成されたオブジェクトのポインタを ptim で受け取る。

    ITfThreadMgr* ptim;
    HRESULT hr;
 
    hr = CoCreateInstance(CLSID_TF_ThreadMgr, 
                          NULL, 
                          CLSCTX_INPROC_SERVER, 
                          IID_ITfThreadMgr, 
                          (void**)&ptim);

http://yokohama.cool.ne.jp/chokuto/urawaza/api/CoCreateInstance.html

CoCreateInstance() により「指定されたCLSIDに関連付けられたクラスの1つの未初期化オブジェクトを作成」する。

うまくいったら _InitThreadMgrSink() が呼び出される。

    ptls->_pReadCompTSF->_InitThreadMgrSink();

中身は後で見る。

続き:こんどはドキュメントマネージャだ。スレッドマネージャのGetFocus()メソッドで取り出すらしい。

    ITfDocumentMgr *pDocMgr;
    if (SUCCEEDED(ptim->GetFocus(&pDocMgr)))
    {
        if (pDocMgr)
        {
           ptls->_pReadCompTSF->_InitTextEditSink(pDocMgr);
           pDocMgr->Release();
        }
    }

成功したら _InitTextEditSink() を実行。

_InitThreadMgrSink() と _InitTextEditSink() の中身が興味深い。

_InitThreadMgrSink

BOOL CReadCompTSF::_InitThreadMgrSink() を見る。

まず ThreadMgr に対して QueryInterface をかけて、ITfSource インタフェースを取得。

    ITfSource *pSource;
    BOOL fRet;
 
    if (_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource) != S_OK)
        return FALSE;

MSDN の ms628941 によれば、下記に対して ITfSource の QueryInterface をかけることができ、それぞれ機能が違うと書かれている。

ITfThreadMgr
ITfContext
ITfCompartment
ITfInputProcessorProfiles
ITfLangBarItem

ここでは pSource は ITfThreadMgr の ITfSource であることを確認。

続きのコードは、pSource に対して AdviseSink を実行し、ITfThreadMgrEventSink として this を登録している。

    fRet = FALSE;
 
    if (pSource->AdviseSink(IID_ITfThreadMgrEventSink, (ITfThreadMgrEventSink *)this, &_dwThreadMgrEventSinkCookie) != S_OK)
    {
        // make sure we don't try to Unadvise _dwThreadMgrEventSinkCookie later
        _dwThreadMgrEventSinkCookie = TF_INVALID_COOKIE;
        goto Exit;
    }
 
    fRet = TRUE;
 
Exit:
    pSource->Release();
    return fRet;

念のため tsf.h を確認すると確かに CReadCompTSF は ITfThreadMgrEventSink を実装している:

class CReadCompTSF : public ITfThreadMgrEventSink, 
                     public ITfTextEditSink

ms628945 によると最後の引数は

であり、コールバックを解除するときに必要な情報の格納場所。

pSource は AdviceSink だけのために使われる。終わったら IUnknown のメソッド Release() を呼んでいる。

TF_INVALID_COOKIE は SDK の msctf.h で定義されている。

まとめると、スレッドマネージャの Source インタフェースに ITfThreadMgrEventSink として CReadCompTSF のインスタンスを登録。

ms628973 で確認。これで CReadCompTSF の以下のメンバ関数が呼ばれるようになるわけだ。

OnSetFocus

STDAPI CReadCompTSF::OnSetFocus(ITfDocumentMgr *pDocMgrFocus, ITfDocumentMgr *pDocMgrPrevFocus)

を見ておく。

if (GetSharedMemory()→fReadCompRunning) の場合に下記を実行。

        _InitTextEditSink(pDocMgrFocus);

けっきょく CReadCompTSF の初期化時に加えて ITfThreadMgrEventSink が OnSetFocus を受け取ったときにも、後述の _InitTextEditSink が呼ばれる。

ついでにGetSharedMemory()について見ると globals.h で以下の記述。

extern CReadCompSharedMem g_SharedMemory;
 
inline SHAREDMEM *GetSharedMemory() { return g_SharedMemory.GetPtr(); }
inline void FlushSharedMemory() { g_SharedMemory.Flush(0); }

このクラスは CReadCompFileMapping がスーパークラスであり filemap.h で実装されている。 OpenFileMapping という API が使われている。 cc430187 に説明があるが。。。

http://wisdom.sakura.ne.jp/system/winapi/win32/win150.html

によると「プロセス間データ共有」とのこと。

ここでは TEXT("ReadCompSharedMem") という名前の共有メモリを作成して、 下記の構造体のポインタを GetSharedMemory() が返す、と理解しておく。

typedef struct
{
    HHOOK hSysGetMsgHook;
    BOOL  fReadCompRunning;
} SHAREDMEM;

_InitTextEditSink

BOOL CReadCompTSF::_InitTextEditSink(ITfDocumentMgr *pDocMgr) を見る。

引数は ITfThreadMgr の GetFocus() で得たオブジェクトである。

最初に

  // clear out any previous sink first

につづく行がある。2回目以降に呼ばれるときの処理。

_dwTextEditSinkCookie != TF_INVALID_COOKIE の場合に _pTextEditSinkContext を使って ITfSource に UnadviseSink をかける。

引数 pDocMgr == NULL のときは、Unadvice だけ行って終了。 caller just wanted to clear the previous sink

1回目から必ず実行されるのはその下。ThreadMgrEventSink と同様だ。

pDocMgr→GetTop(&_pTextEditSinkContext) で NULL が返ったら抜ける:

最後に pSource→AdviseSink する:

    if (_pTextEditSinkContext->QueryInterface(IID_ITfSource, (void **)&pSource) == S_OK)
    {
        if (pSource->AdviseSink(IID_ITfTextEditSink, (ITfTextEditSink *)this, &_dwTextEditSinkCookie) == S_OK)
        {
            fRet = TRUE;
        }
        else
        {
            _dwTextEditSinkCookie = TF_INVALID_COOKIE;
        }
        pSource->Release();
    }

OnEndEdit から GetText まで

ITfTextEditSink は ms628962 に情報。

メソッドは1つだけだ。

STDAPI CReadCompTSF::OnEndEdit(ITfContext *pContext, TfEditCookie ecReadOnly, ITfEditRecord *pEditRecord) をみる。

    FlushSharedMemory();
    if (GetSharedMemory()->fReadCompRunning)
        _CheckComposition(pContext, ecReadOnly);

_CheckComposition

BOOL CReadCompTSF::_CheckComposition(ITfContext *pContext, TfEditCookie ecReadOnly) をさらに見る。

    _ClearCompositionText();
 
    hr = pContext->QueryInterface(IID_ITfContextComposition,
                                  (void **)&pContextComposition);

ここで ITfContext に QueryInterface して ITfContextComposition を取り出す。

ITfContextComposition の EnumCompositions で EnumCompositionView を取り出す。

EnumCompositionView の Next で ITfCompositionView を取り出す。

ITfCompositionView の GetRange で ITfRange を取り出す。

_AppendCompositionText() に ITfRange を渡す。

_AppendCompositionText

void CReadCompTSF::_AppendCompositionText(ITfRange *pRange, TfEditCookie ecReadOnly) も見てみる。

pRange->GetText(ecReadOnly, TF_TF_MOVESTART, wstr, ulcch, &ulcch);

これでやっと入力文字列が取り出せたことになる。

OnEndEdit() で受け取った ecReadOnly が最後まで使われていることになる。

ITfRange は ms628908 に情報。

TSF情報の表示

BOOL CReadCompTSF::_CheckComposition(ITfContext *pContext, TfEditCookie ecReadOnly)

の最後に void CCompositionPopup::SetTSFCompositionString(WCHAR *psz) の呼び出しが出てくる。

ptls->_pCompositionPopup->SetTSFCompositionString(_pszCompositionText);

中では _pszTSFComposition にコピーするだけだ。

表示は

void CCompositionPopup::OnPaint(HWND hwnd, HDC hdc)

にて

TextOutW(hdc, 50, 1, _pszTSFComposition, lstrlenW(_pszTSFComposition));

によって実行される。

OnPaint() の呼び出しは

LRESULT CALLBACK CCompositionPopup::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

の中の case WM_PAINT: の処理である。Win32 のイベント処理がクラス化されている。