Delphi Unicodeワールド パートII: RTLの新機能と、Unicodeをサポートするクラス

投稿者:: Tomohiro Takahashi

概要: この記事では、Unicode文字列の処理に役立つDelphi 2009のランタイムライブラリの新機能について解説します。

    はじめに

パートIでは、Unicodeの領域の全ての文字を扱えるようになることで、UnicodeサポートによりDelphiユーザーがどれくらい大きなメリットを得られるか、UnicodeString型の基礎および、それがDelphiでどのように使用されているのかを見ました。

パートIIでは、Unicodeや一般的な文字列処理をサポートするDelphiランタイムライブラリの幾つかの新機能について見ていきます。

    TCharacterクラス

Delphi 2009のRTLにはTCharacterという名前の新しいクラスがあり、これはCharacterユニットに含まれます。これはsealedなクラスで、全てstaticなクラス関数で構成されています。開発者はTCharacterのインスタンスを作成するべきではなく、そのstaticなクラスメソッドを直接呼び出すだけです。それらのクラス関数は次のように数多くの処理を行います。

  • 大文字から小文字に変換する
  • ある文字が特定の文字種であるかどうか(例えば、その文字が数字なのか句読点であるか等)を判定する

TCharacterは、Unicodeコンソーシアムの標準規格を利用します。

開発者は文字集合に対してこれまで行ってきた数多くの処理に、TCharacterクラスを使用できます。例えば、次のコードは

uses
Character;

begin
if MyChar in [‘a’...’z’, ‘A’...’Z’] then
begin
  ...
end;
end;

以下のように簡単に置き換え可能です。

uses
  Character;

begin
if TCharacter.IsLetter(MyChar) then
begin
    ...
end;
end;

Characterユニットには、TCharacterの各関数の機能をラップしたスタンドアロンの関数が数多く含まれているので、もしシンプルな関数呼び出しのほうが好みで場合あれば、上のコードは次のように記述できます。

uses
  Character;

begin
if IsLetter(MyChar) then
begin
    ...
end;
end;

このように、文字に対する操作やチェック作業のほとんど全てにTCharacterクラスを使用できます。

さらにTCharacterには、ある文字がサロゲートペアの上位サロゲートなのか下位サロゲートなのかを判定するクラスメソッドが含まれています。

    TEncodingクラス

また、Delphi 2009のRTLにはTEncodingという名前の新しいクラスがあります。その目的は特定のキャラクタエンコーディングを定義することで、それにより、特定の状況で使用したいエンコーディングの種類をVCLに指定することができます。

例えば、ファイルに書き出したいテキストを含んだTStringListのインスタンスがあるとします。以前なら次のように記述したでしょう。

begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’);  
  ...
end; 

デフォルトのANSIエンコーディングを使用してファイルが出力されていました。このコードは依然として正しく動作します。これまでと同様にANSI文字列のエンコーディングを使ってファイルに出力されますが、今やDelphiはUnicodeの文字列データをサポートしていますので、開発者は特定のエンコーディングを利用して文字列データを書き出したいと思うでしょう。そのため、SaveToFileとLoadFromFileは使用するエンコーディングを定義する第2引数をオプションとして受け取るようになりました。

begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’, TEncoding.Unicode);  
  ...
end; 

上のコードを実行すると、ファイルはUnicode(UTF-16)でエンコードされたテキストファイルとして出力されます。

TEncodingはまた、あるエンコーディングのバイト配列を別のエンコーディングへ変換し、ある文字列や文字の配列におけるバイトや文字に関する情報を受け取り、あらゆる文字列をバイト配列(TBytes)へ変換します。必要となるその他の機能は、ある文字列や文字の配列の特定のエンコーディングに関連するものです。

TEncodingクラスには以下のような、あるエンコーディングに対応するTEncodingのシングルトンインスタンスにアクセスするためのクラスプロパティを含まれます。

    class property ASCII: TEncoding read GetASCII;
    class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;
    class property Default: TEncoding read GetDefault;
    class property Unicode: TEncoding read GetUnicode;
    class property UTF7: TEncoding read GetUTF7;
    class property UTF8: TEncoding read GetUTF8;

DefaultプロパティはANSIの現在のコードページを指します。UnicodeプロパティはUTF-16を指します。

また、TEncodingは次のクラス関数を含みます。

class function TEncoding.GetEncoding(CodePage: Integer): TEncoding;

これは、パラメータに渡されたコードページを表すTEncodingのインスタンスを返します。

さらに、次の関数も含んでいます。

function GetPreamble: TBytes;

これは、あるエンコーディングに対応する正しいBOMを返します。

TEncodingは、Encodingという.NETのクラスのインターフェースと互換性も持っています。

    TStringBuilder

RTLにはTStringBuilderという名前のクラスも含まれるようになりました。その目的はその名前に表されており、文字列を「構築する」よう設計されたクラスです。TStringBuilderには、ある文字列を追加したり、置換したり、挿入したりするための数多くのオーバーロード関数があります。この文字列構築用のクラスは、様々な種類のデータ型から1つの文字列を生成するのを容易にしてくれます。全てのAppend/Insert/Replace関数はTStringBuilderのインスタンスを返すので、1つの文字列を生成するのにそれらを簡単に繋げることができます。

例として、複雑なFormat文の代わりにTStringBuilderを使用してもかまいません。例えば、次のようなコードを記述することもできます。

procedure TForm86.Button2Click(Sender: TObject);
var
  MyStringBuilder: TStringBuilder;
  Price: double;
begin
  MyStringBuilder := TStringBuilder.Create('');
  try
    Price := 1.49;
    Label1.Caption := MyStringBuilder.Append('The apples are $').Append(Price). 
             ÄAppend(' a pound.').ToString;
  finally
    MyStringBuilder.Free;
  end;
end;

TStringBuilderは、StringBuilderという.NETのクラスのインターフェースと互換性も持っています。

    新しい文字列型を宣言する

Delphi 2009のコンパイラでは、あるコードページを表現できる文字列型を独自に定義できます。数多くのコードページが利用可能です。(MSDNには、利用可能なコードページに関する素晴らしい概説記事があります) 例えば、ANSI-Cyrillicを表現できる文字列型が必要な場合、次のように宣言できます。

type
  // The code page for ANSI-Cyrillic is 1251
  CyrillicString = type Ansistring(1251);

この新しい文字列型は、キリルの文字コードを表現できる文字列になります。

    RTLの追加のUnicodeサポート機能

RTLには、Unicode文字列の利用をサポートする数多くのルーチンが追加されました。

    StringElementSize

StringElementSizeは、ある文字列における要素(code point)の一般的なサイズを返します。次のコードを見てください。

procedure TForm88.Button3Click(Sender: TObject);
var
  A: AnsiString;
  U: UnicodeString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('The ElementSize for an AnsiString is: ' + IntToStr(StringElementSize(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('The ElementSize for an UnicodeString is: ' + IntToStr(StringElementSize(U)));
end;

上のコードの結果は以下のようになります。

The ElementSize for an AnsiString is: 1
The ElementSize for an UnicodeString is: 2

    StringCodePage

StringCodePageは、ある文字列に対応するコードページをWord値で返します。

次のコードを見てください。

procedure TForm88.Button2Click(Sender: TObject);
type
  // The code page for ANSI-Cyrillic is 1251
  CyrillicString = type AnsiString(1251);
var
  A: AnsiString;
  U: UnicodeString;
  U8: UTF8String;
  C: CyrillicString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('AnsiString Codepage: ' + IntToStr(StringCodePage(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('UnicodeString Codepage: ' + IntToStr(StringCodePage(U)));
  U8 := 'This is a UTF8string';
  Memo1.Lines.Add('UTF8string Codepage: ' + IntToStr(StringCodePage(U8)));
  C := 'This is a CyrillicString';
  Memo1.Lines.Add('CyrillicString Codepage: ' + IntToStr(StringCodePage(C)));
end;

上のコードの結果、以下のように出力されます。

The Codepage for an AnsiString is: 1252
The Codepage for an UnicodeString is: 1200
The Codepage for an UTF8string is: 65001
The Codepage for an CyrillicString is: 1251

    Unicode用のその他のRTL機能

あるコードページの文字列を別のものに変換するために数多くのルーチンが用意されています。次のものが含まれます。

UnicodeStringToUCS4String
UCS4StringToUnicodeString
UnicodeToUtf8
Utf8ToUnicode

さらにRTLは、エンコーディングを持たない文字列型としてRawByteStringと呼ばれる型も宣言しています。

  RawByteString = type AnsiString($FFFF);

RawByteString型の目的は、コードページによる変換を一切行わずに任意のコードページの文字列を渡せるようにすることです。これは、バイト単位での文字列検索のように、特定のエンコーディングにこだわらないルーチンに極めて便利です。一般的に、文字列のコードページに無関係に文字列を処理するルーチンのパラメータにはRawByteStringを使用すべき、ということになるでしょう。RawByteString型のインスタンスを宣言すると、未定義の動作やデータの欠落を引き起こしかねないので、滅多に使うべきではないでしょう。

一般的には、様々な文字列型には相互の代入互換性があります。

例えば、次のコードは期待通りに動作します。

MyUnicodeString := MyAnsiString;

AnsiStringの内容をUnicodeStringに変換しています。一般的に、ある文字列を別の文字列に代入することが可能ですし、可能であればコンパイラは変換に必要な作業を行ってくれます。

しかし、変換の中にはデータの欠落を起こすものもあります。Unicodeのデータを含む文字列型を、Unicodeを含まない文字列型へ変換する際には注意する必要があります。例えば、UnicodeStringをAnsiStringに代入することができますが、実行時、現在のANSIコードページへのマッピングを持たない文字をUnicodeStringが含んでいる場合には、その文字は変換時に失われます。次のコードを見てください。

procedure TForm88.Button4Click(Sender: TObject);
var
  U: UnicodeString;
  A: AnsiString;
begin
  U := 'This is a UnicodeString';
  A := U;
  Memo1.Lines.Add(A);
  U := 'Добро пожаловать в мир Юникода с использованием Дельфи 2009!!';
  A := U;
  Memo1.Lines.Add(A);
end;

現在のOSのコードページが1252である場合、上のコードは以下のように出力します。

This is a UnicodeString
????? ?????????? ? ??? ??????? ? ?????????????? ?????? 2009!!

見てのとおり、キリル文字はWindows-1252へのマッピングを持たないため、そのUnicodeStringをAnsiStringに代入した際に情報が失われました。AnisStringのコードページでは表現できない文字をUnicodeStringが含んでいるため、結果はめちゃくちゃなものになりました。それらの文字は、UnicodeStringをAnsiStringに代入した際、クエスチョンマーク(?)に置き換えられて失われました。

    SetCodePage

SetCodePageはSystem.pasユニットで次のように宣言されています。

procedure SetCodePage(var S: AnsiString; CodePage: Word; Convert: Boolean);

これは、あるAnsiStringに新しいコードページを設定するRTLの新しい関数です。オプションのConvertパラメータは、文字列の内部データ自体を指定したコードページへ変換するかどうかを表します。ConvertパラメータがFalseの場合は、その文字列のコードページが単に変更されるだけです。ConvertパラメータがTrueの場合には、渡された文字列の内部データが、指定したコードページへ変換されます。

SetCodePageは、十分に注意した上で、控え目に使うべきです。もしそのコードページが既存の文字列データに実際に適合しない場合には(例えばConvertがFalseに設定された)、予測できない結果が生じます。また、もし文字列の既存データが変換され、その新しいコードページが元の文字を表現できない場合には、データの欠落が発生します。

    文字列からTBytesを取得する

RTLには、文字列からバイト配列を取り出すためにオーバーロードされたルーチンの集合も含まれます。パートIIIで見ますが、データバッファとして文字列を利用するのではなく、代わりにTBytesの利用をお勧めします。RTLは、様々な文字列型をパラメータとして受け取る複数のオーバーロード版 BytesOf() を用意しており、とても簡単になっています。

    まとめ

Delphi 2009のランタイムライブラリは、新しいUnicodeStringを完全にサポートできるようになりました。Unicodeの文字列を処理・変換し、コードページを管理し、旧バージョンからの移行を容易にしてくれる、新しいクラスやルーチンが搭載されています。

パートIIIでは、コードを確実にUnicode対応にするために必要な特定のコーディング作業について解説します。

次からのサーバー応答:: ETNASC03