INTSourceChangelist:2435622 Availability:Public Title:文字エンコード Crumbs:%ROOT%, Programming, Programming/UnrealArchitecture/Reference, Programming/UnrealArchitecture/Reference/Properties, Programming/UnrealArchitecture/StringHandling Description:アンリアル・エンジン 4 で使用する文字エンコードの概要 Version:4.5 [TOC(start:2)] ## 概要 このドキュメントはアンリアルで使用する文字のエンコードの概要を説明します。 あらかじめ必要な知識: [The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)](http://www.joelonsoftware.com/articles/Unicode.html) ## テキストフォーマット テキストと文字列を表現するには、いくつかのフォーマットを使用します。これらのフォーマットと、フォーマット特有の良い点と悪い点を把握することにより、プロジェクトに使用するフォーマットの決定に役立てることができます。 フォーマットの技術的定義ではありませんが、このドキュメント適した簡易なバージョンとなっています。 $ **ASCII** : 32と126 (32 と 126 を含む) の間の文字、および 0、9、10、13 です。(P4 タイプのテキスト) (チェックイン時に P4 のトリガーで検証済みです) $ **ANSI** :ASCII と現行のコードページです (例えば Western European high ASCII) (P4 サーバーにバイナリとして格納しなくてはいけません)。 $ **UTF-8** : 8 ビットで構成される文字列です。非 ANSI 文字の生成に特別な文字のシーケンスを使用できます (ASCII のスーパーセット) (P4 タイプの Unicode)。 $ **UTF-16** : [BOM] (http://en.wikipedia.org/wiki/Byte-order_mark) 付きで 1 文字を16 ビットで構成するする文字列です (アストラル文字は 32 ビットまで可能) (P4 タイプの UTF-16) (チェックインの際に P4 トリガーで検証されます)。 ### バイナリの例 | **メリット** | **デメリット** | | --- | --- | | 内部フォーマットが定義されていません。フォーマットに関係なく各ファイルを読み込むことができます。 | マージできません。このタイプの全てのファイルは排他的チェックアウトが必要です。 | | | 内部フォーマットが定義されていません。それぞれのファイルが異なるフォーマットになる場合もあります。| | | P4 は各バージョンを全て格納します。デポのサイズが必要以上に大きくなる要因となります。 | ### テキストの例 | **メリット** | **デメリット** | | --- | --- | | マージが可能です。排他的なチェックアウトは必要ありません。 | とても限定的で、ASCII 文字のみを許容します。 | ### UTF-8 の例 | **メリット** | **デメリット** | | --- | --- | | 必要に応じて全ての文字に簡単にアクセスできます。 | アジア系言語に対し別のメモリプロファイルがあります。 | | より少ないメモリ消費量です。| P4 タイプの Unicode は Perforce サーバーでは有効ではありません。 | | ASCII のスーパーセットです。単純な ASCII 文字列は、完全に有効な UTF-8 文字列です。| 文字列操作がより複雑です。 長さの計算のような簡単な操作さえも文字列をパースしなくてはいけません。 | | ゲームが文字列を ASCII と認識しても機能し、そのように出力をします。 |アジア地域では、 MSDev は ASCII 以外は上手く処理することができません。これがチェックイン時にテキストを ASCII として検証する理由です。 | | Unicode が有効になっているサーバーの場合、ファイルのマージが可能で排他的なチェックアウトは必要ありません。 | | | パースして文字列が UTF-8 かどうかを検知することができます (BOM の有無に関係なく) | | ### UTF-16 の例 | **メリット** | **デメリット** | | --- | --- | | 必要に応じて全ての文字に簡単にアクセスできます。 | メモリの使用量が多くなります。 | | 簡単です。メモリの使用量は文字数の 2 倍になります (弊社が使用する文字は全て [Basic Multilingual Plane](http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes)) にあります。 | BOM が無い場合はこのフォーマットの検知は困難です。 | | 簡単です。文字列操作は文字列をパースせずに分割/結合することができます。 | ゲームが文字列を ASCII と検知した時は機能せず、その由を出力します (UTF-16 検証ソフトでチェックイン時に検証が可能になりました)。 | | ゲームで使用しているフォーマットと同じです。平行移動、パース、メモリ操作は必要ありません。 | MSDevはアジア地域では、ASCII 以外は何も処理しません。 これがチェックイン時にテキストを ASCII として検証する理由です。 | | マージが可能です。排他的なチェックアウトは必要ありません。 | | | C# 内部で UTF-16 を使用します。 | | ## UE4 の内部文字列の表現 アンリアル エンジン 4 の全文字列は、FStrings や TCHAR 配列などの [UTF-16](http://en.wikipedia.org/wiki/UTF-16/UCS-2) フォーマットでメモリに格納しています。多くのコードが 2 バイトを 1 コードポイントと想定しているため、基本多言語プレーン (Basic Multilingual Plane:BMP) のみをサポートしています。アンリアルの内部エンコーディングは UCS-2 として記述するのがより正確です。文字列は現行プラットフォームのエンディアンネス (メモリ上でのバイトの並び) に適した方法で格納されます。 ネットワーク構築中にパッケージへ、もしくはディスクからシリアル化する場合、0xff より小さい TCHAR 文字は全て (8 ビット) バイト列として格納されます。それ以外は 2 バイトの UTF-16 文字列として格納されます。シリアライズコードは、必要に応じていかなるエンディアン変換も処理することができます。 ## UE4 でロードするテキストファイル Unreal が外部のテキストファイルをロードする時は (例えばランタイム時の「.INT」ファイルの読み込み)、ほとんどの場合、「UnMisc.cpp」にある appLoadFileToString() 関数で処理します。主な処理は、appBufferToString() 関数で行います。 この関数は、UTF-16 ファイルにある Unicode のバイトオーダーマーク (BOM) を読み取り、もし BOM があれば、そのファイルを UTF-16ファイルとしてビッグエンディアン順もしくはリトルエンディアン順で読み込みます。 BOM が存在しなかった場合の挙動はプラットフォームに依存します。 Windows では、デフォルトの Windows MBCS エンコーディングを使用してテキストを UTF-16 に変換して (米国英語および西ヨーロッパは [Windows-1252](http://en.wikipedia.org/wiki/Windows-1252)、韓国語は CP949、日本語は CP932)、MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS...) を使用します。これは 2009 年 7 月頃の QA ビルドで追加されました。 Windows 以外のプラットフォームで変換に失敗した場合、関数は単にそれぞれのバイトを読み込み、TCHAR の配列を作成するために読み込んだものを 16 ビットに埋め込みます。 AppLoadFileToString() 関数でロードした、UTF-8 でエンコードされたテキストファイルを検出またはデコードするコードはないことに留意してください。 ## アンリアルで保存したテキストファイル エンジンによって生成されるテキストファイルの多くは、appSaveStringToFile() 関数を利用して保存します。 TCHAR 型の文字がすべてシングルバイトで表されている文字列は、(8-bit) 1 バイト列として格納されます。もしくは bAlwaysSaveAsAnsi フラグが true で渡されない限り、UTF-16 として格納されます。その場合、まずデフォルトの Windows エンコーディング形式に変換されます。シェーダーコンパイラが抱える UTF-16 ファイルに関する問題を回避するため、現時点ではシェーダーファイルのみで実行されます。 ## アンリアルで使用するテキストファイルに推奨されるエンコーディング ### 「INT」と「INI」ファイル どちらかのバイトオーダー順の UTF-16 です。デフォルトのアジア言語用の MBCS 文字 (例えば CP932) が Windows 上で機能する一方で、これらのファイルを PS3 と Xbox360 プラットフォームへロードする必要があり、変換コードは Windows のみで実行されます。 ### ソースコード 一般的に、C++ ソースコード内部への文字列リテラルの格納は推奨しておらず、このデータを「INT」ファイルに格納することを推奨します。 #### C++ ソースコード UTF-8 またはデフォルトの Windows の符号化です。MSVC、Xbox360 コンパイラ、gcc はすべて、UTF-8 で符号化されたソースファイルで問題ないはずです。例えば著作権、商標、「度」のシンボルのような高いビット セットの文字を持つ Latin-1 で符号化されたファイルは、ソースコードでは可能な限り避けるべきです。これは、異なるロケールを持つシステム上で符号化が壊れるためです。サードパーティのソフトウェアでのいくつかの事例は回避不可能 (例:著作権表示)なので、MSVC に関しては、警告 4819 を無効化します。これは、アジアの Windows でコンパイルを行う際に起こる警告です。 ## UTF-16 テキストファイルを Perforce に格納する * 'Text' を使用 **しないで** ください。 * UTF-x ファイルがチェックインされている状態でテキストとして格納すると、同期後にファイルは破損します。 * 「バイナリ」を使用する場合、ファイルを排他的チェックアウトとして印をつけてください。 * ASCII、UTF-8、UTF-16 文字コードとしてチェックインが可能で、これらはエンジンで機能します。 * しかしながら、バイナリファイルはマージすることができないので、ファイルが排他的チェックアウトとマークされていない場合は変更は無視されます。 * 'UTF-16' を使用する場合、UTF-16 以外のファイルがチェックインされない様に注意してください。 * Perforce には、非 UTF-16 を UTF-16 としてチェックインすることを許可しないトリガーがあります。 * 「//depot/UnrealEngine3/Development/Tools/P4Utils/CheckUTF16/」 * 'Unicode' タイプは UTF-8 を用いて変換し、ここでは主な使い道はありません。 ## 変換ルーチン さまざまな符号化へ、またさまざまな符号化から文字列を変換する多くのマクロがあります。これらのマクロは、ローカル スコープで宣言されたクラスインスタンスを使用し、スタック上でスペースを割り当てるため、これらへのポインタを保持しないことが非常に重要です。関数呼び出しへ文字列を渡すためだけに使用します。 * TCHAR_TO_ANSI(str) * TCHAR_TO_OEM(str) * ANSI_TO_TCHAR(str) * TCHAR_TO_UTF8(str) * UTF8_TO_TCHAR(str) 「UnStringConv.h」ファイルから以下のヘルパクラスを使用します。 * typedef TStringConversion FANSIToTCHAR; * typedef TStringConversion FTCHARToANSI; * typedef TStringConversion FTCHARToOEM; * typedef TStringConversion FTCHARToUTF8; * typedef TStringConversion FUTF8ToTCHAR; TCHAR_TO_ANSI の使用時は、バイト数が TCHAR 文字列の長さと同じになると仮定しないことが重要です。複数バイトの文字セットは、TCHAR 文字ごとに複数バイトを必要とすることがあります。最終的な文字列の長さをバイトで知りたい場合は、マクロの代わりにヘルパ クラスを使用することができます。例: FString String; ... FTCHARToANSI Convert(*String); Ar->Serialize((ANSICHAR*)Convert, Convert.Length()); // FTCHARToANSI::Length() は null ターミネータを除いて、符号化された文字列のバイト数を返します。 ## Unicode で ToUpper() と ToLower() が難しい問題 UE4 は、現時点で ANSI のみを処理します (ASCII | コードページ 1252 | | 西ヨーロッパ) 全言語において、不本意ながらも他よりはましな方法はこちらで参照してください 「http://en.wikipedia.org/wiki/ISO/IEC_8859」 * 英語、フランス語、ドイツ語、イタリア語、ポルトガル語、スペインとメキシコのスペイン語両方はISO/IEC 8859-1 です。 * ポーランド語、チェコ語、ハンガリー語は ISO/IEC 8859-2 です。 * ロシア語は ISO/IEC 8859-5 です。 「ftp://ftp.unicode.org/Public/MAPPINGS/ISO8859/」からのマッピングは、上記の言語に対応する変換ルールが含まれています。 「大文字」や「小文字」情報は、期待通りの結果を得るために、適切な Unicode 文字をクロスリファレンスします。 ## 東アジア系言語の符号化に特有な C++ ソースコードに関する注意事項 UTF-8 およびデフォルトの Windows エンコーディングは、C++ コンパイラに以下のような問題が生じる可能性があります。 **デフォルトの Windows による符号化** CP932(日本語)、CP936(簡体字中国語)、CP950(繁体字中国語) などの東アジア系言語のダブルバイト文字エンコード形式がソースコードに含まれている場合は、シングルバイト文字のコードページ (米国のCP437など) を使用して動作する Windows上で C++ によるソースコードをコンパイルする際に注意が必要です。 東アジア系文字のエンコードシステムは、最初のバイトには 0x81 から 0xFE までが使用され、2 番目のバイトには 0x40 から 0xFE までが使用されます。2 番目のバイトの値 0x5C は、ASCII/latin-1 ではバックスラッシュとして処理され、C++ 言語では特別な意味を持ちます。(文字列リテラル内ではエスケープシークエンスの意味。また、行末での使用は、行の継続を意味します)。 そのようなソースコードを、シングルバイトコードページをもつ Windows でコンパイルする場合、コンパイラは、東アジア系言語のダブルバイト文字の符号化を無視します。その結果、コンパイルエラーが起きるか、最悪の場合は EXE ファイルでバグが発生します。 シングルライン コメント: 東アジア系言語のコメントに 0x5c が入っている場合は、行の欠落が生じるために、発見が難しいバグやエラーが生じる原因となります。 // EastAsianCharacterCommentThatContains0x5cInTheEndOfComment0x5c'\' important_function(); /* this line would be connected to above line as part of comment */ 文字列リテラル内: 0x5c エスケープシーケンスとして認識するために、文字列の破損またはエラーが生じる原因となります。 printf("EastAsianCharacterThatContains0x5c'\'AndIfContains0x5cInTheEndOfString0x5c'\'"); function(); printf("Compiler recognizes left double quotation mark in this line as the end of string literal that continued from first line, and expected this message is C++ code."); 0x5c に続く文字が実際にエスケープシーケンスを指定する場合、コンパイラは、このエスケープシーケンス文字のセットを指定された単一文字に変換します。 (エスケープシーケンスの指定がない場合は、動作結果は実装時の定義に依存することになります。ただし、MSVC では、0x5c が取り除かれ、"unrecognized character escape sequence" (エスケープ シーケンスとして正しく認識できません) という警告が表示されます。) 上記の例は、文字列の最後に 0x5c バックスラッシュがあり、次の文字がダブルクオーテーションマークです。そのため、このエスケープシーケンス「\"」は、文字列データの中で 1 つのダブルクオーテーションマークに変換され、コンパイラは次のダブルクオーテーションマークが出てくるか、ファイルの終わりに達するまで、文字列データが生成され続け、エラーが発生します。 危険な文字の例: CP932 (日本語 Shift-JIS) の「表」という文字のコードは、0x955C です。CP932 では、多くの文字に 0x5C が入っています。 CP936 (簡体字中国語 GBK) において、「乗」という文字は 0x815C です。CP936 では、多くの文字に 0x5C が入っています。 CP950 (繁体字中国語 Big5) において、「功」という文字は 0xA55C です。CP950 では、多くの文字に 0x5C が入っています。 CP949 (韓国語 EUC-KR) は問題ありません。EUC-KR では、2 番目のバイトに 0x5C が使用されないためです。 __BOM が付いていない UTF-8 __ (一部のテキストエディタは BOM をシグネチャと呼びます) 東アジア系言語を UTF-8 として格納しているソースコードは、Windows CP949 (韓国語)、CP932 (日本語)、CP936 (簡体字中国語)、CP950 (繁体字中国語) 上で C++ ソースコードのコンパイルをする際は注意が必要です。 UTF-8 文字エンコードは東アジア系文字に 3 バイト使用します。0xE0 から 0xEF までが第 1 バイトに、0x80 から 0xBF までが第 2 バイトに、0x80 から 0xBF までが第 3 バイトに割り当てられています。BOM が付いていない場合、東アジア言語系 Windows のデフォルトのエンコーディングでは、UTF-8 でエンコードされた 3 バイトとその次に続く 1 バイトを、2 バイトの東アジア系エンコード文字が 2 つあるものとして認識してしまいます。具体的には、第 1 バイトと第 2 バイトを合わせて第 1 の東アジア系文字として認識し、第 3 バイトとその後に続く 1 バイト分を 2 つ目の東アジア系文字として認識するのです。 UTF-8 でエンコードされた 3 バイトに続く文字が、文字列リテラルもしくはコメントにおいて特別な意味がある場合に問題が発生する可能性があります。 インライン コメントの例: コメントを構成するテキストに東アジア系文字が奇数個あり、次に続く文字がコメント終了の記号である場合、コードが欠落してしまうため、発見しづらいバグやエラーが生じます。 /*OddNumberOfEastAsianCharacterComment*/ important_function(); /*normal comment*/ 東アジア系言語のコードページを使用した Windows 上のコンパイラは、UTF-8 でデコードされた東アジア系文字からなるコメントの最後に置かれた 1 バイトとアスタリスク (*) を、1 つの東アジア系文字として認識し、その次の文字もコメントの一部として扱ってしまいます。上記の例では、コンパイラは important_function() 関数をコメントの一部として除去してしまうのです。 この動作はたいへん危険なものでありながら、同時に、この欠落したコードを発見することは難しいのです。 シングルライン コメント: バックラッシュ '\' が東アジア系言語によるコメントの最後に置かれた場合、行が欠落しないため発見が難しいバグやエラーが発生します。 // OddNumberOfEastAsianCharacterComment\ description(); /* coder intended this line as comment, by using backslash at the end of above line */ プログラマは、コメントの最後に意図的なバックラッシュ '\' を置く必要がないため、これは大変珍しいケースです。 文字列リテラル内部: 文字列リテラル内に奇数個の東アジア系文字があり、次に続く文字が特別な意味をもつ記号である場合は、文字列が破損してエラーや警告が発生します。 printf("OddNumberOfEastAsiaCharacterString"); printf("OddNumberOfEastAsiaCharacterString%d",0); printf("OddNumberOfEastAsiaCharacterString\n"); 東アジア系言語のコードページを使う Windows では、コンパイラが、UTF-8 でデコードされた東アジア系文字からなる文字列の最後に置かれた 1 バイトとその次に置かれた 1 バイトを、1 つの東アジア系文字として認識してしまいます。運よく、コンパイラ警告 C4819 (無効にしていない場合) やエラーによって問題に気がつくこともあります。そうでない場合は、文字列が破損してしまいます。 __結論__ UTF-8 またはデフォルトの Windows による符号化スキームを使用することができますが、上記の問題について注意する必要があります。繰り返しになりますが、C++ ソース内部で文字列リテラルの使用は推奨しません。C++ ソースコード内部で東アジア系文字のコード化を使用する場合、デフォルトのコードページに必ず東アジア系のコードページを使用してください。 その他の適切な方法として、BOM 付きの UTF-8 の使用があげられます (一部のテキストエディタは BOM を Unicode シグネチャと呼びます)。 __注記__ 2010 年 2 月 18 日に、UTF-8 および UTF-16 符号化スキームをいくつかのコンパイラでテストを行いました。 PC および Xbox 360 用の MSVC や、PS3 用の gcc または slc では、UTF-8 でエンコードされたソースコード (BOM ありと BOM なしの両方) をコンパイルすることができました。 しかし UTF-16 (リトルエンディアンとビッグエンディアン) は、MSVC のみがサポートしています。 Perforce は、UTF-16 と UTF-8 の両方で機能しました。ただし p4 diff コマンドは、UTF-8 ファイルに含まれている BOM の文字を可視化してしまいます。 外部参照リンク: [Code Pages Supported by Windows](http://msdn.microsoft.com/en-us/goglobal/bb964654.aspx)