Delphi2007(またはそれ以前)とUnicode

 Delphi2007(またはそれ以前)のUnicode対応はどうなっているのでしょう?

IDE
 Delphi8以降のガリレオIDEのコードエディタはUnicodeに対応しています。内部的な処理はUTF-8で行われています。入出力可能な文字エンコーディングは以下の通りです。
 UTF-16がサポートされていないのが残念です。

 IDE自身は殆どがAnsiで処理されており、OSのロケールを変更するとIDEが文字化けを起こしてしまいます。UTF-8の4バイト文字(UTF-16のサロゲートペア)をコードエディタに入力する事は可能ですが、キャレット位置やBackSpaceでの削除等の挙動がおかしくなります。また、いかなるOSでもUTF-8の4バイト文字(UTF-16のサロゲートペア)はコードエディタ以外のIDEで正しく表示されません(オブジェクトインスペクタ等)。

VCL
 内部でA系のAPIが呼ばれており、"TNT Unicode Control"を使わなければ、UIによるUnicode文字の入出力は困難です。

RTL
 VCL同様に内部で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のまま文字列操作が可能な関数も決定的に不足しています。

 UTF8Stringは "UTF8String = type string;" と定義されており、実質String(AnsiString)と何ら変わりはありません。 このため、引数がString(AnsiString)/UTF8Stringと異なるだけのオーバーロードされた関数やメソッドを作成する事ができません。逆に、UTF8Stringを引数や戻り値に持つ関数をString(AnsiString)で処理する事が可能です。"String(AnsiString)でEUCを扱う"のと"String(AnsiString)でUTF-8を扱う"事に決定的な違いはない、という事です。 UTF8StringにUTF-8を格納したからと言って、Ansiと名の付く関数を使って文字列処理を行える訳ではありません(例外はあります)。

 UCS4Stringは "UCS4String = array of UCS4Char;" と定義されており、さらにUCS4Charは "UCS4Char = type LongWord;" と定義されています。単なるLongWordの配列なので、String(AnsiString)やWideStringのように手軽には扱えません。PCharでのように自前で配列を初期化をする必要があり、文字列サイズ変更にもSetLength()を伴います(配列なのでメモリ確保は不要)。UCS4Stringのまま文字列操作が可能な関数は一切用意されていませんので、コンバート用途だと思った方がいいでしょう。32bitヌル終端です。

総括
 このような状況なので、Delphi2007(またはそれ以前)で本格的なUnicodeアプリケーションを作成するは少々骨の折れる作業を伴います。本格的なUnicodeアプリケーションを簡単に作成したいのであれば、Delphi2009を待つ事になります。

See Also:

Delphi 2009 以降と Unicode

IDE
 Delphi2009のコードエディタは当然ながらUnicodeに対応しています。他のガリレオIDE同様、内部処理はUTF-8で行われています。入出力可能な文字エンコーディングは以下の通りです。
 これまた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のサロゲートペア)を表示できない事があります。

RTL
 Unicodeを扱うための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は少し趣が違っています。"UTF8String = type AnsiString(CP_UTF8);" と定義され、"CP_UTF8のコードページを持ったAnsiString"という事になっています。Delphi2007同様、Ansiと名の付く関数を使って文字列処理を行える訳ではありません(例外はあります)。この件に関しては後のトピックで補足します。

総括
 "Delphi2009のクセ"を知っていれば新規にUnicodeアプリケーションを作るのはあっけない位に簡単です。既存のプロジェクトのマイグレーション(移行)についてはUnicodeだけの知識だけでは足りないので、その辺りも含めて以降のトピックで紹介していきます。

See Also:

Delphi 2009 以降と文字列処理 (1)

AnsiString
 AnsiStringは "新しいAnsiString型 = type AnsiString(コードページ);" のように定義する事が可能になりました。コードページには1..65534が指定できます。AnsiString(0)はデフォルトコードページを保持するAnsiString型です。単に"AnsiString"とした場合にはこのAnsiString型になります。AnsiString(65535)はRawByteStringとして定義されています。これはコードページを保持しない/代入に文字コード(コードページ)変換を伴わないAnsiString型です。UTF8Stringはこの新しいAnsiStringの機能を利用して定義されています。

 異なるコードページを持つAnsiString間で代入を行うと、"暗黙の文字コード(コードページ)変換"が行われます。以下のコードを見て下さい。

type
  SJISString = type AnsiString(932);   // Shift-JIS(CP932) 
  EUCJString = type AnsiString(20932); // EUC-JP(CP20932) 
var
  SJIS: SJISString;  
  EUCJ: EUCJString; 
  A: AnsiString;    // 日本語環境ならデフォルトはCP932 
  U8: UTF8String;  
begin
  A    := 'あいうえお';
  SJIS := A;
  EUCJ := A;
  U8   := A;

  ShowMessage(SJIS);
  ShowMessage(EUCJ);
  ShowMessage(U8);
end;  

 ...たったこれだけの事で、今まで面倒だった文字コードの変換が可能です。暗黙の文字コード(コードページ)変換が行われるため、AnsiStringのコードページが何であれ、ShowMessageは文字化けせずに正しく表示されます。

 ですが、便利になった反面、思わぬ所に落とし穴があります。

function test(S: AnsiString): AnsiString;
begin
  result := S + S;
end;  

 このようなコードは時としてバグを生みます。何故なら、引数と戻り値の型はAnsiString(デフォルトコードページ)であり、日本語環境ではCP932ですが、海外ではそうだとは限らないからです。また、任意のコードページのAnsiString変数を渡すと、AnsiString(デフォルトコードページ)との暗黙の文字コード(コードページ)変換が行われるため、思った結果にならない事があります。これを回避するには

function test(S: RawByteString): RawByteString;
begin
  result := S + S;
end;  

 このように、RawByteStringを利用します。RawByteStringは代入時に文字コード(コードページ)変換を伴わないからです。Delphi2007のプロジェクトをDelphi2009へ移行する場合、AnsiString文字列を扱う関数は"String->AnsiString"へ置換するのではなく、"String->RawByteString"へ置換すべきだと思われます。

 変数のコードページを知るにはStringCodepage()が利用できます。また、扱う文字列のコードページが不定な場合には、最初にRawByteStringの変数を用意しておき、SetCodePage()で変数のコードページを動的に変更する事もできます。

var
  S: RawByteString;
begin
  S := 'あいう';

  // コンバートを伴わずにコードページを変更
  SetCodePage(S, 932, False); 
  ShowMessage(IntToStr(StringCodePage(S)));

  S := 'あいう';

  // コンバートを伴ってコードページを変更
  SetCodePage(S, CP_UTF8);  // or SetCodePage(S, CP_UTF8, True);
  ShowMessage(IntToStr(StringCodePage(S)));
end;  

 ここで注意が必要なのですが、SetCodePage()は変数が空の場合...つまり、文字列の長さが0の場合にはコードページを変更できません

var
  S: RawByteString;
begin
  S := '123';

  // EUC-JP(CP20932)に変更
  SetCodepage(S, 20932);
  Showmessage(IntToStr(StringCodepage(S)));

  // Shift-JISに変更
  SetCodepage(S, 932);
  Showmessage(IntToStr(StringCodepage(S)));

  S := '';

  // UTF-8に変更?
  SetCodepage(S, CP_UTF8);
  Showmessage(IntToStr(StringCodepage(S)));
end;

 正確には空文字列が格納された(=ペイロードが存在しない)AnsiString変数は常にコードページ0(CP_ACP)になります。コードページ0はデフォルトコードページを返すので、空のAnsiString変数をStringCodePage()した場合にはデフォルトのコードページが返ります。丁度、中身が空のファイルの文字コード判定が不可能(BOMを持つものは除く)なのと似ています。空のUnicodeString変数をStringCodePage()した場合には普通に1200(CP1200=UTF-16LE)が返ります。

 何故、ペイロードが存在しないという状況になるのか?は別トピックの"各種文字列の実際"をご覧頂けると解るかと思います。

AnsiStrings名前空間
 同名の関数がUnicodeString版とAnsiString版の両方が用意されている場合、AnsiString版の関数はAnsiStringsユニットに格納されている事があります。Delphi2007ではそれらの関数はSysUtils/Strutilsに存在します。よって、AnsiStringで文字列操作を行う処理がプロジェクトに含まれる場合には、真っ先にAnsiStringsをusesすべきです。AnsiStringsをusesしなかった場合には、UnicodeStringとAnsiStringには代入互換性がありますので、UnicodeString版が使われてしまい、思ったような結果を得られない事があります。この際に「代入互換性の警告」は出るのですが、コンパイルそのものは通るため見落としがちです。

Functions/Procedures NameSpace AnsiString NameSpace UnicodeString/
WideString
AdjustLineBreaks     SysUtils
AnsiCompareFileName AnsiStrings SysUtils
AnsiCompareStr AnsiStrings SysUtils
AnsiCompareText AnsiStrings SysUtils
AnsiContainsStr AnsiStrings StrUtils
AnsiContainsText AnsiStrings StrUtils
AnsiDequotedStr AnsiStrings SysUtils
AnsiEndsStr AnsiStrings StrUtils
AnsiEndsText AnsiStrings StrUtils
AnsiExtractQuotedStr SysUtils SysUtils
AnsiIndexStr AnsiStrings StrUtils
AnsiIndexText AnsiStrings StrUtils
AnsiLastChar SysUtils/
AnsiStrings
SysUtils
AnsiLeftStr StrUtils
AnsiLowerCase AnsiStrings SysUtils
AnsiLowerCaseFileName AnsiStrings SysUtils
AnsiPos AnsiStrings SysUtils
AnsiMatchStr AnsiStrings StrUtils
AnsiMatchText AnsiStrings StrUtils
AnsiMidStr StrUtils
AnsiPos AnsiStrings AnsiStrings
AnsiQuotedStr AnsiStrings SysUtils
AnsiReplaceStr AnsiStrings StrUtils
AnsiReplaceText AnsiStrings StrUtils
AnsiResemblesText StrUtils
AnsiReverseString StrUtils
AnsiRightStr StrUtils
AnsiSameStr AnsiStrings SysUtils
AnsiSameText AnsiStrings SysUtils
AnsiStrAlloc SysUtils    
AnsiStartsStr AnsiStrings StrUtils
AnsiStartsText AnsiStrings StrUtils
AnsiStrComp SysUtils SysUtils
AnsiStrIComp SysUtils SysUtils
AnsiStrLastChar SysUtils SysUtils
AnsiStrLComp SysUtils SysUtils
AnsiStrLIComp SysUtils SysUtils
AnsiStrLower SysUtils SysUtils
AnsiStrPos SysUtils SysUtils
AnsiStrRScan SysUtils SysUtils
AnsiStrScan SysUtils SysUtils
AnsiStrUpper SysUtils SysUtils
AnsiToUtf8     System
AnsiUpperCase AnsiStrings SysUtils
AnsiUpperCaseFileName AnsiStrings SysUtils
AppendStr SysUtils

AssignStr SysUtils 

BoolToStr     SysUtils
ByteLength     SysUtils
BytesOf SysUtils SysUtils
ByteToCharIndex SysUtils SysUtils
ByteToCharLen SysUtils SysUtils
ByteType SysUtils SysUtils
ChangeFileExt AnsiStrings SysUtils
ChangeFilePath AnsiStrings SysUtils
ChDir     System
CharInSet SysUtils SysUtils
CharLength SysUtils/
AnsiStrings
SysUtils
CharToByteIndex SysUtils SysUtils
CharToByteLen SysUtils SysUtils
CompareStr AnsiStrings SysUtils
CompareText AnsiStrings SysUtils
ContainsStr AnsiStrings StrUtils
ContainsText AnsiStrings StrUtils
CreateDir     SysUtils
CurrToStr     SysUtils
CurrToStrF     SysUtils
DateTimeToStr     SysUtils
DateTimeToString     SysUtils
DateToStr     SysUtils
DecodeSoundexInt StrUtils
DecodeSoundexWord StrUtils
DeleteFile     SysUtils
DirectoryExists     SysUtils
DisposeStr SysUtils

DupeString AnsiStrings StrUtils
EndsStr AnsiStrings StrUtils
EndsText AnsiStrings StrUtils
ExceptionErrorMessage     SysUtils
ExcludeTrailingBackslash AnsiStrings SysUtils
ExcludeTrailingPathDelimiter AnsiStrings SysUtils
ExpandFileName AnsiStrings SysUtils
ExpandFileNameCase AnsiStrings SysUtils
ExpandUNCFileName AnsiStrings SysUtils
ExtractFileDir AnsiStrings SysUtils
ExtractFileDrive AnsiStrings SysUtils
ExtractFileExt AnsiStrings SysUtils
ExtractFileName AnsiStrings SysUtils
ExtractFilePath AnsiStrings SysUtils
ExtractRelativePath AnsiStrings SysUtils
ExtractShortPathName AnsiStrings SysUtils
FileAge     SysUtils
FileCreate     SysUtils
FileExists     SysUtils
FileGetAttr     SysUtils
FileIsReadOnly     SysUtils
FileOpen     SysUtils
FileSearch     SysUtils
FileSetAttr     SysUtils
FileSetDate     SysUtils
FileSetReadOnly     SysUtils
FindCmdLineSwitch     SysUtils
FindFirst     SysUtils
FloatToStr     SysUtils
FloatToStrF     SysUtils
FloatToText SysUtils SysUtils
FloatToTextFmt SysUtils SysUtils
FmtLoadStr     SysUtils
FmtStr     SysUtils
ForceDirectories     SysUtils
Format     SysUtils
FormatBuf     SysUtils
FormatCurr     SysUtils
FormatDateTime     SysUtils
FormatFloat     SysUtils
GetCurrentDir     SysUtils
GetEnvironmentVariable     SysUtils
GetFileVersion     SysUtils
GetLocaleChar     SysUtils
GetLocaleStr     SysUtils
GetModuleName     SysUtils
GetPackageDescription     SysUtils
GUIDToString     SysUtils
HashName SysUtils

IfThen StrUtils
IncludeTrailingBackslash AnsiStrings SysUtils
IncludeTrailingPathDelimiter AnsiStrings SysUtils
IndexStr AnsiStrings StrUtils
IndexText AnsiStrings StrUtils
IntToHex     SysUtils
IntToStr     SysUtils
IsAssembly     SysUtils
IsDelimiter AnsiStrings SysUtils
IsLeadChar SysUtils SysUtils
IsPathDelimiter AnsiStrings SysUtils
IsValidIdent     SysUtils
LastDelimiter AnsiStrings SysUtils
LeftBStr StrUtils
LeftStr StrUtils StrUtils
LoadPackage     SysUtils
LoadResourceModule     System
LoadResString     System
LoadStr     SysUtils
LowerCase AnsiStrings SysUtils
MatchStr AnsiStrings StrUtils
MatchText AnsiStrings StrUtils
MidBStr StrUtils
MidStr StrUtils StrUtils
MkDir     System
MoveChars     System
NewStr SysUtils

NextCharIndex SysUtils/
AnsiStrings
SysUtils
OleStrToString     System
OleStrToStrVar System System
ParamStr     System
PlatformBytesOf     SysUtils
PlatformStringOf     SysUtils
Pos System System
PosEx AnsiStrings StrUtils
PUCS4Chars     System
QuotedStr AnsiStrings SysUtils
RandomFrom AnsiStrings StrUtils
RemoveDir     SysUtils
RenameFile     SysUtils
ReplaceStr AnsiStrings StrUtils
ReplaceText AnsiStrings StrUtils
ResemblesText StrUtils
ReverseString AnsiStrings StrUtils
RightBStr StrUtils
RightStr StrUtils StrUtils
RmDir     System
SafeLoadLibrary     SysUtils
SameFileName AnsiStrings SysUtils
SameStr     SysUtils
SameText AnsiStrings SysUtils
SetCodePage System System
SetCurrentDir     SysUtils
SearchBuf StrUtils
Soundex StrUtils
SoundexCompare StrUtils
SoundexInt StrUtils
SoundexProc StrUtils
SoundexSimilar StrUtils
SoundexWord StrUtils
StartsStr AnsiStrings StrUtils
StartsText AnsiStrings StrUtils
StrAlloc

SysUtils
StrBufSize SysUtils

StrByteType SysUtils SysUtils
StrCat SysUtils SysUtils
StrCharLength SysUtils SysUtils
StrComp SysUtils  SysUtils
StrCopy SysUtils  SysUtils
StrDispose SysUtils  SysUtils
StrECopy SysUtils  SysUtils
StrEnd SysUtils  SysUtils
StrFmt SysUtils  SysUtils
StrIComp SysUtils  SysUtils
StringCodePage System System
StringElementSize System System
StringOf     SysUtils
StringOfChar System System
StringRefCount System System
StringReplace AnsiStrings SysUtils
StringToGUID     SysUtils
StringToOleStr System System
StringToWideChar     System
StrLCat SysUtils  SysUtils
StrLComp SysUtils  SysUtils
StrLCopy SysUtils  SysUtils
StrLen SysUtils  SysUtils
StrLFmt SysUtils  SysUtils
StrLFmt SysUtils  SysUtils
StrLIComp SysUtils  SysUtils
StrLower SysUtils  SysUtils
StrMove SysUtils  SysUtils
StrNew SysUtils  SysUtils
StrNextChar SysUtils  SysUtils
StrPas SysUtils  SysUtils
StrPCopy SysUtils  SysUtils
StrPLCopy SysUtils  SysUtils
StrPos SysUtils  SysUtils
StrRScan SysUtils  SysUtils
StrScan SysUtils  SysUtils
StrToBool     SysUtils
StrToBoolDef     SysUtils
StrToCurr     SysUtils
StrToCurrDef     SysUtils
StrToDate     SysUtils
StrToDateDef     SysUtils
StrToDateTime     SysUtils
StrToDateTimeDef     SysUtils
StrToFloat     SysUtils
StrToFloatDef     SysUtils
StrToInt     SysUtils
StrToInt64     SysUtils
StrToInt64Def     SysUtils
StrToIntDef     SysUtils
StrToTime     SysUtils
StrToTimeDef     SysUtils
StuffString AnsiStrings StrUtils
StrUpper SysUtils SysUtils
SysErrorMessage     SysUtils
TextPos SysUtils SysUtils
TextToFloat SysUtils  SysUtils
TimeToStr     SysUtils
Trim AnsiStrings SysUtils
TrimLeft AnsiStrings SysUtils
TrimRight AnsiStrings SysUtils
TryStrToBool     SysUtils
TryStrToCurr     SysUtils
TryStrToDate     SysUtils
TryStrToDateTime     SysUtils
TryStrToFloat     SysUtils
TryStrToInt     SysUtils
TryStrToInt64     SysUtils
TryStrToTime     SysUtils
UCS4StringToUnicodeString     System
UCS4StringToWideString     System
UIntToStr     SysUtils
UpCase System System
UpperCase AnsiStrings SysUtils
UnicodeStringToUCS4String     System
UniqueString System System
WideBytesOf     SysUtils
WideCompareStr     SysUtils
WideCharLenToString     System
WideCharLenToStrVar     System
WideCharToString     System
WideCharToStrVar     System
WideCompareText     SysUtils
WideFmtStr     SysUtils
WideFormat     SysUtils
WideSameStr     SysUtils
WideSameText     SysUtils
WideStrAlloc     SysUtils
WideStringToUCS4String     System
WideStringOf     SysUtils
WideUpperCase     SysUtils
WrapText     SysUtils

 ※製品版では上記の表と異なる事があります。

 関数/手続きがオーバーロードされていれば、コンパイラは引数の型に最適な関数/手続きを呼び出しますが、"別の名前空間に存在するオーバーロードされた関数"があり、適切な名前空間をuses句に記述しないと、代入互換性がある引数の型を持つオーバーロードされた関数/手続きが呼び出される事があります。

uses
  ...;

function Test(aPath: AnsiString): AnsiString;
begin
  result := ExcludeTrailingPathDelimiter(aPath);
end;

 例えば、上記の場合ExcludeTrailingPathDelimiter()はUnicodeString版が呼び出されます。正しくは以下のようになります。

uses
  ..., AnsiStrings;

function Test(aPath: AnsiString): AnsiString;
begin
  result := ExcludeTrailingPathDelimiter(aPath);
end;

 オーバーロードされていない"名前空間が別になっている同名の関数/手続き"は"名前空間.関数/手続き名"として呼び出す必要があります。"別の名前空間に存在するオーバーロードされた関数/手続き"の曖昧さを回避するために、"別の名前空間にオーバーロードされた関数/手続きが存在する"場合には名前空間を関数/手続き名の前に明示的に指定する癖を付けておくと代入互換性のケアレスミスを防ぐ事ができると思います。

uses
  ..., AnsiStrings;

function Test(aPath: AnsiString): AnsiString;
begin
  result := AnsiStrings.ExcludeTrailingPathDelimiter(aPath);
end;

コントロール文字列
 コントロール文字列には注意が必要です。
 桁数を間違うと、Ansi<->Unicode変換が行われたり、意図しない文字コードを指定する事になってしまいます。

in演算子
 Delphi2007は以下のようなコードが普通に使われていました。

var
  S: set of Char;
begin
  S := ['A'..'Z'];
  if ('A' in S) then
    ...
end;

 しかし、Delphi2009ではこのコードは通りません。これを回避するにはCharInSet()を使います。

var
  S: set of Char;
begin
  S := ['A'..'Z'];
  if CharInSet('A', S) then
    ...
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"系の関数を利用する事をオススメします。


Delphi2007/
Delphi2009
Delphi2009
ByteToCharLen ElementToCharLen
CharToByteLen CharToElementLen
ByteToCharIndex ElementToCharIndex
CharToByteIndex 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を用意し、代入を行うだけなのですから。


 BACK