UE4 4.23.0 FName相关源码阅读

575 阅读4分钟

逆向一款UE4游戏的提前准备工作。

main

map for generating a FName

FName Usage

FName TestHUDName = FName(TEXT("ThisIsMyTestFName"));
  • FName::FName()
    • FNameHelper::MakeDetectNumber()
      • MakeWithNumber()
        • Make()
          • GetNamePool(); // 拿到单例的全局FNamePool 实例
          • Pool.Store(View); // 将FName 试图添加到FNamePool 实例中
            • ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded); // ComparisonShards 是FNamePool的一个成员, Insert方法调用的是 FNamePoolShard::Insert()
              • Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader); // 为FNameStringVIew的字符串分配一块内存,并设置其所在的block,以及在block中的偏移offset,返回一个FNameEntryHandle对象
              • ClaimSlot(Slot, FNameSlot(NewEntryId, Value.Hash.SlotProbeHash)); // 超出边界则调整边界大小,以及重新hash等操作

signleton

// NamePoolData 就是一块 sizeof(FNamePool) 大小的内存, 其作用就是为单例的 FNamePoll 提供一块内存而已
static bool bNamePoolInitialized;
alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)];

function

GetNamePool()

// Only call this once per public FName function called
//
// Not using magic statics to run as little code as possible
static FNamePool &GetNamePool()
{
    // 已经初始化过,直接返回 NamePollData的指针
    if (bNamePoolInitialized)
    {
        return *(FNamePool *)NamePoolData;
    }

    // 此处调用的不是调用malloc的new, 而是以 NamePollData这一块内存来调用 placement-params new
    FNamePool *Singleton = new (NamePoolData) FNamePool;
    bNamePoolInitialized = true;
    return *Singleton;
}

Probe

/** Find slot containing value or the first free slot that should be used to store it  */
FNameSlot &Probe(const FNameValue<Sensitivity> &Value) const
{
    return Probe(Value.Hash.UnmaskedSlotIndex,
				[&](FNameSlot Slot)
				{ return Slot.GetProbeHash() == Value.Hash.SlotProbeHash &&
						 EntryEqualsValue<Sensitivity>(Entries->Resolve(Slot.GetId()), Value); });
}

/** Find slot that fulfills predicate or the first free slot  */
template <class PredicateFn>
FNameSlot &Probe(uint32 UnmaskedSlotIndex, PredicateFn Predicate) const
{
    // 遍历所有的Slots,通过对比哈希值来判断是否已经存在该字符串
    const uint32 Mask = CapacityMask;
    for (uint32 I = FNameHash::GetProbeStart(UnmaskedSlotIndex, Mask); true; I = (I + 1) & Mask)
    {
        FNameSlot &Slot = Slots[I];
	if (!Slot.Used() || Predicate(Slot))
	{
            return Slot;
	}
    }
}

FNameEntryId::Insert

FNameEntryId Insert(const FNameValue<Sensitivity> &Value, bool &bCreatedNewEntry)
{
    FRWScopeLock _(Lock, FRWScopeLockType::SLT_Write);

    // 所谓的 Probe 其实就是check
    FNameSlot &Slot = Probe(Value);

    // 字符串已经存在,返回 id
    if (Slot.Used())
    {
        return Slot.GetId();
    }

    // 此处调用了 FNamePool::Create方法,返回一个FNameEntry,此处强制转化为了 FNameEntryId
    FNameEntryId NewEntryId = Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader);

    ClaimSlot(Slot, FNameSlot(NewEntryId, Value.Hash.SlotProbeHash));

    bCreatedNewEntry = true;

    return NewEntryId;
}

FNamePoll::Store

FNameEntryId FNamePool::Store(FNameStringView Name)
{
#if WITH_CASE_PRESERVING_NAME
	FNameDisplayValue DisplayValue(Name);
	FNamePoolShard<ENameCase::CaseSensitive> &DisplayShard = DisplayShards[DisplayValue.Hash.ShardIndex];
	if (FNameEntryId Existing = DisplayShard.Find(DisplayValue))
	{
            return Existing;
	}
#endif
        // buffer中已经存在的字符串的数量
	TAtomic<uint32> &EntryCount = Name.IsAnsi() ? AnsiCount : WideCount;
	bool bAdded = false;

	// Insert comparison name first since display value must contain comparison name
        // FNameComparisonValue 会对传入的FNameStringView进行hash等操作
	FNameComparisonValue ComparisonValue(Name);
        // 将当前的FName的hash值试图插入到保存字符串的表中,
	FNameEntryId ComparisonId = ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded);
	EntryCount += bAdded;

#if WITH_CASE_PRESERVING_NAME
	// Check if ComparisonId can be used as DisplayId
	if (bAdded || EqualsSameDimensions<ENameCase::CaseSensitive>(Resolve(ComparisonId), Name))
	{
            DisplayShard.InsertExistingEntry(DisplayValue.Hash, ComparisonId);
            return ComparisonId;
	}
	else
	{
            bAdded = false;
            DisplayValue.ComparisonId = ComparisonId;
            FNameEntryId DisplayId = DisplayShard.Insert(DisplayValue, bAdded);
            EntryCount += bAdded;

            return DisplayId;
	}
#else
    return ComparisonId;
#endif
}

FNameHelper::Make()

static FName Make(FNameStringView View, EFindName FindType, int32 InternalNumber)
{
        // 拿到存储字符串表的单例FNamePoll的句柄
	FNamePool &Pool = GetNamePool();

	FNameEntryId DisplayId, ComparisonId;
        // 本次操作的类型是添加 StringView 到NamePool中
	if (FindType == FNAME_Add)
	{
		DisplayId = Pool.Store(View);
#if WITH_CASE_PRESERVING_NAME
		ComparisonId = Pool.Resolve(DisplayId).ComparisonId;
#else
		ComparisonId = DisplayId;
#endif
	}
        // 本次操作的类型是从NamePool查找StringView 到
	else if (FindType == FNAME_Find)
	{
		DisplayId = Pool.Find(View);
#if WITH_CASE_PRESERVING_NAME
		ComparisonId = DisplayId ? Pool.Resolve(DisplayId).ComparisonId : DisplayId;
#else
		ComparisonId = DisplayId;
#endif
	}
        // other
	else
	{
		check(FindType == FNAME_Replace_Not_Safe_For_Threading);

#if FNAME_WRITE_PROTECT_PAGES
		checkf(false, TEXT("FNAME_Replace_Not_Safe_For_Threading can't be used together with page protection."));
#endif
		DisplayId = Pool.Store(View);
#if WITH_CASE_PRESERVING_NAME
		ComparisonId = Pool.Resolve(DisplayId).ComparisonId;
#else
		ComparisonId = DisplayId;
#endif
		ReplaceName(Pool.Resolve(ComparisonId), View);
	}

	return FName(ComparisonId, DisplayId, InternalNumber);
}

class

FNameEntry

struct FNameEntry
{
private:
#if WITH_CASE_PRESERVING_NAME
    FNameEntryId ComparisonId;
#endif
    FNameEntryHeader Header;
    union
    {
        ANSICHAR AnsiName[NAME_SIZE];
	WIDECHAR WideName[NAME_SIZE];
    };
    ...
};

FNameHash

/** Hashes name into 64 bits that determines shard and slot index.
 *
 *	Small parts	of the hash is also stored in unused bits of the slot and entry.
 *	The former optimizes linear probing by accessing less entry data.
 *	The latter optimizes linear probing by avoiding copying and deobfuscating entry data.
 *
 *	The slot index could be stored in the slot, at least in non shipping / test configs.
 *	This costs memory by doubling slot size but would essentially never touch entry data
 *	nor copy and deobfuscate a name needlessy. It also allows growing the hash table
 *	without rehashing the strings, since the unmasked slot index would be known.
 */
struct FNameHash
{
	uint32 ShardIndex;
	uint32 UnmaskedSlotIndex;		   // Determines at what slot index to start probing
	uint32 SlotProbeHash;			   // Helps cull equality checks (decode + strnicmp) when probing slots
	FNameEntryHeader EntryProbeHeader; // Helps cull equality checks when probing inspects entries

	template <class CharType>
	FNameHash(const CharType *Str, int32 Len)
	{
            // 将字符串hash得到一个uint64,高32位计算后可用作ShardIndex和SlotProbeHash,
            // 低32位直接被当作 UnmaskedSlotIndex
            uint64 Hash = CityHash64(reinterpret_cast<const char *>(Str), Len * sizeof(CharType));
            uint32 Hi = static_cast<uint32>(Hash >> 32);
            uint32 Lo = static_cast<uint32>(Hash);

            // "None" has FNameEntryId with a value of zero
            // Always set a bit in SlotProbeHash for "None" to distinguish unused slot values from None
            // @see FNameSlot::Used()
            uint32 IsNoneBit = IsAnsiNone(Str, Len) << FNameSlot::ProbeHashShift;

            static constexpr uint32 ShardMask = FNamePoolShards - 1;
            static_assert((ShardMask & FNameSlot::ProbeHashMask) == 0, "Masks overlap");

            ShardIndex = Hi & ShardMask;
            UnmaskedSlotIndex = Lo;
            SlotProbeHash = (Hi & FNameSlot::ProbeHashMask) | IsNoneBit;
            EntryProbeHeader.Len = Len;
            EntryProbeHeader.bIsWide = sizeof(CharType) == sizeof(WIDECHAR);

            // When we always use lowercase hashing, we can store parts of the hash in the entry
            // to avoid copying and decoding entries needlessly. WITH_CUSTOM_NAME_ENCODING
            // that makes this important is normally on when WITH_CASE_PRESERVING_NAME is off.
#if !WITH_CASE_PRESERVING_NAME
            static constexpr uint32 EntryProbeMask = (1u << FNameEntryHeader::ProbeHashBits) - 1;
            EntryProbeHeader.LowercaseProbeHash = static_cast<uint16>((Hi >> FNamePoolShardBits) & EntryProbeMask);
#endif
	}

	uint32 GetProbeStart(uint32 SlotMask) const
	{
		return UnmaskedSlotIndex & SlotMask;
	}

	static uint32 GetProbeStart(uint32 UnmaskedSlotIndex, uint32 SlotMask)
	{
		return UnmaskedSlotIndex & SlotMask;
	}

	static uint32 IsAnsiNone(const WIDECHAR *Str, int32 Len)
	{
		return 0;
	}

	static uint32 IsAnsiNone(const ANSICHAR *Str, int32 Len)
	{
		if (Len != 4)
		{
			return 0;
		}

#if PLATFORM_LITTLE_ENDIAN
		static constexpr uint32 NoneAsInt = 0x454e4f4e;
#else
		static constexpr uint32 NoneAsInt = 0x4e4f4e45;
#endif
		static constexpr uint32 ToUpperMask = 0xdfdfdfdf;

		uint32 FourChars = FPlatformMemory::ReadUnaligned<uint32>(Str);
		return (FourChars & ToUpperMask) == NoneAsInt;
	}
};

FName

FName是ue4引擎提供的一种轻量级字符串使用方式,其类定义在Engine\Source\Runtime\Core\Public\UObject\NameTypes.h文件中。

/**
 * Public name, available to the world.  Names are stored as a combination of
 * an index into a table of unique strings and an instance number.
 * Names are case-insensitive, but case-preserving (when WITH_CASE_PRESERVING_NAME is 1)
 */
class CORE_API FName {
    ...
private:

	/** Index into the Names array (used to find String portion of the string/number pair used for comparison) */
        // 当前FName所保存的字符串在Names array中的index
	FNameEntryId	ComparisonIndex;
#if WITH_CASE_PRESERVING_NAME
	/** Index into the Names array (used to find String portion of the string/number pair used for display) */
	FNameEntryId	DisplayIndex;
#endif
	/** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */
        // 实例的数量,对于[字符串+数组]这种情况,对于空串而言,Number为0,也就是没有实例
	uint32			Number;
};

FNameEntryHandle

/** An unpacked FNameEntryId */
struct FNameEntryHandle
{
    uint32 Block = 0;
    uint32 Offset = 0;

    FNameEntryHandle(uint32 InBlock, uint32 InOffset)
	: Block(InBlock)
	, Offset(InOffset)
    {}

    FNameEntryHandle(FNameEntryId Id)
	: Block(Id.ToUnstableInt() >> FNameBlockOffsetBits)
	, Offset(Id.ToUnstableInt() & (FNameBlockOffsets - 1))
    {}

    operator FNameEntryId() const
    {
	return FNameEntryId::FromUnstableInt(Block << FNameBlockOffsetBits | Offset);
    }

    explicit operator bool() const { return Block | Offset; }
};

FNameEntryId

/** Opaque id to a deduplicated name */
struct FNameEntryId
{
    ...
private:
	uint32 Value;
};

FNameEntryAllocator

/** Remember to update natvis if you change these */
enum { FNameMaxBlockBits = 13 }; // Limit block array a bit, still allowing 8k * block size = 1GB - 2G of FName entry data
enum { FNameBlockOffsetBits = 16 };
enum { FNameMaxBlocks = 1 << FNameMaxBlockBits };
enum { FNameBlockOffsets = 1 << FNameBlockOffsetBits };

class FNameEntryAllocator
{
    ...
    mutable FRWLock Lock;
    uint32 CurrentBlock = 0;
    uint32 CurrentByteCursor = 0;
    uint8* Blocks[FNameMaxBlocks] = {};    //  FNameMaxBlocks = 8192
};

FNamePool

FNamePoll定义在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp中,其在全局中只有一个单例实例。

static bool bNamePoolInitialized;
alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)];
enum { FNamePoolShards = 1 << FNamePoolShardBits };


class FNamePool
{
    ...
private:
	

	FNameEntryAllocator Entries;
	TAtomic<uint32> AnsiCount;
	TAtomic<uint32> WideCount;

#if WITH_CASE_PRESERVING_NAME
	FNamePoolShard<ENameCase::CaseSensitive> DisplayShards[FNamePoolShards];
#endif
	FNamePoolShard<ENameCase::IgnoreCase> ComparisonShards[FNamePoolShards];

	// Put constant lookup on separate cache line to avoid it being constantly invalidated by insertion
	alignas(PLATFORM_CACHE_LINE_SIZE) FNameEntryId ENameToEntry[NAME_MaxHardcodedNameIndex] = {};
	uint32 LargestEnameUnstableId;
	TMap<FNameEntryId, EName, TInlineSetAllocator<MaxENames>> EntryToEName;
};

Reference