RAD Studio における Unicode
RAD Studio 入門 への移動
RAD Studio では、ANSI ベースの文字列が Unicode ベースの文字列に変更されました。 型 string は、Unicode 文字列(System.UnicodeString)になっています。 このトピックでは、文字列を正しく処理するために開発者が把握する必要がある事項について説明します。
ANSI 文字列やワイド文字列を使用したい場合は、AnsiStringやWideString 型を使用します。
RAD Studio は完全な Unicode 準拠です。文字列処理を含むコードの一部では変更が必要です。 ただし、これらの変更を最小限に抑えるために努力が続けられています。 新しいデータ型が導入されていますが、既存のデータ型が残されており、以前と同様に動作します。 Unicode 変換に関する社内での経験に基づいて、開発者の既存のアプリケーションは順調に移行できます。
その他の関連リソース:
- 一般向け Delphi Unicode 移行: Stories and Advice from the Front Lines, by Cary Jensen
- RAD Studio 2010 移行センター
目次 |
既存の文字列型
以前からあるデータ型 AnsiString と System.WideString は以前と同様に機能します。
短い文字列も以前と同様に機能します。 短い文字列は 255 文字に制限され、文字カウントとシングルバイト文字データのみが含まれることに注意してください。 コード ページ情報は含まれません。 短い文字列には、特定のアプリケーション用の UTF-8 データを含むことができますが、すべてに可能という訳ではありません。
AnsiString
以前は、string は、AnsiString のエイリアスでした。 この表では AnsiString の以前の形式でのフィールドの位置を示します。
AnsiString データ型の形式
| 参照カウント | 長さ | 文字列データ(バイト単位のサイズ) | NULL の終端 |
|---|---|---|---|
-8 |
-4 |
0 |
Length |
RAD Studio では、AnsiString.PChar の形式が変更されました。 2 種類の新フィールド(CodePage と ElemSize)が追加されました。 これにより AnsiString 型と新しい UnicodeString 型に対する形式が同一になります。
WideString
System.WideString は以前は Unicode 文字データに使用されていました。 この形式は Windows の BSTR と本質的に同じです。 WideString は引続き COM アプリケーションでの使用に適しています。
新しい文字列型: UnicodeString
RAD Studio での string の型は、UnicodeString 型です。
Delphi では、Char 型は WideChar に、PChar 型は PWideChar にマップされるようになりました。
メモ:2009 より前のバージョンでは、stringがAnsiStringのエイリアス、Char型はAnsiChar、PChar型はPAnsiCharであったのが、2009 で変更されました。
C++ では、[_TCHAR のマップ先] オプションで、_TCHAR の変動する定義を制御します。これらは wchar_t または char のどちらかになります。
VCL では 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 関数もコンパイラが呼び出す必要があります。
C++Builder
C++ Builder の UnicodeString クラスでは Delphi と同様の自動変換セマンティクスを使用できます。 AnsiString のパラメータが必要な既存の VCL イベント ハンドラでは、変換が必要に応じて実行されるので開発者が意識する必要ありません。 さらに、これによりユーザーは独自のスケジュールで完全な Unicode 対応に順次移行できます。
ただし、場合により自動変換では、期待しない結果が発生することがあります。 VCL の文字列型は UnicodeString です(AnsiString ではない)。 ただし、下位互換性のため、UnicodeString インスタンスのワイド データを縮小変換することによって、メソッド UnicodeString::t_str() では const char* が返ります(const wchar_t* ではない)。 これにより、結果として予期しない動作が起きることがあります。コードで、t_str() の呼び出しにより元のデータが破壊されることを予測しない場合です。 この動作を確認できるのは、ユーザー インターフェイス(TListItem など)で元のデータを表示するコードです。
たとえば、次のように TListView で表示したデータが、メソッドの最終行で t_str() の呼び出し後に破損することがあります:
void ProcessSelectedItem(const char* item);
void __fastcall TForm6::ListView1DblClick(TObject *Sender)
{
int index = ListView1->Selected->Index;
TListItem *ClassItem = ListView1->Items->Item[index];
ProcessSelectedItem(ClassItem->Caption.t_str());
}
条件付きコンパイル
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 または memory>)-
FillChar(Rect, SizeOf(Rect), #0) -
FillChar(WndClassEx, SizeOf(TWndClassEx), #0)WndClassEx.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)-- 移植可能なMove(<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静的クラスを使用。CharacterCharacter ユニットには文字を分類する関数(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 を有効にする必要があります。
暗黙的に、AnsiString(または AnsiChar)を、Unicode の形式(UnicodeString または WideString)に変換する必要があることを、コンパイラが検出した場合にあ、この警告がでます。 (メモ: デフォルトではこの警告が有効です)。
暗黙的に Unicode の一形式(UnicodeString または WideString)を AnsiString(または AnsiChar)にキャストする必要があることをコンパイラが検出したときにこの警告が出ます。 この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: デフォルトではこの警告が有効です)。
AnsiString(または AnsiChar)を Unicode の一形式(UnicodeString または WideString)にプログラマが明示的にキャストしていることをコンパイラが検出したときにこの警告が出ます。 (メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります。)
Unicode の一形式(UnicodeString または WideString)を AnsiString(または AnsiChar)にプログラマが明示的にキャストしていることをコンパイラが検出したときにこの警告が出ます。 この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります)。
推奨事項
- ソース ファイルを UTF-8 形式で維持する。
- Delphi 2005、2006、 2007 でサポート。
- ファイルはソースが正しいコード ページでコンパイルされている場合に限り、ANSI 形式を維持できます(
–codepageコンパイラ スイッチを使用可能)。 - UTF-8 BOM をソース ファイルに書き込みます。 ソース コントロール管理システムでこれらのファイルをサポートしていることを確認します(多くのシステムではサポート)。
- コードが
AnsiStringまたはAnsiCharであるときに IDE リファクタリングを実行します(コードの移植性は不変)。 - 静的コードの見直し:
- コードではデータを渡すだけか
- コードでは単純な文字のインデックス化を実行しているか
- すべての警告に注意し、エラーとして対応する。
- 疑いのあるポインタのキャスト
- 暗黙的なキャストと明示的なキャスト(見込み)
- コードの目的を判別する
- 文字列(
AnsiString)をバイトの動的配列として使用しているか。 この場合は、代わりに移植可能なTBytes型(Byteの配列)を使用します。 - ポインタの算術演算を有効にするために
PCharのキャストを使用しているか。 この場合は、代わりにPByteにキャストし、$POINTERMATH ONを使用します。
- 文字列(
関連項目
- 一般向け Delphi Unicode 移行:最前線からの体験談と助言」 by Cary Jensen(英語)
- 「Delphi Unicode ワールド パートI: Unicode とは? なぜ必要なのか? そして、Delphi でどのような作業を行うのか?」(英語)
- 「Delphi Unicode World パート II: RTL の新機能と Unicode をサポートするクラス」
- RAD Studio 2010 移行センター
- Delphi 2009 および C++Builder 2009 の新機能
- アプリケーションを Unicode 対応にする
- C++ アプリケーションの Unicode 対応
- _TCHAR マッピング (C++)
- コマンド コンソールで Unicode を使用する
- Unicode ファイルに対する TEncoding の使用
- C++ での Delphi AnsiString コード ページ指定の扱い方
- System.UnicodeString
- System.AnsiString