目次
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 のアクセシビリティ
-
- readcomp というデモプログラムが入手できる。rcomplib 側の tsf.cpp, tsf.h が参考になりそう。ATOKだとIMMの情報は得られるがTSFの情報は得られない、といったことも確認できる。
- readcomp は WinEvent の取得処理は含んでいない。
- nvdajp チケット 26438 : ワードパッドでのIME読み上げの不具合
TSF (コードネーム Cicero)
- ms629002 によると TF_INVALID_COOKIE は Not used. である
IMM32 : TSFよりも古い時代のAPIで、Vista からは TSF に本格移行(??)
- ImmGetCompositionString() の例
- VB版をDelphi2007に移植して試してみたが、変換候補の文字列は取れないようだ
- 「Windows Vistaでデフォルト機能となった「Text Services Framework (TSF)」は、従来のIMEで使われていたIMM32を置き換えるものだ」
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 を実行したが何も出力されない。
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 系にしか対応していない。
- 2011-10-30 追記:ATOK2011 は TSF に対応している。
いずれにせよ 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 によると最後の引数は
- Address of a DWORD value that receives an identifying cookie. This value is used to uninstall the advise sink in a subsequent call to ITfSource::UnadviseSink. Receives (DWORD)-1 if a failure occurs.
であり、コールバックを解除するときに必要な情報の格納場所。
pSource は AdviceSink だけのために使われる。終わったら IUnknown のメソッド Release() を呼んでいる。
TF_INVALID_COOKIE は SDK の msctf.h で定義されている。
- 2011-10-30 追記:TF_INVALID_COOKIE は ReadComp の中で「初期化に失敗したことを表す」フラグである可能性。
まとめると、スレッドマネージャの Source インタフェースに ITfThreadMgrEventSink として CReadCompTSF のインスタンスを登録。
ms628973 で確認。これで CReadCompTSF の以下のメンバ関数が呼ばれるようになるわけだ。
- OnInitDocumentMgr :
- Called when the first context is added to the context stack
- Sink called by the framework just before the first context is pushed onto a document.
- OnUninitDocumentMgr :
- Called when the last context is removed from the context stack
- Sink called by the framework just after the last context is popped off a document.
- OnSetFocus :
- Called when a document view receives or loses the focus
- Sink called by the framework when focus changes from one document to another. Either document may be NULL, meaning previously there was no focus document, or now no document holds the input focus.
- この呼び出しを使ってテキストを追跡する。
- 最後に OnSetFocus(NULL, ..) が呼ばれるので、そこでリソース解放処理をすればよい。
- TextEditSink の登録を行うなど。
- OnPushContext :
- Called when a context is added to the context stack
- Sink called by the framework when a context is pushed.
- OnPopContext :
- Called when a context is removed from the context stack
- Sink called by the framework when a context is popped.
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) を見る。
- Init a text edit sink on the topmost context of the document. Always release any previous sink.
引数は 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 と同様だ。
- setup a new sink advised to the topmost context of the document
pDocMgr→GetTop(&_pTextEditSinkContext) で NULL が返ったら抜ける:
- empty document, no sink possible
最後に 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つだけだ。
- OnEndEdit :
- Receives a notification upon completion of an ITfEditSession::DoEditSession method that has read/write access to the context.
- Called by the system whenever anyone releases a write-access document lock.
STDAPI CReadCompTSF::OnEndEdit(ITfContext *pContext, TfEditCookie ecReadOnly, ITfEditRecord *pEditRecord) をみる。
FlushSharedMemory(); if (GetSharedMemory()->fReadCompRunning) _CheckComposition(pContext, ecReadOnly);
- if we get here, only property values changed とされている。
- ReadComp では使われていないが、第3引数の説明:
_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 のイベント処理がクラス化されている。