最近の変更サイトマップ

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には結構罠があるので注意が必要だ。

FreeBSD 11-STABLEでVirtualBox復活(`・ω・´)

3回に渡ってお送りしてきたFreeBSD 11.0-RELEASEでkern.proc.pathnameに失敗してVirtualBoxが動かない問題だが、無事解決。予想通り11-STABLEで問題なく動いた。

うちのデジタルデータ保管を一手に担っている結構重要な家鯖だし、STABLEを追いかける元気もないので、早いところ11.1-RELEASE出ないかしらー。これまでのマイナーリリース間隔から見るに、まだまだ先っぽいけど……。

FreeBSD 11.0RでZFSの特定プロパティ条件下でkern.proc.pathnameが失敗する

FreeBSD 11.0-RELEASEでVirtualBoxが起動しない問題、もといKERN_PROC_PATHNAMEのsysctlに失敗するのは、どうやらZFSが原因っぽい。ZFSのcasesensitivityないしnormalizationプロパティがデフォルト値以外になっていると、VFS絡みでうまく動かない模様。うちはnormalization=formCにしてるので、どう見てもこいつのせいです。本当にありがとうございました。

幸い、既にパッチが投稿されており、10/7付けでstableにも取り込まれているので11.1では直ると思われる。

が、それまでVirtualBoxが使えないのは地味に辛いなぁ。うちの環境では今のところVirtualBoxしか表面化してないけど、割と影響受けるソフト多いんじゃないかしら?久々にカーネルを自前ビルドしてみましょうかね。

参考サイト

FreeBSD 11.0RにしたらVirtualBoxが動かなくなった(´・ω・`)

先日、家鯖をFreeBSD 11.0-RELEASEに更新してからVirtualBoxが動かなくなった。起動しようとするとVirtualBox: supR3HardenedExecDir: sysctl failedとエラーを吐いて終了する。sysctlに失敗するってどういうこっちゃ。

当該ソースはSUPR3HardenedMain.cppの1243行目付近で、VirtualBox自身の実行ファイルパスを取得してる部分。

# else /* RT_OS_FREEBSD */
    int aiName[4];
    aiName[0] = CTL_KERN;
    aiName[1] = KERN_PROC;
    aiName[2] = KERN_PROC_PATHNAME;
    aiName[3] = getpid();
 
    size_t cbPath = sizeof(g_szSupLibHardenedExePath);
    if (sysctl(aiName, RT_ELEMENTS(aiName), g_szSupLibHardenedExePath, &cbPath, NULL, 0) < 0)
        supR3HardenedFatal("supR3HardenedExecDir: sysctl failed\n");
    g_szSupLibHardenedExePath[sizeof(g_szSupLibHardenedExePath) - 1] = '\0';
    int cchLink = suplibHardenedStrLen(g_szSupLibHardenedExePath); /* paranoid? can't we use cbPath? */
 
# endif

何の変哲もないコードだし、特に最近変わったような雰囲気もない。原因切り分けのため、上記コードと同じ事をする簡単なテストコードをでっちあげて実行してみたら、同じように失敗する。

#include <sys/param.h>
#include <sys/sysctl.h>
#include <stdio.h>
 
int main(void)
{
    int pid = getpid();
    int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid };
 
    printf("pid = %d\n", pid);
 
    size_t bufSize = 1024;
    char buf[bufSize];
    int result = sysctl(mib, 4, buf, &bufSize, NULL, 0);
    if (result < 0)
    {
        perror("sysctl");
    }
 
    return 0;
}
-------- 実行結果 --------
$ ./sysctltest
pid = 66245
sysctl: No such file or directory

supR3HardenedExecDirでググるとprocfsをマウントし忘れてんじゃね?という投稿が出てくるが、今回の問題箇所はprocfsを不要とするための部分なのでprocfsをマウントしようがしまいが変わらない(大体今までprocfsマウントしてなくても動いてたし…)、と思いつつ藁をも掴む思いでマウントしてみたけど、やっぱり何の解決にもならなかった\(^o^)/

portsでソースからのインストールも試みたけどビルドがコケるし、もぅマヂ無理。リスカしょ…。

参考サイト

PortsにNetatalk 3.1.10がキテタ━━━(゚∀゚)━━━ !!!!!

家鯖をFreeBSD 11.0-RELEASEに上げたタイミングで、Netatalk 3.1.8に更新したのだが、どうにもBonjourで広告されなさげ。OS更新で諸々おかしくなったか?と思い、NetatalkやmDNSResponderを再ビルドしても症状変わらず。

afpd -Vしてみたら、なんとZeroconfがAvahiになってた。間違いなくmDNSResponderを選んでるんだけどな…。

$ afpd -V
afpd 3.1.8 - Apple Filing Protocol (AFP) daemon of Netatalk
 
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version. Please see the file COPYING for further information and details.
 
afpd has been compiled with support for these features:
 
          AFP versions:    2.2 3.0 3.1 3.2 3.3 3.4
         CNID backends:    dbd last tdb
      Zeroconf support:    Avahi
  TCP wrappers support:    Yes
         Quota support:    No
   Admin group support:    Yes
    Valid shell checks:    Yes
      cracklib support:    No
            EA support:    ad | sys
           ACL support:    Yes
          LDAP support:    No
         D-Bus support:    Yes
     Spotlight support:    No
         DTrace probes:    No
 
              afp.conf:    /usr/local/etc/afp.conf
           extmap.conf:    /usr/local/etc/extmap.conf
       state directory:    /var/netatalk/
    afp_signature.conf:    /var/netatalk/afp_signature.conf
      afp_voluuid.conf:    /var/netatalk/afp_voluuid.conf
       UAM search path:    /usr/local/libexec/netatalk-uams//
  Server messages path:    /var/netatalk/msg/

portsの更新ログ見ると、10/10に「Fix build with mDNSResponder」とあったので、portsがバグってたっぽい。と、ここで、11-RELEASEにした際Portsツリーを更新してなかったことに気付く。

portsnap fetch extractしてportmaster netatalk3したところ、無事mDNSResponderで広告されるようになった。

$ afpd -V
afpd 3.1.10 - Apple Filing Protocol (AFP) daemon of Netatalk
 
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version. Please see the file COPYING for further information and details.
 
afpd has been compiled with support for these features:
 
          AFP versions:    2.2 3.0 3.1 3.2 3.3 3.4
         CNID backends:    dbd last tdb
      Zeroconf support:    mDNSResponder
  TCP wrappers support:    Yes
         Quota support:    No
   Admin group support:    Yes
    Valid shell checks:    Yes
      cracklib support:    No
            EA support:    ad | sys
           ACL support:    Yes
          LDAP support:    No
         D-Bus support:    Yes
     Spotlight support:    No
         DTrace probes:    No
 
              afp.conf:    /usr/local/etc/afp.conf
           extmap.conf:    /usr/local/etc/extmap.conf
       state directory:    /var/netatalk/
    afp_signature.conf:    /var/netatalk/afp_signature.conf
      afp_voluuid.conf:    /var/netatalk/afp_voluuid.conf
       UAM search path:    /usr/local/libexec/netatalk-uams//
  Server messages path:    /var/netatalk/msg/

3.1.7では問題なかったので、もしかして最近までずっとバグってた系・・・?

start.txt · 最終更新: 2016-05-07 17:46 by decomo
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0