昔は、指定されたコードページのWideString
をAnsiString
に変換する関数がありました。
_function WideStringToString(const Source: WideString; CodePage: UINT): AnsiString;
...
begin
...
// Convert source UTF-16 string (WideString) to the destination using the code-page
strLen := WideCharToMultiByte(CodePage, 0,
PWideChar(Source), Length(Source), //Source
PAnsiChar(cpStr), strLen, //Destination
nil, nil);
...
end;
_
そして、すべてがうまくいきました。関数にnicode文字列(つまり、UTF-16エンコードデータ)を渡して、AnsiString
のバイトが指定されたコードページの文字を表していることを理解して、AnsiString
に変換しました。
例えば:
_TUnicodeHelper.WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
_
_Windows-1252
_エンコードされた文字列を返します:
_The qùíçk brown fôx jumped ovêr the lázÿ dog
_
注:もちろん、完全なUnicode文字セットからWindows-1252コードページの限定された領域への変換中に情報は失われました。
- _
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ
_ (before)- _
The qùíçk brown fôx jumped ovêr the lázÿ dog
_ (after)
しかし、WindowsのWideChartoMultiByte
は、最適なマッピングのかなり良い仕事をしています。それがするように設計されているように。
今、私たちは後の時代にいます。 WideString
は現在、Pariahであり、UnicodeString
が長所です。それは取るに足らない変更です。とにかく、Windows関数は一連のWideChar
へのpointerのみを必要としたため(これはUnicodeString
も必要です)。したがって、代わりにUnicodeString
を使用するように宣言を変更します。
_funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
...
end;
_
ここで戻り値に到達します。バイトを含むAnsiString
があります。
_54 68 65 20 71 F9 ED E7 The qùíç
6B 20 62 72 6F 77 6E 20 k brown
66 F4 78 20 6A 75 6D 70 fôx jump
65 64 20 6F 76 EA 72 20 ed ovêr
74 68 65 20 6C E1 7A FF the lázÿ
20 64 6F 67 dog
_
昔は大丈夫でした。 AnsiString
に実際に含まれているコードページを追跡しました。私は覚えておく返されたAnsiString
はコンピューターのロケール(たとえばWindows 1258)を使用してエンコードされず、代わりに別のコードページ(CodePage
コードページ)を使用してエンコードされなければなりませんでした。
しかし、Delphi XE6では、AnsiString
にもコードページが密かに含まれています。
The qùíçk brown fôx jumped ovêr the lázÿ dog
_このコードページは間違っています。 Delphiは、文字列のコードページではなく、コンピューターのコードページを指定しています。技術的にはこれは問題ではありません。AnsiString
が特定のコードページにあることを常に理解していたので、その情報を必ず渡す必要がありました。
そのため、文字列をデコードしたいときは、コードページを渡す必要がありました。
_s := TUnicodeHeper.StringToWideString(s, 1252);
_
と
_function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
...
MultiByteToWideChar(...);
...
end;
_
問題は、昔は_Utf8String
_という型を宣言していたことでした:
_type
Utf8String = type AnsiString;
_
十分に一般的だったため:
_function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
Result := WideStringToString(s, CP_UTF8);
end;
_
そしてその逆:
_function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
Result := StringToWideString(s, CP_UTF8);
end;
_
XE6には、takes_Utf8String
_という関数があります。既存のコードのどこかにUTF-8でエンコードされたAnsiString
を使用し、_Utf8ToWideString
_を使用してUnicodeStringに変換しようとすると失敗します。
_s: AnsiString;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
...
ws: UnicodeString;
ws := Utf8ToWideString(s); //Delphi will treat s an CP1252, and convert it to UTF8
_
さらに悪いことに、既存のコードの幅は次のとおりです。
_s: Utf8String;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
_
返される文字列は完全に壊れます:
AnsiString(1252)
(AnsiString
は現在のコードページを使用してエンコードされているとタグ付けされています)を返しますAnsiString(65001)
文字列(_Utf8String
_)に保存されています理想的には、私のUnicodeStringToString(string, codePage)
関数(AnsiString
を返す)は、 CodePage
のようなものを使用して、文字列内のSetCodePage
を実際のコードページに一致するように設定できます。
_function UnicodeStringToString(s: UnicodeString; CodePage: UINT): AnsiString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
//SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
_
AnsiString
の内部構造を手動でいじることは恐ろしく危険です。
RawByteString
を返すのはどうでしょうか?RawByteString
がniversal recipient;であることを意図しているのは、私ではない多くの人々から繰り返し言われています。それは戻り値のパラメータとして意図されていませんでした:
_function UnicodeStringToString(s: UnicodeString; CodePage: UINT): RawByteString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
end;
_
これには、サポートされ文書化されているSetCodePage
を使用できるという利点があります。
しかし、行を超えてRawByteString
を返し始める場合、Delphiには既にUnicodeString
をRawByteString
文字列に、またはその逆に変換できる関数が既にあります。
_function WideStringToString(const s: UnicodeString; CodePage: UINT): RawByteString;
begin
Result := SysUtils.Something(s, CodePage);
end;
function StringToWideString(const s: RawByteString; CodePage: UINT): UnicodeString;
begin
Result := SysUtils.SomethingElse(s, CodePage);
end;
_
しかし、それは何ですか?
これは、些細な質問の背景の長いセットでした。 realの質問は、もちろん、代わりに何をすべきでしょうか? UnicodeStringToString
およびその逆に依存する多くのコードがあります。
UnicodeString
をUTFに変換するには、次のようにします。
_Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
_
そして、私はUnicodeString
を現在のコードページに変換することができます:
_AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
_
しかし、どのようにUnicodeString
を任意の(指定されていない)コードページに変換できますか?
私の気持ちは、すべてが本当にAnsiString
であるということです。
_Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);
_
私は弾丸を噛み、AnsiString
構造体を破り、それに正しいコードページを突く必要があります:
_function StringToAnsi(const s: UnicodeString; CodePage: UINT): AnsiString;
begin
LocaleCharsFromUnicode(CodePage, ..., s, ...);
...
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
_
その後、VCLの残りの部分が整列します。
この特定のケースでは、RawByteString
を使用するのが適切なソリューションです。
_function WideStringToString(const Source: UnicodeString; CodePage: UINT): RawByteString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(Result, CodePage, False);
end;
end;
_
このように、RawByteString
はコードページを保持し、RawByteString
をAnsiString
または_UTF8String
_などのその他の文字列型に割り当てると、 RawByteString
データを現在のコードページから宛先文字列のコードページ(UnicodeString
への変換を含む)に自動的に変換するRTL。
AnsiString
(これはお勧めしません)を絶対に返す必要がある場合でも、型キャスト経由でSetCodePage()
を使用できます。
_function WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(PRawByteString(@Result)^, CodePage, False);
end;
end;
_
RTLは既にコードページを取得して使用する方法を知っているため、_(Ansi|RawByte)String
_に既に格納されているコードページを使用するだけで、逆の方がはるかに簡単です。
_function StringToWideString(const Source: AnsiString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
_
_function StringToWideString(const Source: RawByteString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
_
そうは言っても、ヘルパー関数をすべて削除し、代わりに型指定された文字列を使用することをお勧めします。 RTLが変換を処理するようにします。
_type
Win1252String = type AnsiString(1252);
var
s: UnicodeString;
a: Win1252String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
a := Win1252String(s);
s := UnicodeString(a);
end;
_
_var
s: UnicodeString;
u: UTF8String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
u := UTF8String(s);
s := UnicodeString(u);
end;
_
RawByteString
を返すことは、おそらくあなたと同じくらい良いと思う。 AnsiString
を使用してそれを行うことができますが、RawByteString
は意図をよりよく捉えます。このシナリオでは、RawByteString
はEmbarcaderoの公式アドバイスの意味で、道徳的にパラメーターとしてカウントされます。入力ではなく、単なる出力です。実際のキーは、変数として使用しないことです。
次のようにコーディングできます。
function MBCSString(const s: UnicodeString; CodePage: Word): RawByteString;
var
enc: TEncoding;
bytes: TBytes;
begin
enc := TEncoding.GetEncoding(CodePage);
try
bytes := enc.GetBytes(s);
SetLength(Result, Length(bytes));
Move(Pointer(bytes)^, Pointer(Result)^, Length(bytes));
SetCodePage(Result, CodePage, False);
finally
enc.Free;
end;
end;
それから
var
s: AnsiString;
....
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1251);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 65001);
Writeln(StringCodePage(s));
予想どおり1252、1251、そして65001を出力します。
必要に応じて、LocaleCharsFromUnicode
を使用できます。もちろん、 そのドキュメント を少しつまみます:LocaleCharsFromUnicodeはWideCharToMultiByte関数のラッパーです。 LocaleCharsFromUnicode
はクロスプラットフォームであるためにのみ存在するので、テキストがこれまでに作成されたことは驚くべきことです。
ただし、プログラムのAnsiString
変数にANSIでエンコードされたテキストを保持しようとするのを間違えているのではないかと思います。通常、ANSIにエンコードするのはできるだけ遅く(相互運用境界で)、同様にできるだけ早くデコードします。
単純にこれを行う必要がある場合は、恐ろしいAnsiString
を完全に回避するより良い解決策があるかもしれません。テキストをAnsiString
に保存する代わりに、TBytes
に保存します。エンコードを追跡するデータ構造が既にあるので、それらを保持しないでください。コードページとAnsiString
を含むレコードを、コードページとTBytes
を含むレコードに置き換えます。そうすれば、あなたの背中の後ろにテキストを書き直すことを恐れることはないでしょう。また、モバイルコンパイラでコードを使用する準備が整います。
System.pas
、組み込み関数 SetAnsiString
が見つかりました。
procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);
また、この関数がdoesCodePageを内部のStrRec構造にプッシュすることに注意することも重要です:
PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;
これにより、次のような記述が可能になります。
function WideStringToString(const s: UnicodeString; DestinationCodePage: Word): AnsiString;
var
strLen: Integer;
begin
strLen := Length(Source);
if strLen = 0 then
begin
Result := '';
Exit;
end;
//Delphi XE6 has a function to convert a unicode string to a tagged AnsiString
SetAnsiString(@Result, @Source[1], strLen, DestinationCodePage);
end;
だから私が電話するとき:
actual := WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 850);
結果のAnsiString
を取得します。
codePage: $0352 (850)
elemSize: $0001 (1)
refCnt: $00000001 (1)
length: $0000002C (44)
contents: 'The qùíçk brown fôx jumped ovêr the láZÿ dog'
AnsiString、適切なコードページが既にcodePage
秘密メンバーに詰め込まれています。
class function TUnicodeHelper.ByteStringToUnicode(const Source: RawByteString; CodePage: UINT): UnicodeString;
var
wideLen: Integer;
dw: DWORD;
begin
{
See http://msdn.Microsoft.com/en-us/library/dd317756.aspx
Code Page Identifiers
for a list of code pages supported in Windows.
Some common code pages are:
CP_UTF8 (65001) utf-8 "Unicode (UTF-8)"
CP_ACP (0) The system default Windows ANSI code page.
CP_OEMCP (1) The current system OEM code page.
1252 Windows-1252 "ANSI Latin 1; Western European (Windows)", this is what most of us in north america use in Windows
437 IBM437 "OEM United States", this is your "DOS fonts"
850 ibm850 "OEM Multilingual Latin 1; Western European (DOS)", the format accepted by Fincen for LCTR/STR
28591 iso-8859-1 "ISO 8859-1 Latin 1; Western European (ISO)", Windows-1252 is a super-set of iso-8859-1, adding things like euro symbol, bullet and ellipses
20127 us-ascii "US-ASCII (7-bit)"
}
if Length(Source) = 0 then
begin
Result := '';
Exit;
end;
// Determine real size of final, string in symbols
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not get wide length of UTF-16 string. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
// Allocate memory for UTF-16 string
SetLength(Result, wideLen);
// Convert source string to UTF-16 (WideString)
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(wideStr), wideLen);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(Result), wideLen);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not convert string to UTF-16. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
end;
注:パブリックドメインにリリースされたコード。帰属は必要ありません。