RAD Studio における Unicode
RAD Studio 入門 への移動
RAD Studio では、Unicode ベースの文字列が使用されています: つまり、型 string
は、ANSI 文字列の代わりに、Unicode 文字列(System.UnicodeString)となります。 このトピックでは、文字列を正しく処理するために開発者が把握する必要がある事項について説明します。
ANSI 文字列やワイド文字列を使用したい場合は、AnsiStringやWideString 型を使用します。
RAD Studio は完全に Unicode 対応しているため、文字列処理を行っているコードの一部をいくらか変更する必要があります。 ただし、これらの変更を最小限に抑えるために努力が続けられています。 新しいデータ型が導入されていますが、既存のデータ型が残されており、以前と同様に動作します。 Unicode 変換に関する社内での経験に基づいて、開発者の既存のアプリケーションは順調に移行できます。
その他の関連リソース:
- 『Delphi Unicode Migration for Mere Mortals: Stories and Advice from the Front Lines(一般向け Delphi Unicode 移行:最前線からの体験談と助言)』(英語版、Cary Jensen 著)
- Unicode Migration Resources for Delphi, C++Builder and RAD Studio
目次
既存の文字列型
以前からあるデータ型 AnsiString
と System.WideString
は以前と同様に機能します。
短い文字列も以前と同様に機能します。 短い文字列は 255 文字に制限され、文字カウントとシングルバイト文字データのみが含まれることに注意してください。 コード ページ情報は含まれません。 短い文字列には、特定のアプリケーション用の UTF-8 データを含むことができますが、すべてに可能という訳ではありません。
AnsiString
以前は、string は、AnsiString のエイリアスでした。 この表では AnsiString
の以前の形式でのフィールドの位置を示します。
以前の AnsiString
データ型の形式
参照カウント | 長さ | 文字列データ(バイト単位のサイズ) | NULL の終端 |
---|---|---|---|
-8 |
-4 |
0 |
Length |
RAD Studio では、AnsiString の形式が変更されました。 2 種類の新フィールド(CodePage
と ElemSize
)が追加されました。 これにより、AnsiString の形式が、新しい UnicodeString 型のものと同一になります。 (新しい形式の詳細については、「長い文字列型」参照してください。)
WideString
System.WideString
は以前は Unicode 文字データに使用されていました。 この形式は Windows の BSTR
と本質的に同じです。 WideString
は引続き COM アプリケーションでの使用に適しています。
新しい文字列型: UnicodeString
RAD Studio での string
の型は、UnicodeString
型です。
Delphi では、Char
型は WideChar
に、PChar
型は PWideChar
にマップされるようになりました。
string
が AnsiString
のエイリアス、Char
型は AnsiChar
、PChar
型は PAnsiChar
であったのが、2009 で変更されました。C++ では、[_TCHAR のマップ先] オプションで、_TCHAR
の変動する定義を制御します。これらは wchar_t
または char
のどちらかになります。
RAD Studio フレームワークおよびライブラリでは、UnicodeString
型を使用します。文字列値を、シングル バイト文字列や MBCS 文字列として表さなくなりました。
UnicodeString
データ型の形式
コードページ | 要素サイズ | 参照カウント | 長さ | 文字列データ(要素単位のサイズ) | NULL の終端 |
---|---|---|---|---|---|
-12 |
-10 |
-8 |
-4 |
0 |
Length * elementsize |
UnicodeString
は次の Delphi 構造体で表現できます:
type StrRec = record CodePage: Word; ElemSize: Word; refCount: Integer; Len: Integer; case Integer of 1: array[0..0] of AnsiChar; 2: array[0..0] of WideChar; end;
UnicodeString
には文字列の内容を示す CodePage
コード ページ フィールドと ElemSize
要素サイズ フィールドが追加されています。 UnicodeString
は他のすべての文字列型と代入のときに互換性があります。 ただし、AnsiString
と UnicodeString
の間の代入では、適切に変換(拡大や縮小)されます。 UnicodeString
型を AnsiString
型に代入することはお勧めできません。データが失われることがあります。
AnsiString
にも CodePage
フィールドと ElemSize
フィールドがあります。
UnicodeString
データは次に示す理由により UTF-16 形式が採用されています。
- UTF-16 がベースのオペレーティング システムの形式に一致している。
- UTF-16 では明示的変換または暗黙的変換を削減できる。
- Windows API を呼び出したときのパフォーマンスが向上する。
- オペレーティング システムで UTF-16 に変換する必要がない。
- BMP(Basic Multilingual Plane:基本多言語面)には既に大多数の使用言語のグリフ(字形)が含まれ、単一の UTF-16
Char
(16 ビット)に適合します。 - Unicode サロゲート ペアは MBCS(マルチバイト 文字セット)に類似していますが、さらに予測可能になり、標準化が進んでいます。
UnicodeString
では COM インターフェイスのマーシャリング用にWideString
との間でデータ損失のない暗黙的な変換が可能です。
UTF-16 の文字は 2 または 4 バイトです。したがって文字列の要素数は、必ずしも文字数と一致しません。 文字列に含まれるのが、BMP の文字のみである場合は、文字数と要素数が一致します。
UnicodeString
には次の利点があります。
- 参照カウントに対応。
- C++Builder での従来アプリケーションの問題を解決。
AnsiString
ではエンコーディング情報(コード ページ)を保持できるので、暗黙的なキャストによるデータ損失の可能性を軽減できる。- コンパイラがデータが変換される前にデータが正しいことを保証する。
WideString
は参照がカウントされません。したがって、ほとんどのタイプのアプリケーションでは UnicodeString
がさらに柔軟で効率的です(WideString
は COM アプリケーションでの使用により適しています)。
インデックス化
UnicodeString
のインスタンスは文字をインデックス化できます。 インデックス化は 1 からはじまります。AnsiString
の場合と同様です。 以下のコードについて考えてみましょう。
var C: Char; S: string; begin ... C := S[1]; ... end;
前に示した例の場合は、コンパイラは S
のデータが正しい形式であることを保証する必要があります。 文字列要素への代入が正しい型であり、インスタンスが固有(つまり参照カウントが 1)であることを UniqueString
関数の呼び出しで保証するコードをコンパイラが生成します。 前に示したコードでは、文字列に Unicode データが含まれるので、文字配列にインデックス化する前に適切な UniqueString
関数もコンパイラが呼び出す必要があります。
条件付きコンパイル
Delphi と C++Builder では、条件定義を使用して、同じソースで Unicode と非 Unicode のコードを利用できます。
Delphi
{$IFDEF UNICODE}
C++Builder
#ifdef _DELPHI_STRING_UNICODE
変更点(要約)
string
はAnsiString
ではなく、UnicodeString
にマップされます。Char
はWideChar
(1 バイトではなく 2 バイト)にマップされ、UTF-16 文字です。PChar
はPWideChar
にマップされます。- C++ では、
System::String
はUnicodeString
クラスにマップされます。
変更されていない点(要約)
AnsiString
WideString
AnsiChar
、PAnsiChar
WideChar
、PWideChar
- 暗黙的変換は以前と同様に機能します。
AnsiString
ではユーザーのアクティブ コード ページを使用します。
文字サイズに依存しないコード構造
次の操作は文字サイズに依存しません。
- 文字列の連結:
<string var> + <string var>
<string var> + <literal>
<literal> + <literal>
Concat(<string> , <string>)
- 標準文字列関数:
Length(<string>)
では文字要素数が返されます。バイト数と同じではないことがあります。SizeOf
関数ではバイト数が返されます。つまりSizeOf
とLength
の戻り値は異なることがあります。Copy(<string>, <start>, <length>)
は、Char
要素の部分文字列を返します。Pos(<substr>, <string>)
は、最初のChar
要素のインデックスを返します。
- 演算子:
<string> <comparison_operator> <string>
CompareStr()
CompareText()
...
FillChar(<struct or memory>)
FillChar(Rect, SizeOf(Rect), #0)
FillChar(WndClassEx, SizeOf(TWndClassEx), #0)
Note thatWndClassEx.cbSize := SizeOf(TWndClassEx);
- Windows API
- API はデフォルトで
WideString
("W")版を呼び出します。 PChar(<string>)
キャストは同じセマンティクスです。
- API はデフォルトで
GetModuleFileName
の例:
function ModuleFileName(Handle: HMODULE): string; var Buffer: array[0..MAX_PATH] of Char; begin SetString(Result, Buffer, GetModuleFileName(Handle, Buffer, Length(Buffer))); end;
GetWindowText
の例:
function WindowCaption(Handle: HWND): string; begin SetLength(Result, 1024); SetLength(Result, GetWindowText(Handle, PChar(Result), Length(Result))); end;
文字列の文字をインデックス化するときの例:
function StripHotKeys(const S: string): string; var I, J: Integer; LastChar: Char; begin SetLength(Result, Length(S)); J := 0; LastChar := #0; for I := 1 to Length(S) do begin if (S[I] <> '&') or (LastChar = '&') then begin Inc(J); Result[J] := S[I]; end; LastChar := S[I]; end; SetLength(Result, J); end;
文字サイズに依存するコード構造
一部の操作は文字サイズに依存します。 次のリストにある関数や機能には、可能な場合は "移植可能" 版も含まれます。 移植可能(つまり AnsiString
と UnicodeString
変数の両方で動作する)になるようにコードを同様に書き換えることができます。
SizeOf(<Char array>)
-- 移植可能なLength(<Char array>)
を使用。Move(<Char buffer>... CharCount)
-- use the portableMove(<Char buffer> ... CharCount * SizeOf(Char))
.- ストリームの読み書き -- 移植可能な
AnsiString
、SizeOf(Char)
またはTEncoding
クラスを使用。 FillChar(<Char array>, <size>, <AnsiChar>)
--#0
で埋められている場合は*SizeOf(Char)
を使用、そうでなければ、移植可能なStringOfChar
関数を使用。GetProcAddress(<module>, <PAnsiChar>)
--PWideChar
を引数に取る用意されたオーバーロード関数を使用。- ポインタ算術演算のために
PChar
を使用またはキャスト -- ファイルの先頭に、{IFDEF PByte = PChar}
を配置します(ポインタ算術演算にPChar
を使用する場合)。 または、{POINTERMATH <ON|OFF>}
Delphi コンパイラ指令を使用してすべての型付きポインタでポインタ算術演算を有効にします。要素サイズでインクリメントやデクリメントできます。
Set of Char 構造
これらの構造は場合により変更が必要です。
- <Char> in <set of
AnsiChar
> -- コードは正しく生成(>#255
の文字はセットになし)。 コンパイラによりset 式で WideChar がバイト char に縮小されました
という警告が表示されます。 ユーザーのコードにより、安全に警告をオフにできます。 他の方法として、CharinSet
関数を使用します。 - <Char> in
LeadBytes
-- グローバルのLeadBytes
セットは、MBCS ANSI ロケール用です。 UTF-16 にはまだ "リード文字"(#$D800 - #$DBFF
は上位サロゲート、#$DC00 - #$DFFF
は下位サロゲート)の概念があります。 これを変更するには、オーバーロード関数IsLeadChar
を使用します。 ANSI 版ではLeadBytes
に対してチェックします。WideChar
版は上位サロゲートか下位サロゲートかどうかをチェックします。 - 文字の分類 --
TCharacter
静的クラスを使用。Character
ユニットでは、文字を分類する関数を提供します (IsDigit
、IsLetter
、IsLetterOrDigit
、IsSymbol
、IsWhiteSpace
、IsSurrogatePair
、など)。 これらは Unicode.org にある表のデータを基にしています。
注意が必要な構造
次の問題が発生する可能性のあるコード構造を調査する必要があります。
- 明確ではない型のキャスト:
AnsiString(Pointer(foo))
- 妥当性のレビュー: 用途は何か?
- 疑いのあるキャスト -- 警告が発生:
PChar(<AnsiString var>)
PAnsiChar(<UnicodeString var>)
- 文字列の内部構造に直接操作、アクセスまたは構造を作成している。
AnsiString
など一部は内部で変更されているため、安全ではありません。StringRefCount
、StringCodePage
、StringElementSize
などの関数を使用して、文字列情報を取得します。
ランタイム ライブラリ
- オーバーロード。
PChar
が必要な関数では、現在PAnsiChar
とPWideChar
版があり、適切な関数が呼び出されます。 AnsiXXX
の各関数については配慮が必要です。SysUtils.AnsiXXXX
の各関数(AnsiCompareStr
など):string
での宣言がそのまま残り、適宜UnicodeString
に変換されます。- 下位互換性に優れています(コード変更が不要)。
AnsiStrings
ユニットのAnsiXXXX
の各関数は、SysUtils.AnsiXXXX
の各関数と同じ機能を提供していますが、AnsiString
に対してのみ動作します。 また、AnsiStrings.AnsiXXXX
関数は、AnsiString
とUnicodeString
の両方に使用できる SysUtils.AnsiXXXXの各関数よりも、暗黙的な変換を行わないため、
AnsiString
に対して、より良いパフォーマンスを提供します。
Write/Writeln
およびRead/Readln
:
PByte
-$POINTERMATH ON
とともに宣言します。 これにより配列がインデックス化され、ポインタ演算はPAnsiChar
と同様です。
- 文字列情報関数:
StringElementSize
では実際のデータ サイズが返されます。StringCodePage
では文字列データのコード ページが返されます。System.StringRefCount
では参照カウントが返されます。
- コード ページと要素サイズでの明示的変換を可能にするヘルパー関数が RTL に用意されています。 開発者が文字配列で
Move
関数を使用している場合は、要素サイズについて推測できません。 問題の大半は正しい要素サイズを保証するための RTL の正しい呼び出しをすべての RValue 参照が確実に生成することによって軽減できます。
コンポーネントとクラス
TStrings
:UnicodeString
を内部的に格納します(string
として宣言されたまま)。TWideStrings
は変更されていませんが、推奨されていません。WideString
(BSTR)を内部で使用します。TStringStream
- 書き直されています - 内部ストレージ用のデフォルト ANSI エンコーディングがデフォルト設定されています。
- エンコーディングはオーバーライドできます。
- それぞれの要素から文字列を構築するために
TStringStream
ではなくTStringBuilder
を使用することを検討してください。
TEncoding
- ユーザーのアクティブ コード ページにデフォルト設定されます。
- UTF-8 をサポートします。
- UTF-16(ビッグ エンディアンとリトル エンディアン)をサポートします。
- バイト オーダー マーク(BOM)をサポートします。
- ユーザー固有エンコーディング用の下位クラスを作成できます。
- コンポーネント ストリーミング(テキスト DFM ファイル):
- 完全に下位互換性があります。
- コンポーネント タイプ、プロパティまたは名前に ASCII-7 以外の文字が含まれる場合のみ、UTF-8 のストリームです。
- "#" でエスケープした形式では文字列プロパティ値は引続きストリーム化されます。
- UTF-8 の値も許可されます(未解決)。
- バイナリ形式の変更のみが、コンポーネント名、プロパティおよび型名に対する UTF-8 データになる見込み。
バイト オーダー マーク
BOM(Byte Order Mark:バイト オーダー マーク)はエンコーディングを示すためにファイルに追加する必要があります。
- UTF-8 では
EF BB BF
を使用します。 - UTF-16 リトル エンディアンでは
FF FE
を使用します。 - UTF-16 ビッグ エンディアンでは
FE FF
を使用します。
Unicode 対応アプリケーションの手順
次の手順を実行する必要があります:
- 文字関連と文字列関連関数を見直します。
- アプリケーションを再びビルドします。
- サロゲート ペアを見直します。
- 文字列ペイロードを見直します。
詳細については、「アプリケーションを Unicode 対応にする」を参照してください。
Delphi コンパイラの新しい警告
Delphi コンパイラにはキャストする型のエラーに関連した警告が追加されています(UnicodeString
や WideString
から AnsiString
や AnsiChar
になど)。 アプリケーションを Unicode に変換するとき、コードの問題を検出するために警告 1057 と 1058 を有効にする必要があります。
- W1050 set 式で WideChar がバイト char に変換されました (Delphi) Win32 での「set of char」は、Char 型の全体範囲の集合(セット)を定義します。Char は Win32 のバイトサイズの型であるため、256 要素を含む最大サイズの集合を定義します。.NET では、Char はワードサイズの型であり、この範囲(0~65535)は集合型の容量を超えています。
- W1057 文字列の暗黙的なキャスト ('%s' から '%s')(IMPLICIT_STRING_CAST)
暗黙的に、AnsiString
(または AnsiChar
)を、Unicode の形式(UnicodeString
または WideString
)に変換する必要があることを、コンパイラが検出した場合にあ、この警告がでます。 (メモ: デフォルトではこの警告が有効です)
暗黙的に Unicode の一形式(UnicodeString
または WideString
)を AnsiString
(または AnsiChar
)にキャストする必要があることをコンパイラが検出したときにこの警告が出ます。 この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: デフォルトではこの警告が有効です)
AnsiString
(または AnsiChar
)を Unicode の一形式(UnicodeString
または WideString
)にプログラマが明示的にキャストしていることをコンパイラが検出したときにこの警告が出ます。 (メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります。)
Unicode の一形式(UnicodeString
または WideString
)を AnsiString
(または AnsiChar
)にプログラマが明示的にキャストしていることをコンパイラが検出したときにこの警告が出ます。 この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります。)
推奨事項
- ソース ファイルを UTF-8 形式で維持する。
- ファイルはソースが正しいコード ページでコンパイルされている場合に限り、ANSI 形式を維持できます。 プロジェクト > オプション... > C++ コンパイラ > 拡張 を選択し、[コード ページ]オプション([その他のオプション] 下にある)を使用して、正しいコード ページを設定します。
- UTF-8 BOM をソース ファイルに書き込みます。 ソース コントロール管理システムでこれらのファイルをサポートしていることを確認します(多くのシステムではサポート)。
- コードが
AnsiString
またはAnsiChar
であるときに IDE リファクタリングを実行します(コードの移植性は不変)。 - 静的コードの見直し:
- コードではデータを渡すだけか
- コードでは単純な文字のインデックス化を実行しているか
- すべての警告に注意し、エラーとして対応する。
- 疑いのあるポインタのキャスト
- 暗黙的なキャストと明示的なキャスト(見込み)
- コードの目的を判別する
- 文字列(
AnsiString
)をバイトの動的配列として使用しているか。 この場合は、代わりに移植可能なTBytes
型(Byte
の配列)を使用します。 - ポインタの算術演算を有効にするために
PChar
のキャストを使用しているか。 この場合は、代わりにPByte
にキャストし、$POINTERMATH ON
を使用します。
- 文字列(
関連項目
- UTF-8 変換ルーチン
- 『Delphi Unicode Migration for Mere Mortals: Stories and Advice from the Front Lines(一般向け Delphi Unicode 移行:最前線からの体験談と助言)』(英語版、Cary Jensen 著)
- 「Delphi Unicode ワールド パートI: Unicode とは? なぜ必要なのか? そして、Delphi でどのような作業を行うのか?」(英語)
- 「Delphi Unicode World パート II: RTL の新機能と Unicode をサポートするクラス」
- RAD Studio 2010 移行センター
- アプリケーションを Unicode 対応にする
- C++ アプリケーションの Unicode 対応
- _TCHAR マッピング (C++)
- コマンド コンソールで Unicode を使用する
- Unicode ファイルに対する TEncoding の使用
- C++ での Delphi AnsiString コード ページ指定の扱い方
- System.UnicodeString
- System.AnsiString