Windows 7/ Windows Server 2008 R2 以降のOS とStrConv関数 – VBStrConv.Wideを使用した文字変換について
こんにちは。Visual Studio サポート チームです。
今回は、濁点や半濁点を含む文字列に対して Visual Basic の StrConv 関数を使用した場合に、Windows 7 以降 の OS とそれ以前の OS で結果が異なる現象について解説します。
StrConv 関数を使用するアプリケーションをご利用の場合、以前の OS から、Windows 7 や Windows Server 2008 R2 以降の OS に実行環境をアップグレードすると、以下のような現象が発生するのでご注意ください。
現象
StrConv 関数を使用して濁点 (Shift_JIS : 0x814A) や半濁点 (Shift_JIS : 0x814B) の文字の全角・半角を変換しようとした場合、以下のように、実行環境の OS のバージョンによって結果が異なります。
<変換結果 : Windows Vista / Windows Server 2008 以前> 濁点
変換前 : ゛
変換後 : ゛
半濁点
変換前 : ゜
変換後 : ゜
<変換結果 : Windows 7 / Windows Server 2008 R2 以降>
濁点
変換前 : ゛
変換後 : ?
半濁点
変換前 : ゜
変換後 : ?
<テスト コード>
Dim enc_sjis As Encoding = Encoding.GetEncoding(932)
Dim byteArray_0x814A As Byte() = {&H81, &H4A}
Dim byteArray_0x814B As Byte() = {&H81, &H4B}
' 濁点
Dim str_0x814A As String = enc_sjis.GetString(byteArray_0x814A)
Dim str_0x814A_conv As String = StrConv(str_0x814A, VbStrConv.Wide)
' 半濁点
Dim str_0x814B As String = enc_sjis.GetString(byteArray_0x814B)
Dim str_0x814B_conv As String = StrConv(str_0x814B, VbStrConv.Wide)
Console.WriteLine("濁点")
Console.WriteLine(" 変換前 : " + str_0x814A)
Console.WriteLine(" 変換後 : " + str_0x814A_conv)
Console.WriteLine("半濁点")
Console.WriteLine(" 変換前 : " + str_0x814B)
Console.WriteLine(" 変換後 : " + str_0x814B_conv)
原因
Windows 7 以降の OS では、文字変換で使用される LCMapString Win32 API 関数の実装が変更されており、Unicode の結合文字 (基底文字と濁点や半濁点を組み合わせた文字) が存在する文字コードについて、内部処理の過程で結合文字への変換が行われます。
その後、Unicode から Shift_JIS に再変換される際に、Shift_JIS には結合文字に対応する文字コードが存在しないために代替文字として ”?” に変換されます。
詳細
StrConv 関数は、同一の文字の半角 (Narrow) を全角 (Wide) に変換するといった日本語特有の文字の比較制御を行うことが可能です。
この処理を実現するために OS が管理する文字コード ページや文字処理を利用しており、StrConv 関数の内部では LCMapStringW 関数や LCMapStringA 関数といった Win32 API 関数を使用しています。
一方、Windows OS での文字処理では、各国の文字を一つの文字集合で表現することを可能とするため、 Unicode を内部コードで利用するように設計されています。
Unicode については複数のバージョンが存在し、最近では絵文字の追加登録など、必要に応じ、文字の追加登録、カテゴリのアップグレードが Unicode コンソーシアムによって行われています。
Unicode の更新に伴い、Windows OSの Unicode 対応も、OS のバージョンによって異なります。
今回の現象に影響しているのは、Windows 7 で対応した濁点、半濁点の取り扱いに関するものです。
<Windows 7 で変更された LCMapString 関数>
Windows 7 では文字処理を実行する LCMapStringW 関数の濁点、半濁点の取り扱いに変更が追加されました。
この変更により、LCMapStringW 関数の濁点、半濁点を使用する文字の取り扱いにおいて、“が“ や “ぱ” のように日本語としての意味から結合可能な文字のみではなく、Unicode に収録された文字に合わせ、“あ” に濁点、または、“か” 半濁点などの “結合不可能な文字” の組み合わせの表示が可能となりました。
Windows XP、 Server 2003 など Windows 7以前のOSでは、 “結合不可能な文字”の組み合わせの文字処理は、何も処理をせずに、入力された文字のままとしていました。
Windows 7 以降の OS はこの変更により、Visual Basic 6.0 のような非 Unicode 文字セットであるマルチバイト文字セットを既定とする言語の関数においては、このような結合不可能な文字と濁点、半濁点を組み合わせた文字を処理した場合、Windows 7 以前の OS と異なる結果を返します。
StrConv 関数についても同様に、マルチバイト文字セットを既定の文字セットとして想定した関数となっており、その内部処理では LCMapString 関数 (LCMapStringA、LCMapStringW) が使用されます。
Windows 7 で変更された濁点、半濁点を含む文字列処理の詳細についてを以下でご説明します。
<濁点、半濁点を含む文字列の処理>
StrConv 関数を使用して文字列処理を行った場合、OS 内部では以下のような関数の呼び出しが発生します。
StrConv 実行時の関数呼び出しの流れ
各関数の呼び出しの際に実行される文字に対する処理は以下のようになります。
文字処理の流れ
このように、StrConv 関数で VbStrConv.Wide を使用して、全角文字への変換を実施した場合、その内部処理では、マルチバイト文字セット用の LCMapStringA 関数と、Unicode 文字セット用の LCMapStringW 関数の間でデータの変換が実行され、最終的に結果として入力元の文字セットであるマルチバイトに変換し、結果が出力されます。
Windows 7 以降の OS では、LCMapStringA 関数から通常文字の濁点 (U+309B) や、半濁点 (U+309C) がLCMapStringW 関数に渡された場合、それぞれ Unicode の結合文字濁点 (U+3099)、半濁点 (U+309A) に変換するように処理が変更されました。
この変更により、か行・さ行 ・た行・は行の文字と濁点や、は行と半濁音の組み合わせのように結合文字がマルチバイト、Unicode の両方にその定義が存在する文字の組み合わせについては、これまで通り結合文字として変換されますが、あ行・な行・ま行・や行等、日本語の意味として “結合不可能な文字“ の組み合わせにおいては、以下のように基底文字と濁点、半濁点がそれぞれの Unicode 文字に変換されます。
例:結合可能な文字の組み合わせ は(U+306F) + 通常文字の濁点(U+309B) -> ば(U+3070)
は(U+306F) + 結合文字の濁点(U+3099) -> ば(U+3070)
は(U+306F) + 通常文字の半濁点(U+309C) -> ぱ(U+3071) は(U+306F) + 結合文字の半濁点(U+309A) -> ぱ(U+3071)
例 : 結合不可能な文字の組み合わせ あ(U+3042) + 通常文字の濁点(U+309B) -> あ゛(U+3042 + U+3099) あ(U+3042) + 結合文字の濁点(U+3099) -> あ゛(U+3042 + U+3099)
あ(U+3042) + 通常文字の半濁点(U+309C) -> あ゜(U+3042 + U+309A)
あ(U+3042) + 結合文字の半濁点(U+309A) -> あ゜(U+3042 + U+309A)
結合不可能な文字の組み合わせを StrConv 関数の内部処理に沿って、Wide 文字へ変換した場合、LCMapStringA 関数 --> LCMapStringW 関数の変換で濁点、半濁点は Unicode の U+3099 (濁点)、U+309A (半濁点) に変換されます。
しかし、その後 LCMapStringW 関数 –> LCMapStringA 関数に再度変換する場合には、Unicode の U+3099 (濁点)、U+309A (半濁点) に相当する マルチバイト文字 (Shift_JIS エンコード) の文字コードの定義が存在しないため、マルチバイト文字へのマッピングができず、”?” (U+003F) として出力されます。
通常文字の濁点(U+309B) -> ゛(U+3099) 結合文字の濁点(U+3099) -> ゛(U+3099) 通常文字の半濁点(U+309C) -> ゜(U+309A) 結合文字の半濁点(U+309A) -> ゜(U+309A)
例 : Windows 7 OS 上で結合不可能な文字の組み合わせを LCMapStringA –> LCMapStringW –> LCMapStringA と変換した場合
StrConv 関数では、上記のように LCMapString 関数を使用して処理を行っています。このため、Windows 7 とそれより前の OS 上で濁点、半濁点と結合不可能な文字を組み合わせた文字列を処理した場合、結果が異なる場合があります。
対処策
StrConv 関数、ならびに、LCMapStringW 関数の動作を Windows 7 より前の OS の動作と同じにするオプションの提供はありません。濁点、半濁点と結合不可能な文字を組み合わせた文字列に対する処理を Windows 7 以降の OS においても以前の OS 上での動作と同等にするためには、StrConv 関数で変換処理を行う前に、処理する文字列が結合不可能な濁点と半濁点の組み合わせを持っているかを確認し、該当する場合には、濁点、半濁点については変換しないようにするような処理をアプリケーションに追加する必要があります。
対処策のイメージ例