内部で Unicode を使うということは・・・?

よく 「Windows は Shift JIS だ」 という一方で、コードを書き出すと 「Windows は内部的に Unicode を使っている」 ということを耳にして、実際どうなっているのか混乱しやすのではないでしょうか。

私も API などはどこで何を使うかはわかっていたつもりでしたが、 それでは 「何か文字を入力した時、いつそれが Unicode になるんだ?」 と考えるといまひとつハッキリわかっている気がしませんでした。

そこで、当サイト流のやり方で実際に目で見てみようと思います。

実験

次のようなプログラムを、ANSI ビルド、Unicode ビルドの2通り用意します。

そこで、"テスト" という文字をエディットコントロールに入力してみます。 今回は 「貼り付け」 によって入力します。

Shift JIS から Unicode への変換が発生すれば、MultiByteToWideChar が呼ばれるはずですし、 (それ以外あります?) Unicode から Shift JIS への変換が発生すれば WideCharToMultiByte が呼ばれるはずです。

デバッガでいつ文字データが変換されるか見てみることにしましょう。

ちなみに、実験で 「テスト」 という文字を扱うので先に Shift JIS と Unicode のコードを確認しておきましょう。


Shift JIS


Unicode

ANSI ビルドのプログラムの場合

プログラムを起動して、MultiByteToWideChar にブレークポイントを設定します。

0:000> bp kernel32!MultiByteToWideChar

そこで貼り付けてみましょう。

すると、「貼り付け」 を実行した瞬間にブレークポイントがヒットしました。

Breakpoint 0 hit
eax=00000001 ebx=76ae702c ecx=76a4b44b edx=000003a4 esi=76ae7000 edi=00000006
eip=76525cae esp=0012f8cc ebp=0012f90c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
kernel32!MultiByteToWideChar:
76525cae 8bff            mov     edi,edi
0:000> kbn 100
 # ChildEBP RetAddr  Args to Child
00 0012f8c8 76aa973c 000003a4 00000000 001474f8 kernel32!MultiByteToWideChar
01 0012f90c 76aa978f 001474f8 00000006 00000080 USP10!AnsiToUnicode+0x3b
02 0012f934 76a9af38 000010a2 001474f8 00000006 USP10!SetInputBuffers+0xb9
03 0012f958 765c167f ff0143d7 001474f8 76ae7000 USP10!ScriptStringAnalyse+0x7f
04 0012f9a4 765c1dff ff0143d7 001474f8 00000006 LPK!LpkStringAnalyse+0x114
05 0012fa00 765c258f ff0143d7 00000030 001474f8 LPK!EditStringAnalyse+0xab
06 0012fa38 77e43a4e 00000000 ff0143d7 001474f8 LPK!EditHScroll+0x39
07 0012fa70 77e44088 001473c8 ff0143d7 00000000 USER32!SLScrollText+0x3f
08 0012fa88 77e2c289 001473c8 0091adf0 001473c8 USER32!SLPaste+0x32
09 0012faf4 77e17146 014b035a 001473c8 00000302 USER32!SLEditWndProc+0x4f8
0a 0012fb44 77e170bb 0091adf0 00000302 00000000 USER32!EditWndProc+0x9cf
0b 0012fb68 77e04a73 0091adf0 00000302 00000000 USER32!EditWndProcWorker+0x13c
0c 0012fb9c 77e10afa 0091adf0 0094f300 00000000 USER32!SendMessageWorker+0x438
0d 0012fbbc 77e43faa 014b035a 00000302 00000000 USER32!SendMessageW+0x7c
0e 0012fbec 77e2c1a5 00000001 00000016 0091adf0 USER32!SLChar+0x288
0f 0012fc5c 77e17146 014b035a 001473c8 00000102 USER32!SLEditWndProc+0x415
10 0012fcac 77e170bb 0091adf0 00000102 00000016 USER32!EditWndProc+0x9cf
11 0012fcd0 77e1704b 0091adf0 00000102 00000016 USER32!EditWndProcWorker+0x13c
12 0012fcf0 77e0f8d2 014b035a 00000102 00000016 USER32!EditWndProcA+0x4c
13 0012fd1c 77e0f794 77e16feb 014b035a 00000102 USER32!InternalCallWinProc+0x23
14 0012fd94 77e10008 00000000 77e16feb 014b035a USER32!UserCallWinProcCheckWow+0x14b
15 0012fdf8 77e10060 77e16feb 00000000 0012fe2c USER32!DispatchMessageWorker+0x322
16 0012fe08 77dfaf07 0012fe48 00000001 0094f228 USER32!DispatchMessageW+0xf
17 0012fe2c 77dfafd4 00000089 0091adf0 00000000 USER32!IsDialogMessageW+0x586
18 0012fe68 77dfbcda 006f072a 00000000 00000001 USER32!DialogBox2+0x143
19 0012fe90 77dfbd1c 00400000 00417060 00000000 USER32!InternalDialogBox+0xd0
1a 0012feb0 77e380fe 00400000 00417060 00000000 USER32!DialogBoxIndirectParamAorW+0x37
1b 0012fedc 00401118 00400000 00000065 00000000 USER32!DialogBoxParamA+0x4c
1c 0012fef8 00401370 00400000 00000000 0014283d dlgwin32!WinMain+0x18
1d 0012ff88 76524911 7ffdf000 0012ffd4 77c9e4b6 dlgwin32!__tmainCRTStartup+0x113
1e 0012ff94 77c9e4b6 7ffdf000 5e47f00d 00000000 kernel32!BaseThreadInitThunk+0xe
1f 0012ffd4 77c9e489 004013db 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23
20 0012ffec 00000000 004013db 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b

よくわかりませんが、確かに Paste と見えるので貼り付け処理を行っているところのようです。

そこで kernel32!MultiByteToWideChar のスタックフレームを見てみましょう。

0:000> dc 0012f8c8
0012f8c8  76a84ec7 76aa973c 000003a4 00000000  .N.v<..v........
0012f8d8  001474f8 00000006 76ae72ac 00000006  .t.......r.v....
0012f8e8  00000080 000003a4 00000000 00000000  ................
0012f8f8  00000000 00000000 00020000 00000000  ................
0012f908  76a84ec7 0012f934 76aa978f 001474f8  .N.v4......v.t..
0012f918  00000006 00000080 76ae72ac 76ae702c  .........r.v,p.v
0012f928  76ae7000 0012f9f0 00000001 0012f958  .p.v........X...
0012f938  76a9af38 000010a2 001474f8 00000006  8..v.....t......

マルチバイトのバッファが 001474f8 にあって、その結果を格納するバッファは 76ae72ac ですね。 (「え?なんで?」 って、思った方は EBP フレームをおさらいしてから、MultiByteToWideChar の引数を MSDN で調べてください)

そこで、何を Wide Char にしようとしているか見てみます...

0:000> da 001474f8
001474f8  ".e.X.g"

おっと、これじゃ意味不明ですね。dc で見てみましょう。

0:000> dc 001474f8
001474f8  58836583 00006783 00000000 00000000  .e.X.g..........
00147508  00000000 00000000 00000000 00000000  ................
00147518  abababab abababab 00000000 014e0004  ..............N.
...

お、確かにこれは 「テスト」 という文字の Shift JIS 表現ですね。

戻りバッファの中身を確認してから、この関数の終わりまで進みましょう。

0:000> dc 76ae72ac
76ae72ac  00000000 00000000 00000000 00000000  ................
76ae72bc  00000000 00000000 00000000 00000000  ................
76ae72cc  00000000 00000000 00000000 00000000  ................
...
0:000> g @$ra
eax=00000003 ebx=76ae702c ecx=0409ae87 edx=00000300 esi=76ae7000 edi=00000006
eip=76aa973c esp=0012f8e8 ebp=0012f90c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
USP10!AnsiToUnicode+0x3b:
76aa973c 85c0            test    eax,eax

結果のバッファをみてみます。

0:000> dc 76ae72ac
76ae72ac  30b930c6 000030c8 00000000 00000000  .0.0.0..........
76ae72bc  00000000 00000000 00000000 00000000  ................
76ae72cc  00000000 00000000 00000000 00000000  ................
...
0:000>

確かにこれは、「テスト」 の Unicode 表現です。

以上から、LPK というモジュールが貼り付け処理の間に入り、直ちに Unicode に変換したことがわかります。 LPK は次の通り、Language Pack のモジュールのようです。

0:000> lmvm lpk
start    end        module name
765c0000 765c9000   LPK        (pdb symbols)          C:\websymbols\lp...
    Loaded symbol image file: C:\Windows\system32\LPK.DLL
    Image path: C:\Windows\system32\LPK.DLL
    Image name: LPK.DLL
    Timestamp:        Fri Jan 18 23:29:45 2008 (4791A6E9)
    CheckSum:         0000A2B7
    ImageSize:        00009000
    File version:     6.0.6001.18000
    Product version:  6.0.6001.18000
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    CompanyName:      Microsoft Corporation
    ProductName:      MicrosoftR WindowsR Operating System
    InternalName:     LanguagePack
    OriginalFilename: LanguagePack
    ProductVersion:   6.0.6001.18000
    FileVersion:      6.0.6001.18000 (longhorn_rtm.080118-1840)
    FileDescription:  Language Pack
    LegalCopyright:   c Microsoft Corporation. All rights reserved.
0:000>

LPK がどんな順序でどこで介入してくるか、ここではあまり追求しないことにして、 (興味がある人はぜひ!) とりあえず、初めは Shift JIS でデータを渡したことは確認できました。

Unicode ビルドの場合

上記の流れでは全く bp がヒットしませんでした。

貼り付け先のターゲットウィンドウが Unicode であればシェル側で既に Unicode に変換していたのか、 あるいはまったく別ルートを通るのか、今回は掘り下げて確認しませんでした。 しかし、いずれにせよ貼り付けられる側のプログラムでは一切 MB2WC や WC2MB を通ることなく、 文字を貼り付けることが可能であることが確認できた、と言ってよいでしょう。

ちなみに、ANSI ビルドの場合は幾度と無く MB2WC が呼び出されます。 これひとつとっても、ANSI ビルドに比べ Unicode ビルドの方がいかにも効率が良さそうです。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説