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