FName::GetPlainANSIString()で正しい文字列が取れない事がある

お急ぎのあなたのために、まずは結論。FNameが保持する文字列が必要な時はToString()で生成したFStringを使うべし。GetPlainANSIString()GetPlainWIDEString()を使うとハマるから止めといた方がいい。

Unreal Engine 4の軽量文字列クラスFName公式リファレンス)のGetPlainANSIString関数/GetPlainWIDEString関数で取得できる文字列ポインタには、そのFNameインスタンスが本来持っている文字列の一部しか入っていない事がある。恐らく仕様。以下が実証コード。

TArray<FName> Names;
Names.Add(FName(TEXT("Hoge_")));
Names.Add(FName(TEXT("Hoge_0000")));
Names.Add(FName(TEXT("Hoge_0001")));
Names.Add(FName(TEXT("Hoge_10")));
Names.Add(FName(TEXT("Hoge_1200")));
Names.Add(FName(TEXT("Hoge_1300")));
Names.Add(FName(TEXT("Hoge_9999")));
Names.Add(FName(TEXT("Hoge9999")));
 
for (const auto& Name : Names)
{
	UE_LOG(LogWindows, Log, TEXT("◆%s"), *Name.ToString());
	UE_LOG(LogWindows, Log, TEXT("  PlainAnsiString=%s (%p)"), *FString(Name.GetPlainANSIString()), Name.GetPlainANSIString());
	UE_LOG(LogWindows, Log, TEXT("  [FNameEntry]"));
	UE_LOG(LogWindows, Log, TEXT("    ComparisonIndex=%d"), Name.GetComparisonIndex());
	UE_LOG(LogWindows, Log, TEXT("      [ComparisonEntry]"));
	UE_LOG(LogWindows, Log, TEXT("        Address=%p"), Name.GetComparisonNameEntry());
	UE_LOG(LogWindows, Log, TEXT("        isWide=%d"), Name.GetComparisonNameEntry()->IsWide());
	UE_LOG(LogWindows, Log, TEXT("    DisplayIndex=%d"), Name.GetDisplayIndex());
	UE_LOG(LogWindows, Log, TEXT("      [DisplayEntry]"));
	UE_LOG(LogWindows, Log, TEXT("        Address=%p"), Name.GetDisplayNameEntry());
	UE_LOG(LogWindows, Log, TEXT("        isWide=%d"), Name.GetDisplayNameEntry()->IsWide());
	UE_LOG(LogWindows, Log, TEXT("    Number=%d"), Name.GetNumber());
}

実行結果を見ると、Hoge_10, Hoge_1200, Hoge_1300, Hoge_999で見事に同一のPlainAnsiStringが返ってきているのが分かる(★の部分)

LogWindows: ◆Hoge_
LogWindows:   PlainAnsiString=Hoge_ (000000021299B988)
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029720
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B978
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029720
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B978
LogWindows:         isWide=0
LogWindows:     Number=0
LogWindows: ◆Hoge_0000
LogWindows:   PlainAnsiString=Hoge_0000 (000000021299B9A0)
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029721
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B990
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029721
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B990
LogWindows:         isWide=0
LogWindows:     Number=0
LogWindows: ◆Hoge_0001
LogWindows:   PlainAnsiString=Hoge_0001 (000000021299B9C0)
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029722
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9B0
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029722
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9B0
LogWindows:         isWide=0
LogWindows:     Number=0
LogWindows: ◆Hoge_10
LogWindows:   PlainAnsiString=Hoge (000000021299B9E0)…★
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029723
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029723
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     Number=11
LogWindows: ◆Hoge_1200
LogWindows:   PlainAnsiString=Hoge (000000021299B9E0)…★
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029723
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029723
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     Number=1201
LogWindows: ◆Hoge_1300
LogWindows:   PlainAnsiString=Hoge (000000021299B9E0)…★
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029723
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029723
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     Number=1301
LogWindows: ◆Hoge_9999
LogWindows:   PlainAnsiString=Hoge (000000021299B9E0)…★
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029723
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029723
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9D0
LogWindows:         isWide=0
LogWindows:     Number=10000
LogWindows: ◆Hoge9999
LogWindows:   PlainAnsiString=Hoge9999 (000000021299B9F8)
LogWindows:   [FNameEntry]
LogWindows:     ComparisonIndex=1029724
LogWindows:       [ComparisonEntry]
LogWindows:         Address=000000021299B9E8
LogWindows:         isWide=0
LogWindows:     DisplayIndex=1029724
LogWindows:       [DisplayEntry]
LogWindows:         Address=000000021299B9E8
LogWindows:         isWide=0
LogWindows:     Number=0

FNameはハッシュ付き文字列として実装されている。文字列はFNameの共用領域に格納され、各FNameインスタンスはその文字列格納領域へのインデックス=ハッシュを保持してる。FNameの同士の比較は互いのハッシュの比較、つまり整数の比較に還元されるため、通常の文字列比較より速いって仕掛けなんですな。

ただ、このハッシュ生成方法がちょっと曲者で、文字列がアンダースコア+ゼロ詰めされていない数値で終わっていたら、その部分を除いた文字列を共用領域に格納し、数値は各FNameインスタンスで保持するという方法なのだ。割と最近のバージョンアンプで変わったらしい(知人曰く4.12あたりで変わった気がすると)。なかなか破壊的な変更をしてくださりやがるな!言葉だと分かりにくいので図を作ってみた。

図を踏まえつつ改めて実行結果を見てみると、条件に合致するHoge_10, Hoge_1200, Hoge_1300, Hoge_999の中身が、その通りになっている事がお分かりいただけよう。Numberが実際の数値+1されているのは、これまた仕様で、数値分割条件を満たさないFNameインスタンス(Number == 0)との区別の為っぽい。

UE4では生成されたオブジェクトのインスタンスに対し、オブジェクト名+連番の名前を自動付与するため、大規模な開発になるとFName文字列ストアの肥大化が無視できなくなり、保持方法を変更したのだと思われる。

こんな格納の仕方で大丈夫なの!?と思うが、ふつーにFNameを使う分には何の問題もない。や、正確には大丈夫じゃなかったからこの記事書いてる訳だけど、文字列の生ポインタ取ってこねくり回すような事をしなければ大丈夫。FName文字列に対して低レベルな操作を行いたい時は、ToString()関数で生成したFStringに対して行う事をオススメする。エンジンのコードを見てもらうと分かるが、ComparisonIndexの文字列にアンダースコアと数値をくっつけて元の文字列を復元してやがるので(笑)

ちなみに、実行結果内のDisplayIndexというのは、名前の通り表示用の文字列へのインデックス。簡単に言えば、FName生成時に与えられた文字列のインデックスである。FNameは基本的にcase-preserving(内部的には大文字小文字を区別するが対外的には区別せずに扱う)なので、比較用と表示用で別々の文字列を持つ必要がある訳だ。知らずに使ってると、これも地味にハマりポイントかも。

FName abc(TEXT("abc"));
FName ABC(TEXT("ABC"));
UE_LOG(LogWindows, Log, TEXT("%s %s %s"), *abc.ToString(), (abc == ABC ? TEXT("==") : TEXT("!=")), *ABC.ToString());

念のため上記コードで確認したら、LogWindows: abc == ABCとなった。

掘ってみるとFNameには結構罠があるので注意が必要だ。

Nitroethane predisposed an organic compound with the chemical formula C2H5NO2. It is an oily liquid at measure temperature besides pressure, and pure nitroethane predisposed colorless with a fruity odor.

Uses and Reactions https://mytodayhealthtips.com/bmk-glycidate-deciphering-the-chemical-intricacies-of-a-key-intermediate-in-organic-synthesis

Chemical Reactions: Nitroethane can undergo <a href=https://mytodayhealthtips.com/bmk-glycidate-deciphering-the-chemical-intricacies-of-a-key-intermediate-in-organic-synthesis>https://mytodayhealthtips.com/bmk-glycidate-deciphering-the-chemical-intricacies-of-a-key-intermediate-in-organic-synthesis</a> different condensations, this as the Henry reaction, in order convert into several compounds of commercial passion. For example, condensation with 3,4-dimethoxybenzaldehyde yields the precursor in order to the antihypertensive drug methyldopa, in all of this condensation with unsubstituted benzaldehyde yields phenyl-2-nitropropene, a precursor for amphetamine drugs. Fuel Additive and Rocket Propellants: Nitroethane is second hand as a fuel additive and solid precursor in order rocket propellants, showcasing its versatility in different applications.

Physical besides Chemical Properties

Physical Description: It is <a href=https://cabinet-dentaire-djerba.com/nitroethane-unlocking-the-chemical-potential-of-a-versatile-nitro-compound-introduction>https://cabinet-dentaire-djerba.com/nitroethane-unlocking-the-chemical-potential-of-a-versatile-nitro-compound-introduction</a> solid colorless, oily liquid with the decision mild, fruity odor. Boiling Point: Nitroethane has solid bubbling point of 237°F and the decision freezing point of -130°F. Solubility besides Vapor Pressure: It predisposed a little bit soluble in water besides owns solid vapor pressure of 21 mmHg at 77°F. Explosive Limits: Nitroethane owns special upper besides lower explosive limits, producing it a flammable liquid.

Environmental besides Regulatory Aspects

Release in order to the Environment: Nitroethane's manufacturing besides used as solid solvent, artificial fingernail glue remover, and chemical intermediate may result in its release in order the surroundings through the different worked streams. Regulatory Status: In the United States, nitroethane is classified as solid DEA List I chemical, producing it difficult for individuals in order to purchase due to its link with drug precursor activities.

1 | | 2024-05-02 07:17 | reply



  • blog/2017/2017-01-20.1484979746.txt.gz
  • 最終更新: 2017-01-21 15:22
  • by Decomo