Programming]Delphi2007(またはそれ以前)とUnicodeDelphi2007(またはそれ以前)のUnicode対応はどうなっているのでしょう?IDEDelphi8以降のガリレオIDEのコードエディタはUnicodeに対応しています。内部的な処理はUTF-8で行われています。入出力可能な文字エンコーディングは以下の通りです。EndianUTF-8AnsiUTF-16がサポートされていないのが残念です。IDE自身は殆どがAnsiで処理されており、OSのロケールを変更するとIDEが文字化けを起こしてしまいます。UTF-8の4バイト文字(UTF-16のサロゲートペア)をコードエディタに入力する事は可能ですが、キャレット位置やBackSpaceでの削除等の挙動がおかしくなります。いかなるOSでもUTF-8の4バイト文字(UTF-16のサロゲートペア)はコードエディタ以外のIDEで正しく表示されません(オブジェクトインスペクタ等)。Control"を使わなければ、UIによるUnicode文字の入出力は困難です。RTLVCL同様に内部でA系のAPIが呼ばれている事があります。文字コード変換関数はUTF-16のサロゲートペアやUTF-8の4バイト文字を正しく処理してくれませんし、ShowMessageでWideStringに格納したサロゲートペアを表示する事もできません。UCS-2(BMP)の範囲のUnicodeしか扱わないのであればなかなか有用です。RTLはDelphi6辺りからUnicodeを普通に扱えるようになってきます。Unicodeをどの変数型で扱うか?UCS-2/UTF-16を扱うならWideString、UTF-8を扱うならUTF8String、UCS-4/UTF-32を扱うならUCS4Stringが使えます。WideStringはヘルプによるとBSTR型と互換性があるようです。BSTR型というのは16bitヌル終端ですが、PascalのStringのように先頭に文字列サイズを持った型で、同じくヌル終端のPChar等とは違い、文字列中にヌルを保持できます。事前にメモリ確保も不要で、String(AnsiString)へ相互代入が可能です(その際にAnsi<->Unicode変換を伴います)。"#$xxxx"のように4桁のコントロール文字列で定数を指定する事が可能です。このように使い勝手はString(AnsiString)と非常によく似ていますが、参照カウントはありません。WideStringはW系APIを呼び出すときやCOMで文字列を渡すときに使われ、String(AnsiString)からの変換が前提となっているような雰囲気があります。PCharで文字列の長さを取得するにはStrLen()がありますが、PWideCharにはStrLen()がなかったり、StrAlloc()で文字列用メモリを確保できなかったりと、微妙に使い勝手がよくありません。WideStringのまま文字列操作が可能な関数も決定的に不足しています。定義されており、実質String(AnsiString)と何ら変わりはありません。このため、引数がString(AnsiString)/UTF8Stringと異なるだけのオーバーロードされた関数やメソッドを作成する事ができません。逆に、UTF8Stringを引数や戻り値に持つ関数をString(AnsiString)で処理する事が可能です。"String(AnsiString)でEUCを扱う"のと"String(AnsiString)でUTF-8を扱う"事に決定的な違いはない、という事です。UTF8StringにUTF-8を格納したからと言って、Ansiと名の付く関数を使って文字列処理を行える訳ではありません(例外はあります)。単なるLongWordの配列なので、String(AnsiString)やWideStringのように手軽には扱えません。PCharでのように自前で配列を初期化をする必要があり、文字列サイズ変更にもSetLength()を伴います(配列なのでメモリ確保は不要)。UCS4Stringのまま文字列操作が可能な関数は一切用意されていませんので、コンバート用途だと思った方がいいでしょう。32bitヌル終端です。総括このような状況なので、Delphi2007(またはそれ以前)で本格的なUnicodeアプリケーションを作成するは少々骨の折れる作業を伴います。本格的なUnicodeアプリケーションを簡単に作成したいのであれば、Delphi2009を待つ事になります。UnicodeIDEDelphi2009のコードエディタは当然ながらUnicodeに対応しています。他のガリレオIDE同様、内部処理はUTF-8で行われています。入出力可能な文字エンコーディングは以下の通りです。EndianUTF-8AnsiこれまたUTF-16がサポートされていないのが残念です。IDEはUnicodeで処理されており、OSのロケールを変更してもIDEが文字化けを起こす事はありません。多言語化アプリケーションのデバッグを行う際にOSのロケールを変更しても、IDEがUnicode化されてるためにその影響を受けない、という事です。UTF-8の4バイト文字(UTF-16のサロゲートペア)をコードエディタに入力する事ももちろん可能で、キャレット位置やBackSpaceでの削除等の挙動も正常です。IDE自身のフォントを変更する機構がないため、残念ながらXPに於いてはコードエディタ以外のIDEでUTF-8の4バイト文字(UTF-16のサロゲートペア)を正しく表示できませんが、Vistaであればオブジェクトインスペクタ等にUTF-8の4バイト文字(UTF-16のサロゲートペア)を普通に入力する事が可能です。VCLすべてUnicode化されています。XPに於いてIDEがUTF-8の4バイト文字(UTF-16のサロゲートペア)を表示できない事があるのと同様に、XPでは一部のコンポーネントでUTF-8の4バイト文字(UTF-16のサロゲートペア)を表示できない事があります。RTLUnicodeを扱うためのRTLは用意されています。Delphi2007(またはそれ以前)で文字コード変換関数はUTF-16のサロゲートペアやUTF-8の4バイト文字を正しく処理しないものがありましたが、Delphi2009では正常に動作します。RTLについては若干の注意点があるので、それは後述する事にします。Unicodeをどの変数型で扱うか?UCS-2/UTF-16を扱うならString(UnicodeString)/WideString、UTF-8を扱うならUTF8String、UCS-4/UTF-32を扱うならUCS4Stringが使えます。UCS4StringについてはDelphi2007とほぼ同様です。DelphiのString型はAnsiStringからUnicodeStringになりました。UnicodeStringは参照カウント付きのWideStringに近いものと考えるといいでしょう。WideString型はそのまま残っています。String(UnicodeString)と代入互換性があるため、WideString型のままで文字列操作する事も楽になりました。WideString型への関数も増えており、Delphi2007には存在しなかったStrLen()等も使えます。UTF8Stringは少し趣が違っています。定義され、"CP_UTF8のコードページを持ったAnsiString"という事になっています。Delphi2007同様、Ansiと名の付く関数を使って文字列処理を行える訳ではありません(例外はあります)。この件に関しては後のトピックで補足します。総括"Delphi2009のクセ"を知っていれば新規にUnicodeアプリケーションを作るのはあっけない位に簡単です。既存のプロジェクトのマイグレーション(移行)についてはUnicodeだけの知識だけでは足りないので、その辺りも含めて以降のトピックで紹介していきます。ように定義する事が可能になりました。コードページには1..65534が指定できます。AnsiString(0)はデフォルトコードページを保持するAnsiString型です。"AnsiString"とした場合にはこのAnsiString型になります。AnsiString(65535)はRawByteStringとして定義されています。これはコードページを保持しない/代入に文字コード(コードページ)変換を伴わないAnsiString型です。UTF8Stringはこの新しいAnsiStringの機能を利用して定義されています。異なるコードページを持つAnsiString間で代入を行うと、"暗黙の文字コード(コードページ)変換"が行われます。以下のコードを見て下さい。...たったこれだけの事で、今まで面倒だった文字コードの変換が可能です。暗黙の文字コード(コードページ)変換が行われるため、AnsiStringのコードページが何であれ、ShowMessageは文字化けせずに正しく表示されます。便利になった反面、思わぬ所に落とし穴があります。このようなコードは時としてバグを生みます。引数と戻り値の型はAnsiString(デフォルトコードページ)であり、日本語環境ではCP932ですが、海外ではそうだとは限らないからです。任意のコードページのAnsiString変数を渡すと、AnsiString(デフォルトコードページ)との暗黙の文字コード(コードページ)変換が行われるため、思った結果にならない事があります。このように、RawByteStringを利用します。RawByteStringは代入時に文字コード(コードページ)変換を伴わないからです。Delphi2007のプロジェクトをDelphi2009へ移行する場合、AnsiString文字列を扱う関数は"String->AnsiString"へ置換するのではなく、"String->RawByteString"へ置換すべきだと思われます。変数のコードページを知るにはStringCodepage()が利用できます。扱う文字列のコードページが不定な場合には、最初にRawByteStringの変数を用意しておき、SetCodePage()で変数のコードページを動的に変更する事もできます。ここで注意が必要なのですが、SetCodePage()は変数が空の場合...つまり、文字列の長さが0の場合にはコードページを変更できません。Showmessage(IntToStr(StringCodepage(S)));end;正確には空文字列が格納された(=ペイロードが存在しない)AnsiString変数は常にコードページ0(CP_ACP)になります。コードページ0はデフォルトコードページを返すので、空のAnsiString変数をStringCodePage()した場合にはデフォルトのコードページが返ります。中身が空のファイルの文字コード判定が不可能(BOMを持つものは除く)なのと似ています。空のUnicodeString変数をStringCodePage()した場合には普通に1200(CP1200=UTF-16LE)が返ります。ペイロードが存在しないという状況になるのか?は別トピックの"各種文字列の実際"をご覧頂けると解るかと思います。同名の関数がUnicodeString版とAnsiString版の両方が用意されている場合、AnsiString版の関数はAnsiStringsユニットに格納されている事があります。Delphi2007ではそれらの関数はSysUtils/Strutilsに存在します。よって、AnsiStringで文字列操作を行う処理がプロジェクトに含まれる場合には、真っ先にAnsiStringsをusesすべきです。AnsiStringsをusesしなかった場合には、UnicodeStringとAnsiStringには代入互換性がありますので、UnicodeString版が使われてしまい、思ったような結果を得られない事があります。この際に「代入互換性の警告」は出るのですが、コンパイルそのものは通るため見落としがちです。※製品版では上記の表と異なる事があります。関数/手続きがオーバーロードされていれば、コンパイラは引数の型に最適な関数/手続きを呼び出しますが、"別の名前空間に存在するオーバーロードされた関数"があり、適切な名前空間をuses句に記述しないと、代入互換性がある引数の型を持つオーバーロードされた関数/手続きが呼び出される事があります。ExcludeTrailingPathDelimiter(aPath);end;例えば、上記の場合ExcludeTrailingPathDelimiter()はUnicodeString版が呼び出されます。正しくは以下のようになります。ExcludeTrailingPathDelimiter(aPath);end;オーバーロードされていない"名前空間が別になっている同名の関数/手続き"は"名前空間.関数/手続き名"として呼び出す必要があります。"別の名前空間に存在するオーバーロードされた関数/手続き"の曖昧さを回避するために、"別の名前空間にオーバーロードされた関数/手続きが存在する"場合には名前空間を関数/手続き名の前に明示的に指定する癖を付けておくと代入互換性のケアレスミスを防ぐ事ができると思います。AnsiStrings.ExcludeTrailingPathDelimiter(aPath);end;コントロール文字列コントロール文字列には注意が必要です。5/6桁のコントロール文字列Unicodeのコードポイントと同等になります。U+2000Bは"#$2000B"と記述可能です。"#$xxxx"のような4桁のコントロール文字列UTF-16のイメージのままです。#$DC0B"のようにしてサロゲートペアを扱う事ができます。2桁のコントロール文字列("#$00..#$7F")AnsiCharのコントロール文字列とみなされます。2桁のコントロール文字列("#$80..#$FF")コンパイラ指令によって挙動が異なります。<ON|OFF>}指令というのがあり、これは通常OFFです。OFFだと、"#$80".."#$FF"をAnsiCharとみなし、ONだとUnicodeCharだとみなします。OFF>}になっている必要があります。桁数を間違うと、Ansi<->Unicode変換が行われたり、意図しない文字コードを指定する事になってしまいます。in演算子Delphi2007は以下のようなコードが普通に使われていました。...end;しかし、Delphi2009ではこのコードは通りません。これを回避するにはCharInSet()を使います。...end;CharInSet()はString(UnicodeString)にもAnsiStringにも使えます。関数の名前と挙動が一致しないプロジェクト移行を容易にするため、旧来AnsiString用だった関数の殆どにUnicodeString版が用意してあります。引数をAnsiStringからUnicodeStringにしてしまうと名前と挙動が一致しなくなるものがあります。ByteType()。この関数はSで指定された文字列のIndexバイト目が1バイト文字なのか、2バイト文字の1バイト目/2バイト目なのかを判断します。この関数のUnicodeString版は、Sで指定された文字列のIndexワード目が1ワード文字なのか、サロゲートペアの1ワード目/2ワード目なのかを判断します。「バイトインデックスを指定する訳ではない/バイトの種類を返す訳ではない」事に注意が必要です。関数の機能からすればByteType()のUnicodeString版は"WordType()"が妥当なトコロですが、既存プロジェクトのマイグレーション(移行)の事を考えれば、致仕方ない所もあります。した不幸な命名の関数は他にもありますが、"~Byte~"と名の付く関数には"~Element~"という別名を持つものがあります(実際には"~Element~"の別名として"~Byte~"が用意されています...逆ですね)。これらの関数を利用していたのなら、可読性の面から、今後は"Element"系の関数を利用する事をオススメします。CharToElementIndex文字単位で文字列を操作する場合Delphi2007では"文字単位で文字列を操作する"場合にはマルチバイト文字を考慮してくれる"Ansi~"と名の付く関数を利用します。Delphi2009の"Ansi~"と名の付くUnicode版関数は、引数がAnsiStringからUnicodeStringになるので、「"Ansi~"と名の付くUnicode版関数でサロゲートペアを処理できる」...訳ではありません。ByteType()のAnsiString版はマルチバイトを、UnicodeString版はマルチワードを判定するので、「"Ansi~"と名の付くUnicode版関数はサロゲートペアを考慮する」と思いがちですが、現状ではそうなっていません。"サロゲートペアを考慮するための関数"は用意されていますが、"サロゲートペアを考慮した文字単位の文字列操作を行う関数"は基本的に存在しないと思った方がいいと思います。余談ですが、Delphi2007の"AnsiLeftStr/AnsiMidStr/AnsiReverseStr/AnsiRightStr"とDelphi2009のAnsiString版同関数は関数内部で"Ansi->Unicode->Ansi"変換を伴うので、コードポイントの置換が発生します(詳しくは"Ansi->Unicode->Ansi変換をやっちゃ駄目ってどういう事?"を参照して下さい)。UTF8Stringを"Ansi~"と名の付く関数に渡しても文字列操作がうまく行えない事があります。UTF-8は最大4(6)バイトのマルチバイト文字ですが、"マルチバイトは最大で2バイト"という前提の処理があるためです。同様に最大4バイトのマルチバイト文字であるGB18030(CP54936)等も処理できないと思われます。これらのコードページはUnicodeとラウンドトリップ変換(Unicodeへ変換して、元のコードページに変換)しても何も失われないので、一旦UnicodeStringへ変換してから文字列処理し、再度変換するというロジックで問題ないと思われます。UTF8Stringを"Ansi~"と名の付く関数に渡しても文字列操作がうまく行えない理由は上記のとおりですが、例外的に"AnsiLeftStr/AnsiMidStr/AnsiReverseString/AnsiRightStr"はそれなりに正しく動作します。先述の通り、関数内部で"Ansi->Unicode->Ansi"変換を伴うからです(結果オーライ?)。これらの関数に於いてもやはりサロゲートペアは考慮されません。一般的な文字列操作"Ansi->Unicode->Ansi変換をやっちゃ駄目ってどういう事?"の件を考慮する必要性がない場合には、"文字列処理はUnicodeで行い、AnsiString(UTF8String等のコードページ指定含む)が必要な場合にはコンバートで対処する"というのが基本的なスタンスだと思います。上の方でAnsiStringに関するトピックに書いた通りですが、現状に於いてDelphi2009でのAnsiString文字列操作は予期せぬ結果になりかねず、処理が繁雑になりがちだからです。「そうもいかない場合がある」時だけ、AnsiStringでの文字列操作を行うべきでしょう。"Unicode->AnsiString"への変換は、コードページを指定したAnsiStringを用意し、代入を行うだけなのですから。