RAD Studio における Unicode

提供: RAD Studio
移動先: 案内検索

RAD Studio 入門 への移動


RAD Studio では、Unicode ベースの文字列が使用されています: つまり、型 string は、ANSI 文字列の代わりに、Unicode 文字列(System.UnicodeString)となります。 このトピックでは、文字列を正しく処理するために開発者が把握する必要がある事項について説明します。

ANSI 文字列やワイド文字列を使用したい場合は、AnsiStringWideString 型を使用します。

RAD Studio は完全に Unicode 対応しているため、文字列処理を行っているコードの一部をいくらか変更する必要があります。 ただし、これらの変更を最小限に抑えるために努力が続けられています。 新しいデータ型が導入されていますが、既存のデータ型が残されており、以前と同様に動作します。 Unicode 変換に関する社内での経験に基づいて、開発者の既存のアプリケーションは順調に移行できます。

その他の関連リソース:

既存の文字列型

以前からあるデータ型 AnsiStringSystem.WideString は以前と同様に機能します。

短い文字列も以前と同様に機能します。 短い文字列は 255 文字に制限され、文字カウントとシングルバイト文字データのみが含まれることに注意してください。 コード ページ情報は含まれません。 短い文字列には、特定のアプリケーション用の UTF-8 データを含むことができますが、すべてに可能という訳ではありません。

AnsiString

以前は、string は、AnsiString のエイリアスでした。 この表では AnsiString の以前の形式でのフィールドの位置を示します。

以前の AnsiString データ型の形式

参照カウント 長さ 文字列データ(バイト単位のサイズ) NULL の終端
-8
-4
0
 Length

RAD Studio では、AnsiString の形式が変更されました。 2 種類の新フィールド(CodePageElemSize)が追加されました。 これにより、AnsiString の形式が、新しい UnicodeString 型のものと同一になります。 (新しい形式の詳細については、「長い文字列型」参照してください。)

WideString

System.WideString は以前は Unicode 文字データに使用されていました。 この形式は Windows の BSTR と本質的に同じです。 WideString は引続き COM アプリケーションでの使用に適しています。

新しい文字列型: UnicodeString

RAD Studio での string の型は、UnicodeString 型です。

Delphi では、Char 型は WideChar に、PChar 型は PWideChar にマップされるようになりました。

メモ: 2009 より前のバージョンでは、stringAnsiString のエイリアス、Char 型は AnsiCharPChar 型は 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 は他のすべての文字列型と代入のときに互換性があります。 ただし、AnsiStringUnicodeString の間の代入では、適切に変換(拡大や縮小)されます。 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 

変更点(要約)

  • stringAnsiString ではなく、UnicodeString にマップされます。
  • CharWideChar(1 バイトではなく 2 バイト)にマップされ、UTF-16 文字です。
  • PCharPWideChar にマップされます。
  • C++ では、System::StringUnicodeString クラスにマップされます。

変更されていない点(要約)

  • AnsiString
  • WideString
  • AnsiCharPAnsiChar
  • WideCharPWideChar
  • 暗黙的変換は以前と同様に機能します。
  • AnsiString ではユーザーのアクティブ コード ページを使用します。

文字サイズに依存しないコード構造

次の操作は文字サイズに依存しません。

  • 文字列の連結:
    • <string var> + <string var>
    • <string var> + <literal>
    • <literal> + <literal>
    • Concat(<string> , <string>)
  • 標準文字列関数:
    • Length(<string>) では文字要素数が返されます。バイト数と同じではないことがあります。 SizeOf 関数ではバイト数が返されます。つまり SizeOfLength の戻り値は異なることがあります。
    • 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 that WndClassEx.cbSize := SizeOf(TWndClassEx);
  • Windows API
    • API はデフォルトで WideString("W")版を呼び出します。
    • PChar(<string>) キャストは同じセマンティクスです。


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;

文字サイズに依存するコード構造

一部の操作は文字サイズに依存します。 次のリストにある関数や機能には、可能な場合は "移植可能" 版も含まれます。 移植可能(つまり AnsiStringUnicodeString 変数の両方で動作する)になるようにコードを同様に書き換えることができます。

  • SizeOf(<Char array>) -- 移植可能な Length(<Char array>) を使用。
  • Move(<Char buffer>... CharCount) -- use the portable Move(<Char buffer> ... CharCount * SizeOf(Char)).
  • ストリームの読み書き -- 移植可能な AnsiStringSizeOf(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 ユニットでは、文字を分類する関数を提供します (IsDigitIsLetterIsLetterOrDigitIsSymbolIsWhiteSpaceIsSurrogatePair、など)。 これらは Unicode.org にある表のデータを基にしています。

注意が必要な構造

次の問題が発生する可能性のあるコード構造を調査する必要があります。

  • 明確ではない型のキャスト:
    • AnsiString(Pointer(foo))
    • 妥当性のレビュー: 用途は何か?
  • 疑いのあるキャスト -- 警告が発生:
    • PChar(<AnsiString var>)
    • PAnsiChar(<UnicodeString var>)
  • 文字列の内部構造に直接操作、アクセスまたは構造を作成している。 AnsiString など一部は内部で変更されているため、安全ではありません。 StringRefCountStringCodePageStringElementSize などの関数を使用して、文字列情報を取得します。

ランタイム ライブラリ

  • オーバーロード。 PChar が必要な関数では、現在 PAnsiCharPWideChar 版があり、適切な関数が呼び出されます。
  • AnsiXXX の各関数については配慮が必要です。
    • SysUtils.AnsiXXXX の各関数(AnsiCompareStr など):
      • string での宣言がそのまま残り、適宜 UnicodeString に変換されます。
      • 下位互換性に優れています(コード変更が不要)。
    • AnsiStrings ユニットの AnsiXXXX の各関数は、SysUtils.AnsiXXXX の各関数と同じ機能を提供していますが、AnsiString に対してのみ動作します。 また、AnsiStrings.AnsiXXXX 関数は、AnsiStringUnicodeString の両方に使用できる SysUtils.AnsiXXXX の各関数よりも、暗黙的な変換を行わないため、AnsiString に対して、より良いパフォーマンスを提供します。
  • Write/Writeln および Read/Readln:
    • 引続き ANSI/OEM コード ページに対して変換します。
    • コンソールはいずれにせよ、ほとんどの場合 ANSI または OEM です。
    • 従来のアプリケーションとの互換性のために用意されています。
    • TFDD(テキスト ファイル デバイス ドライバ):
      • TTextRecTFileRec を提供しています。
      • ファイル名は WideChar ですが、前の例のとおり、データは ANSI/OEM です。
    • Unicode ファイルの入出力には、TEncodingTStrings を使用します。
  • PByte - $POINTERMATH ON とともに宣言します。 これにより配列がインデックス化され、ポインタ演算は PAnsiChar と同様です。
  • コード ページと要素サイズでの明示的変換を可能にするヘルパー関数が RTL に用意されています。 開発者が文字配列で Move 関数を使用している場合は、要素サイズについて推測できません。 問題の大半は正しい要素サイズを保証するための RTL の正しい呼び出しをすべての RValue 参照が確実に生成することによって軽減できます。

コンポーネントとクラス

  • TStringsUnicodeString を内部的に格納します(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 対応アプリケーションの手順

次の手順を実行する必要があります:

  1. 文字関連と文字列関連関数を見直します。
  2. アプリケーションを再びビルドします。
  3. サロゲート ペアを見直します。
  4. 文字列ペイロードを見直します。

詳細については、「アプリケーションを Unicode 対応にする」を参照してください。

Delphi コンパイラの新しい警告

Delphi コンパイラにはキャストする型のエラーに関連した警告が追加されています(UnicodeStringWideString から AnsiStringAnsiChar になど)。 アプリケーションを Unicode に変換するとき、コードの問題を検出するために警告 1057 と 1058 を有効にする必要があります。

  • W1050 設定式で WideChar がバイト文字に変換されました (Delphi) Win32 での「文字のセット」は、Char 型の全範囲に及ぶセットを定義します。Char は、Win32 ではバイト サイズ型であるため、最大サイズで 256 の要素を格納するセットを定義します。.NET では、Char は 1 単語サイズの型で、その範囲(0 ~ 65535)は、セット型の容量を超えます。
  • W1057 Implicit string cast from '%s' to '%s' (IMPLICIT_STRING_CAST) [文字列の暗黙的なキャスト ('%s' から '%s')] (IMPLICIT_STRING_CAST)AnsiString(または AnsiChar)を Unicode のなんらかの形式(UnicodeString または WideString)に、暗黙的に変換しなければならないケースを、コンパイラが検知した際に、この警告が発せられます。(メモ: この警告は最終的には、デフォルトで有効になる予定です)。
  • W1058 Implicit string cast with potential data loss from '%s' to '%s' (IMPLICIT_STRING_CAST_LOSS)[データ損失の可能性がある文字列の暗黙的なキャスト ('%s' から '%s')] (IMPLICIT_STRING_CAST_LOSS) Unicode のなんらかの形式(UnicodeStringWideString)を AnsiString(または AnsiChar)に、暗黙的に変換してダウンキャストしなければならないケースを、コンパイラが検知した際に、この警告が発せられます。この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: この警告は最終的には、デフォルトで有効になる予定です)。
  • W1059 Explicit string cast from '%s' to '%s' (EXPLICIT_STRING_CAST) [文字列の明示的なキャスト ('%s' から '%s')] (EXPLICIT_STRING_CAST) AnsiString(または AnsiChar)を Unicode のなんらかの形式(UnicodeString または WideString)に、プログラマが明示的にキャストしていることを、コンパイラが検知した際に、この警告が発せられます。(メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります。)
  • W1060 Explicit string cast with potential data loss from '%s' to '%s' (EXPLICIT_STRING_CAST_LOSS)[データ損失の可能性がある文字列の明示的なキャスト ('%s' から '%s')] (EXPLICIT_STRING_CAST_LOSS) プログラマが、Unicode のなんらかの形式(UnicodeStringWideStringAnsiString(または AnsiChar)に、明示的にダウンキャストしなければならないケースを、コンパイラが検知した際に、この警告が発せられます。この変換では情報が損失する可能性があります。文字列が変換される対象のコード ページで表現できない文字が文字列にあるからです。 (メモ: デフォルトではこの警告は出ません。潜在的な問題を特定するためにのみ使用する必要があります。)

推奨事項

  • ソース ファイルを UTF-8 形式で維持する。
    • ファイルはソースが正しいコード ページでコンパイルされている場合に限り、ANSI 形式を維持できます。 プロジェクト > オプション... > C++ コンパイラ > 拡張 を選択し、[コード ページ]オプション([その他のオプション] 下にある)を使用して、正しいコード ページを設定します。
    • UTF-8 BOM をソース ファイルに書き込みます。 ソース コントロール管理システムでこれらのファイルをサポートしていることを確認します(多くのシステムではサポート)。
  • コードが AnsiString または AnsiChar であるときに IDE リファクタリングを実行します(コードの移植性は不変)。
  • 静的コードの見直し:
    • コードではデータを渡すだけか
    • コードでは単純な文字のインデックス化を実行しているか
  • すべての警告に注意し、エラーとして対応する。
    • 疑いのあるポインタのキャスト
    • 暗黙的なキャストと明示的なキャスト(見込み)
  • コードの目的を判別する
    • 文字列(AnsiString)をバイトの動的配列として使用しているか。 この場合は、代わりに移植可能な TBytes 型(Byte の配列)を使用します。
    • ポインタの算術演算を有効にするために PChar のキャストを使用しているか。 この場合は、代わりに PByte にキャストし、$POINTERMATH ON を使用します。

関連項目