RawByteStringの使い方

投稿者:: Yusuke Konno

概要: Delphi 2009で新設されたRawByteString型についての解説を行います。

Delphi 2009ではVCL/RTLからIDEに至るまで全面的にUnicode化がなされました。その中でこれまでの製品はデフォルトでString型にマップされていたAnsiStringにも拡張が及んでいます。AnsiStringは従来であればデフォルトのコードページでのみ動作するものでしたが、Delphi 2009では任意のコードページを指定することが可能になり、ロケールに依存しないAnsiStringの取り扱いが可能になりました。

 それに伴ってAnsiStringの一種としてRawByteStringという新しい型が新設されました。このRawByteString型は非常に特殊な挙動をするため、取り扱いには注意が必要です。本稿ではRawByteStringについての解説を行います。

    RawByteStringの必要性

    AnsiString型の落とし穴

Delphi 2009でAnsiString向けの関数を作ろうとすると、まず思い浮かぶのが下記のようなコードです。

function HogeHoge(S: AnsiString): AnsiString;
begin
  Result := S + S;
end;

一見何の問題も無さそうに見えますが、何気なく宣言しているAnsiStringという型に思わぬ落とし穴があります。AnsiStringは特にコードページを指定しなければデフォルトのコードページ(0)が使われます。このデフォルトのコードページは環境(ロケール)依存です。ロケールが日本であれば当然CP932(Shift_JIS)になります。ということは上記のコードでは引数と戻り値のAnsiString型のコードページはCP932ということになります。この関数にCP20932(EUC_JP)のような他のコードページの値を代入すると、自動的にデフォルトのコードページ(CP932)へと変換されます。

日本ではデフォルトのコードページがCP932であるため、CP20932から変換されても問題の深刻さがよく分かりません。しかしこれが異なる環境・異なるデフォルトコードページになればとてつもなく深刻な問題が待っています。例えばデフォルトのコードページがCP936(簡体字中国語)になれば、当然日本語の文字列は失われることになります。

function HogeHoge(S: AnsiString): AnsiString;
begin
  Result := S + S;
end;

{略}
type
  gb2312 = type AnsiString(936);//CP936(簡体中国語)
var
  S: gb2312;
begin
  S := #$C7;
  S := S + #$EB;
  S := S + #$CE;
  S := S + #$CA;
  S := S + #$B9;
  S := S + #$AB;
  S := S + #$B9;
  S := S + #$B2;
  S := S + #$C6;
  S := S + #$FB;
  S := S + #$B3;
  S := S + #$B5;
  S := S + #$D5;
  S := S + #$BE;
  S := S + #$D4;
  S := S + #$DA;
  S := S + #$C4;
  S := S + #$C4;
  S := S + #$C0;
  S := S + #$EF;
  ShowMessage(HogeHoge(S));
end;

 上記コードでは例として、cp936(日本語訳:すみません、バス停はどこですか?)という中国語の文字列を変数Sに代入しています(暗黙の文字列変換を避けるため、代入のコードが長くなっています)これらの文字列は全てCP936に存在する正しいコードポイントです。しかし、HogeHogeの引数の型がAnsiStringであるため、日本語環境ではCP932としてAnsiStringが動作します。このため、CP932にはない文字列は損失していまいます。実際の実行結果は以下の通りです。

Hide image
rawbyte_error_cp936

つまり、AnsiStringを引数や戻り値に指定してしまうと、余計なコードページ変換を伴う恐れがあるばかりか、環境依存で役に立たない関数が出来上がってしまいます。

    解決策はRawByteString

 RawByteStringはコードページの変換を行わない特殊なAnsiStringです。RawByteString = type AnsiString($FFFF)と定義されています。先ほどの関数をRawByteStringで置き換えてみると下記のようになります。

function HogeHoge(S: RawByteString): RawByteString;
begin
  Result := S + S;
end;

この関数ではコードページの変換は行われません。つまりCP20932の値を渡せばCP20932のまま処理され、戻り値のコードページもCP20932となります。先ほどと違ってデフォルトのコードページの影響は受けなくなります。

基本的にコードページに依存しないAnsiString向けの関数や手続きを作成する場合にはRawByteStringを使うべきです。特に検索やバイト単位での操作したい時にRawByteStringは威力を発揮するでしょう。

    RawByteString使用上の注意点

    基本的な使途

 先にも述べたとおり、RawByteStringはコードページ変換をされてしまっては困る場合に効果を発揮する特殊なAnsiString型です。主としてその使途は関数や手続きの引数、または関数の戻り値として指定されるべきです。変数宣言、参照渡し、明示的なキャストなどに使うと意図しない動作をする可能性が非常に高いため、これらの使い方は極力行わないことを推奨します。

 Help Update 1を適用したヘルプにも以下のような記載があります。(RTM時のものにはこのトピックは存在しません。)

“RawByteString 型の変数の宣言は、使用するとしてもできるだけ少なくする必要があります。これを使用すると、未定義の動作が発生し、データが失われる可能性があります。 “

    引っかかりやすいポイント

    UnicodeStringを代入しても、コードページ1200とはならない

 RawByteStringにUnicodeStringを代入することは可能ですが、コードページがUTF-16LEを表す1200にはなりません。つまり、RawByteStringはUTF-16LEとして動作しません。この場合にはデフォルトのコードページが用いられます。日本であればCP932(Shift_JIS)が用いられると言うことです。つまり、Shift_JISに無い文字列がUnicodeStringに含まれていた場合には、RawByteString代入時にそれらはデータロスしてしまうことになります。

function HogeHoge(S: RawByteString): RawByteString;
begin
  Result := S + S;
end;

{略}

var
  S: String;
begin
  S := #$D842#$DFB7#$91CE#$5C4B;//サロゲートペアを含む文字列
  S := HogeHoge(S);//サロゲートペアが失われる
  ShowMessage(S);//サロゲートペアの箇所が??になる
end;

上記のコードではHogeHogeに変数Sを与えた際に、RawByteStringがCP932として駆動してしまっています。このため、変数Sに含まれていたサロゲートペアが失われてしまう結果になります。

    SetCodePage

 Delphi 2009ではSystem.pasにSetCodePageという手続きが追加されています。この手続きはRawByteStringのコードページを任意のコードページに変更するというものです。

 この手続きの存在を知っていると、あたかもRawByteStringがどんなコードページでも駆動する万能なAnsiString型であると勘違いしがちですが、実際は違います。まず、上記にもある通りUTF-16LEとして駆動させることは不可能です。また、RawByteStringの保持する文字列が空である場合にはコードページがデフォルトのものとなります。そして、SetCodePageは文字列が空である場合にはコードページを変更できないため、予めRawByteStringに特定のコードページを付与しておき、そこから文字列を代入するということができません。

var
  S: RawByteString;
begin
  S := '';
  SetCodePage(S,65001,False);//コードページをUTF-8(CP65001)に設定する
  ShowMessage(IntToStr(StringCodePage(S)));
end;

 上記コードではShowMessageで65001が表示されるべきところが、932と表示されてしまいます。

 あくまで、RawByteStringードページ変換しない特殊なAnsiStringということを忘れてはいけません。

    旧バージョンとの互換性

 当然のことではありますが、RawByteStringはDelphi 2009にしかないので過去のプロダクトでは動作しません。Delphi 2009と2007以前のバージョンとで使い回せるライブラリなどを作る際には、$IF DEF Unicodeで分岐して、Delphi 2009のみで使用されるように工夫する必要があります。

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