2015年11月4日水曜日

UTF8文字列とUTF16文字列の相互変換

FPC 2.x で、UTF8文字列とUTF16文字列を相互に変換するには、次のように、UTF8Encode 関数、UTF8Decode 関数を使うのが一般的でした。










ソースファイルのコードページが UTF8 (デフォルト)の場合:

var
  s: string;
  ws: widestring;
begin
  s := 'あいうえお';
  ws := UTF8Decode(s);
  s := UTF8Encode(ws);
end;

FPC 2.x では、string 型の変数に ANSI 文字列が格納されているのか、それとも UTF8 文字列が格納されているのかを管理するのはプログラマのみの責任でした。
FPC 3.0 では最新の Delphi と同じく、 string 型にコードページという概念が追加されましたが、FPC 3.0 でも、string 型のデフォルトは UTF8 ですので、UTF8Encode 関数、UTF8Decode 関数を使用した上記コードは正しく動作します。もっとも、FPC 3.0 では、コードページという概念の追加により FPC 側が string 型の変数に何のコードページの文字列が格納されているのかを知ることができるようになったことから、コードページが異なる文字列の代入時に FPC が文字コードの変換を自動的に行うようになりました。なお、自動変換なんて余計なお世話だよ、という方は、 RawByteString 型を使用すれば自動変換を回避できます。

よって、FPC 3.0 では上記の例は次のように書くことができます。
なお、FPC 3.0 では widestring 型と unicodestring 型が厳密には異なるものとされるので、特に拘らない場合は unicodestring 型を使うのが無難です。ユニコード=16ビットではないので疑問の残るネーミングではありますが、Delphi のメーカーがそのようにネーミングしたので仕方ありません。


var
  s: string;
  s16: unicodestring;
begin
  s := 'あいうえお';
  s16 := unicodestring(s);
  s := string(s16);
end;

Decode や Encode という、どっちがどっちかよくわからない命名に悩まなくてよくなるだけでなく、ソースファイルのコードページが UTF8 でない場合にも対応できるようになります。
ちなみに、ソースファイルのコードページは、IDE でソースファイルを右クリックして、File Settings -> Encodeing で変更できます。

2015年10月29日木曜日

ユニコード文字列の取り扱い

Lazarusの標準文字コードはUTF8です。したがって、森鷗外の「鷗」のように、ユニコードにはあるがシフトJISにはない文字もあっさり使用できます。例えば、Label.Caption := '森鷗外'; のように。
そして、Lazarus には Delphi と同じく、ユニコード文字列を取り扱うためのルーチンが豊富に用意されています。したがって、自分でコーディングしてユニコード文字列を処理する場面では困ることはないと思います。

問題は、LCLやRTLなどにある既存のルーチンでのユニコード文字列の取り扱いです。代表的なものはファイルのパスで、例えば 多くのオブジェクトに搭載されている LoadFromFile メソッドには、パスをシフトJIS(ANSI)で渡さなければいけません。 したがって、 LoadFromFile(UTF8ToSys(filename)) のようにして、UTF8をシフトJISに変換して渡す必要があります。当然、ファイル名に、"森鷗外.txt"のようなユニコードにはあるがシフトJISにはない文字が含まれていた場合はエラーになります。これは、内部で Windows API をコールする際に、シフトJISしか使えないAPI、いわゆるA系のAPIを使っていることが主たる原因です。
この問題を解決するため、lazutf8classes や FileUtil ユニットなどに、ユニコード文字列に対応したルーチンが準備されています。その多くは、ParamStrUTF8やFileExistsUTF8、TFileStreamUTF8、TStringListUTF8 など、標準名+"UTF8"という名前がつけられています。これらが Windows API を使用する場合は、ユニコード文字列に対応したいわゆるW系のAPIを呼び出してくれます。
しかし、これで問題が全て解決するわけではありません。先程の例である LoadFromFile メソッドのほとんどでは内部でTFileStreamUT8ではなくTFileStreamを使用しているため、結局ユニコードのパスは使用できません。また、UTF8対応の関数を使う必要があるかという判断には、その関数がどういう処理をするのか、シフトJISとUTF8の違いはなんなのか、という知識が求められることになります。

以上は Lazarus 1.4 でのお話です。 Lazarus 1.4 は、FPC 2.6 をコンパイラとして使用するのですが、FPC 2.6 の基本ルーチンがANSIしか処理できないため上記のようになってしまっています。FPCに依存せずに Lazarus側が全て自前でユニコード処理をしてしまえばよいわけですが現実的ではないので、lazutf8classes や FileUtil ユニットなど最低限のものを苦肉の策としてLazarus側で準備しているわけです。
これに対し、現在RC2がリリースされている FPC 3.0 は、ユニコード標準対応を謳っています。誤解をおそれずに簡単にいってしまえば、多くの基本ルーチンで、引数がANSI文字列型の場合はA系APIを、ユニコード文字列型の場合はW系APIを適切にコールしてくれるようになります。これによって、上記の問題は完全に解決されます。FPC 3.0がLazarusの標準コンパイラになるのはおそらく Lazarus 2.0 からでしょう。待てない方は最新truncで試すことができます。

FPC 2.6 用に作成した自前のソースを FPC 3.0 でビルドすると、UTF8ToSys関数、 lazutf8classes や FileUtil ユニットなどのUTF8対応ルーチンを使用している箇所でWarningが出るので修正すべき場所がすぐ分かります。UTF8ToSys関数はもはや不要になりますし、TFileStreamUTF8などはUTF8を取ってTFileStreamを使えば良いことになります。


2015年10月28日水曜日

TXmlPropStorageとTIniPropStorageは要らない子

misc タブに TXmlPropStorage と TIniPropStorage というコンポーネントがある。
TFormのSessionProperties で指定したプロパティ(ちなみにフォームのプロパティだけではなくフォーム上にある子コントロールのプロパティも指定できる)の値をフォームクローズ時にファイルに保存し、次回フォーム作成時にその値をファイルから復元してくれるというコンポーネントである。
最初は、便利なコンポーネントがあって嬉しいな、という気持ちになるが、いざアプリに使おうとなると、余程の手抜きアプリでないかぎり使えないものであることが分かる。エラー処理が皆無なのである。
もちろん、ソースを追ってコンポーネントの仕組みなどを調べて、エラー処理をうまく追加することは可能かもしれないが、そんな手間をかけるくらいなら、TStrings クラスなどを使って、フォーム作成時にプロパティの値をファイルに保存し、フォームクローズ時にファイルから復元するコードを書いたほうが早い。TStrings クラスの便利さと、Lazarus のイベント処理の容易さが産んだ悲劇といえるかもしれない。