Unlua代码分析

该文章主要记录本人在分析Unlua源代码所作的一些笔记,里面包含一些用到的lua语法的说明,以及动态绑定和静态绑定,并且还分析了C++和lua的互调流程。

lua语法

注册表

Lua的注册表可以被想象成一个全局的哈希表(或映射),键通常是字符串(如元表的名称)或者是特殊的指针或引用(对于需要唯一键的数据)。在注册表中,每个键都关联一个值,这个值可以是任何Lua支持的类型(如表、函数、用户数据等)。

luaL_getmetatable

用于从注册表中检索一个元表。这个函数主要用于获取之前已经注册到 Lua 的全局注册表中的元表,这些元表通常是与特定的用户数据类型或类关联的。

C++
1
luaL_getmetatable(L, "TSharedPtr");

luaL_newmetatable

lua_getmetatable

Unlua

UObject的注册以及Lua覆写函数流程

UObject注册是从NotifyUObjectCreated这个函数监听Object创建开始的。注册函数bool FLuaEnv::TryBind(UObject* Object) ,判断是不是静态绑定

C++
1
const bool bImplUnluaInterface = Class->ImplementsInterface(InterfaceClass);

看看是不是动态绑定,动态绑定的时候会通过

C++
1
2
3
static int32 UWorld_SpawnActor(lua_State* L)
static int32 UWorld_SpawnActorEx(lua_State* L)
static int32 Global_NewObject(lua_State *L)

上面三个函数将路径信息传递到C++这边,然后通过下面的函数记录到全局变量GLuaDynamicBinding中

C++
1
bValid = GLuaDynamicBinding.Push(Class, ModuleName, InitializerTableRef);

因此现在看下面的动态绑定代码就很好理解了

C++
1
2
3
4
5
6
7
8
if (!bImplUnluaInterface)
{
// dynamic binding
if (!GLuaDynamicBinding.IsValid(Class))
return false;

return GetManager()->Bind(Object, *GLuaDynamicBinding.ModuleName, GLuaDynamicBinding.InitializerTableRef);
}

接着往下走,动态绑定和静态绑定的绑定函数是一样的都是 GetManager()->Bind,现在继续分析Bind函数,在绑定UObject的时候会先绑定对应的Class ,首先创建对应的Matetable并进入到Name2Classes

C++
1
2
3
4
5
6
7
8
const auto L = Env->GetMainState();
if (!PushMetatable(L, MetatableName))
return nullptr;

// TODO: refactor
lua_pop(L, 1);
FName Key = FName(UTF8_TO_TCHAR(MetatableName));
return Name2Classes.FindChecked(Key);

之后试图去绑定对应的lua文件,然后就是正式的BindClass

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool UUnLuaManager::BindClass(UClass* Class, const FString& InModuleName, FString& Error)
{
check(Class);
//.................
auto& BindInfo = Classes.Add(Class);
BindInfo.Class = Class;
BindInfo.ModuleName = InModuleName;
BindInfo.TableRef = Ref;

UnLua::LowLevel::GetFunctionNames(Env->GetMainState(), Ref, BindInfo.LuaFunctions);//获取lua中的所有函数名称
ULuaFunction::GetOverridableFunctions(Class, BindInfo.UEFunctions);//获取类里面的反射函数

// 用LuaTable里所有的函数来替换Class上对应的UFunction
for (const auto& LuaFuncName : BindInfo.LuaFunctions)
{
UFunction** Func = BindInfo.UEFunctions.Find(LuaFuncName);
if (Func)
{
UFunction* Function = *Func;
ULuaFunction::Override(Function, Class, LuaFuncName); //要复写的函数
}
}

//.................
return true;
}

可以看到覆盖的过程就是遍历lua函数名称,然后看是否能够在反射函数中找到对应的同名函数,找到了就走重写代码 ULuaFunction::Override(Function, Class, LuaFuncName);

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void FLuaOverrides::Override(UFunction* Function, UClass* Class, FName NewName)
{
const auto OverridesClass = GetOrAddOverridesClass(Class);

ULuaFunction* LuaFunction;
UObject* Outer=Function->GetOuter();
const auto bAddNew = Outer != Class;
if (bAddNew)
{
const auto Exists = Class->FindFunctionByName(NewName, EIncludeSuperFlag::ExcludeSuper);
if (Exists && Exists->GetSuperStruct() == Function)
return;
}
else
{
LuaFunction = Cast<ULuaFunction>(Function); //已经覆写了就跳过
if (LuaFunction)
{
LuaFunction->Initialize();
return;
}
}

FObjectDuplicationParameters DuplicationParams(Function, OverridesClass);
DuplicationParams.InternalFlagMask &= ~EInternalObjectFlags::Native;
DuplicationParams.DestName = NewName;
DuplicationParams.DestClass = ULuaFunction::StaticClass();
// 拷贝一份Function
LuaFunction = static_cast<ULuaFunction*>(StaticDuplicateObjectEx(DuplicationParams));
// 加入到OverridesClass的Children 链接中去
LuaFunction->Next = OverridesClass->Children;
OverridesClass->Children = LuaFunction;

LuaFunction->StaticLink(true);
LuaFunction->Initialize(); //一些初始化的操作
LuaFunction->Override(Function, Class, bAddNew); // 真正的覆写的位置
LuaFunction->Bind();
//防止GC
if (Class->IsRooted() || GUObjectArray.IsDisregardForGC(Class))
LuaFunction->AddToRoot();
else
LuaFunction->AddToCluster(Class);
}

上面的函数需要注意const auto bAddNew = Outer != Class; 当重写的函数是蓝图函数的时候bAddNew的值就是false,当重写的函数的C++函数的时候bAddNew就是true,这里主要是判断该重写的函数是否被蓝图重写,这个在下面的步骤会用到。

接着走到void ULuaFunction::Override(UFunction* Function, UClass* Class, bool bAddNew)

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    check(Function && Class && !From.IsValid());

#if WITH_METADATA
UMetaData::CopyMetadata(Function, this);
#endif

bActivated = false;
bAdded = bAddNew;
From = Function;

if (Function->GetNativeFunc() == execScriptCallLua)
{
// 目标UFunction可能已经被覆写过了
const auto LuaFunction = Get(Function);
Overridden = LuaFunction->GetOverridden();
check(Overridden);
}
else
{
const auto DestName = FString::Printf(TEXT("%s__Overridden"), *Function->GetName());
if (Function->HasAnyFunctionFlags(FUNC_Native)) //判断是不是C++函数,C++函数的则将这个函数加入到类中
GetOuterUClass()->AddNativeFunction(*DestName, *Function->GetNativeFunc());
Overridden = static_cast<UFunction*>(StaticDuplicateObject(Function, GetOuter(), *DestName));
Overridden->ClearInternalFlags(EInternalObjectFlags::Native);
Overridden->StaticLink(true);
Overridden->SetNativeFunc(Function->GetNativeFunc());
}

SetActive(true);

然后就是拷贝一份添加__Overridden存储在LuaFunction中的Overridden 变量中,以便恢复,最后的绑定则在SetActive函数里面

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if (bAdded)  //这就用到前面的额值bAddNew,表示C++函数就绑定execCallLua
{
check(!Class->FindFunctionByName(GetFName(), EIncludeSuperFlag::ExcludeSuper));
SetSuperStruct(Function);
FunctionFlags |= FUNC_Native;
ClearInternalFlags(EInternalObjectFlags::Native);
SetNativeFunc(execCallLua);

Class->AddFunctionToFunctionMap(this, *GetName());
if (Function->HasAnyFunctionFlags(FUNC_Native))
Class->AddNativeFunction(*GetName(), &ULuaFunction::execCallLua);
}
else
{
SetSuperStruct(Function->GetSuperStruct());
Script = Function->Script;
Children = Function->Children;
ChildProperties = Function->ChildProperties;
PropertyLink = Function->PropertyLink;

Function->FunctionFlags |= FUNC_Native;
Function->SetNativeFunc(&execScriptCallLua);
Function->GetOuterUClass()->AddNativeFunction(*Function->GetName(), &execScriptCallLua);
Function->Script.Empty();
Function->Script.AddUninitialized(ScriptMagicHeaderSize + sizeof(ULuaFunction*));
const auto Data = Function->Script.GetData();
FPlatformMemory::Memcpy(Data, ScriptMagicHeader, ScriptMagicHeaderSize);
FPlatformMemory::WriteUnaligned<ULuaFunction*>(Data + ScriptMagicHeaderSize, this);
}

从上面的代码可以看出,当覆盖的是C++的函数时候往FuncMap中添加LuaFunction,当是蓝图函数的时候就直接拿传递进来的Function改造将NativeFunc设置成execScriptCallLua

C++调用被Lua覆写的C++或者蓝图函数

比如当我们调用下面的函数的时候:

C++
1
2
3
4
5
6
UFUNCTION(BlueprintNativeEvent,BlueprintCallable)
void SpawnText();
void UTPSGameInstance::SpawnText_Implementation()
{
GEngine->AddOnScreenDebugMessage(1,10,FColor::White,TEXT("C++ SpawnText_Implementation"));
}

可以看到函数会直接走到

C++
1
2
3
4
5
static FName NAME_UTPSGameInstance_SpawnText = FName(TEXT("SpawnText"));
void UTPSGameInstance::SpawnText()
{
ProcessEvent(FindFunctionChecked(NAME_UTPSGameInstance_SpawnText),NULL);
}

首先是通过FindFunctionChecked查找FuncMap里面的函数,就可以找到之前覆盖的LuaFunction,这样就调用到对应的execCallLua和execScriptCallLua 进而调到lua的里面覆写的函数。

Lua调用C++UFuncition函数流程

创建Class的Matetable的时候会把C++的Class_Index函数与__index绑定

C++
1
2
3
4
luaL_newmetatable(L, MetatableName);
lua_pushstring(L, "__index");
lua_pushcfunction(L, Class_Index);
lua_rawset(L, -3);

因此当lua调用函数的时候发现没有对应的对象就会触发__index,因此下面我们就从C++的Class_Index函数函数开始分析:

下面是Class_Index函数:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int32 Class_Index(lua_State *L)
{
int size= lua_gettop(L);
GetField(L);

auto Ptr = lua_touserdata(L, -1);
if (!Ptr)
return 1;

auto Property = static_cast<TSharedPtr<UnLua::ITypeOps>*>(Ptr);
if (!Property->IsValid())
return 0;

auto Self = GetCppInstance(L, 1);
if (!Self)
return 1;

if (UnLua::LowLevel::IsReleasedPtr(Self))
return luaL_error(L, TCHAR_TO_UTF8(*FString::Printf(TEXT("attempt to read property '%s' on released object"), *(*Property)->GetName())));

if (!UnLua::LowLevel::CheckPropertyOwner(L, (*Property).Get(), Self))
return 0;

(*Property)->ReadValue_InContainer(L, Self, false);
lua_remove(L, -2);
return 1;
}

当触发Class_Index函数的时候栈是什么样的呢,在对GetField函数分析可以得出大概如下:
alt text

其中包含对应的Class的和变量名称或者函数名称。

对于注册绑定的功能位于GetField函数里面,接下来分析下该函数

C++
1
2
3
4
5
6
7
8
9
10
FORCEINLINE static int32 GetField(lua_State* L)
{
lua_getmetatable(L, 1); //获取栈底的matetable放入栈顶
lua_pushvalue(L, 2); //获取栈底往上数第二的元素放到栈顶
int32 Type = lua_rawget(L, -2); //在matetable查找栈顶的key,并移除栈顶将查找到的对象放入栈顶,没找到的话此时栈顶值为nil
if (Type == LUA_TNIL) //表示在matetable没有找到key的value,则表示不存在
GetFieldInternal(L);
lua_remove(L, -2);
return 1;
}

接下来进入到GetFieldInternal(L);

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static void GetFieldInternal(lua_State* L) 
{
lua_pop(L, 1); //把栈顶的nil弹出来

lua_pushstring(L, "__name"); //插入__name到栈顶
auto Type = lua_rawget(L, -2); //然后在前面的inst里面查找__name并加入到栈顶
check(Type == LUA_TSTRING); //检查是否字符串

const char* ClassName = lua_tostring(L, -1); // 获取栈顶的的字符串
const char* FieldName = lua_tostring(L, 2); // 获取栈底第二的Name

lua_pop(L, 1); // 弹出类名

// TODO: refactor
const auto Registry = UnLua::FLuaEnv::FindEnv(L)->GetClassRegistry(); //类注册器
FClassDesc* ClassDesc = Registry->Register(ClassName); //获取注册的类
TSharedPtr<FFieldDesc> Field = ClassDesc->RegisterField(FieldName, ClassDesc); //生成注册信息
if (Field && Field->IsValid())
{
bool bCached = false;
bool bInherited = Field->IsInherited();

//bInherited 删除一些代码

if (!bCached)
{
PushField(L, Field); // Property / closure
lua_pushvalue(L, 2); // key
lua_pushvalue(L, -2); // Property / closure
lua_rawset(L, -4);
}
if (bInherited)
{
lua_remove(L, -2);
lua_pushvalue(L, 2); // key
lua_pushvalue(L, -2); // Property / closure
lua_rawset(L, -4);
}
}
else
{
if (ClassDesc->IsClass())
{
luaL_getmetatable(L, "UClass");
lua_pushvalue(L, 2); // push key
lua_rawget(L, -2);
lua_remove(L, -2);
}
else
{
lua_pushnil(L);
}
}
}

下面着重对下面这个代码的栈分析一下:

C++
1
2
3
4
if (!bCached)
{
PushField(L, Field); // Property / closure
}

初始的栈是这样的
alt text
看到 PushField(L, Field);里面的注册函数部分

C++
1
2
3
4
5
6
7
8
template <typename T>
void FObjectRegistry::Push(lua_State* L, TSharedPtr<T> Ptr)
{
const auto Userdata = lua_newuserdata(L, sizeof(TSharedPtr<T>));
luaL_getmetatable(L, "TSharedPtr");
lua_setmetatable(L, -2);
new(Userdata) TSharedPtr<T>(Ptr);
}

alt text

最终栈顶是一个TSharedPtr的userdata,接下来继续看PushField函数

C++
1
2
3
4
5
6
7
8
if (Function->IsLatentFunction())
{
lua_pushcclosure(L, Class_CallLatentFunction, 1); // closure
}
else
{
lua_pushcclosure(L, Class_CallUFunction, 1); // closure
}

这一部分是表示将一个C++函数和一个upvalue作为一个闭包推入栈顶,并从栈中弹出upvalue

alt text

现在回到GetFieldInternal函数

C++
1
2
3
4
5
6
7
if (!bCached)
{
PushField(L, Field); // Property / closure
lua_pushvalue(L, 2); // 把Name拷贝一份到栈顶,closure的index成了-2
lua_pushvalue(L, -2); //把closure拷贝一份到栈顶
lua_rawset(L, -4); // 把-1和-2号位置的设置到-4号位置,然后出栈-1和-2
}

执行完之后栈里面又剩下Inst和Name,后面执行这个清理栈

C++
1
lua_remove(L, -2);

接下来是调用的步骤:
前面已经看到对于非延迟的函数其会调用的Class_CallUFunction函数:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int32 Class_CallUFunction(lua_State *L)
{
//!!!Fix!!!
//delete desc when is not valid
auto& Env = UnLua::FLuaEnv::FindEnvChecked(L);
auto Function = Env.GetObjectRegistry()->Get<FFunctionDesc>(L, lua_upvalueindex(1));
if (!Function->IsValid())
{
UE_LOG(LogUnLua, Log, TEXT("%s: Invalid function descriptor!"), ANSI_TO_TCHAR(__FUNCTION__));
return 0;
}
int32 NumParams = lua_gettop(L);
int32 NumResults = Function->CallUE(L, NumParams);
return NumResults;
}

上面的重点是lua_upvalueindex(1)获取到upvalue,从而获取到UserData转成FuncDesc,Function->CallUE(L, NumParams);这个就是实际调用到C++函数并提取lua传递过来的参数赋值给C++函数的地方,
调用栈

静态到出C++到Lua

LuaEvn初始化的时候就会导出

C++
1
2
3
auto ExportedNonReflectedClasses = GetExportedNonReflectedClasses();
for (const auto& Pair : ExportedNonReflectedClasses)
Pair.Value->Register(L);

下面就是静态注册的过程,首先创建ClassName对应的metatable

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 luaL_newmetatable(L, ClassName.Get());

if (!SuperClassName.IsEmpty())
{
lua_pushstring(L, "Super");
Type = luaL_getmetatable(L, TCHAR_TO_UTF8(*SuperClassName));
check(Type == LUA_TTABLE);
lua_rawset(L, -3);
}

lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
if (Properties.Num() > 0 || !SuperClassName.IsEmpty())
{
lua_pushcclosure(L, UnLua::Index, 1);
}
lua_rawset(L, -3);

lua_pushstring(L, "__newindex");
lua_pushvalue(L, -2);
if (Properties.Num() > 0 || !SuperClassName.IsEmpty())
{
lua_pushcclosure(L, UnLua::NewIndex, 1);
}
lua_rawset(L, -3);

lua_pushvalue(L, -1); // set metatable to self
lua_setmetatable(L, -2);

然后就开始注册成员函数和成员变量

C++
1
2
3
4
5
6
7
8
for (const auto& Property : Properties)
Property->Register(L);

for (const auto& MemberFunc : Functions)
MemberFunc->Register(L);

for (const auto& Func : GlueFunctions)
Func->Register(L);

最后把这个注册的matetable写入到UE的表中

C++
1
2
3
4
5
lua_getglobal(L, "UE");
lua_pushstring(L, ClassName.Get());
lua_pushvalue(L, -3);
lua_rawset(L, -3);
lua_pop(L, 2);

成员函数是下面的注册方式

C++
1
2
3
4
lua_pushstring(L, TCHAR_TO_UTF8(*Name));   //函数名到栈顶
lua_pushlightuserdata(L, this); //导出函数对象指针到栈顶
lua_pushcclosure(L, InvokeFunction, 1); //推一个闭包函数并把lightuserdata作为upvalue
lua_rawset(L, -3); //将Name和那个闭包作为key-value设置到matetable中

Lua调用静态导出的函数

根据上面的就能看出来,函数调用的时候最终会调用到这个C闭包函数InvokeFunction

C++
1
2
IExportedFunction *Func = (IExportedFunction*)lua_touserdata(L, lua_upvalueindex(1));
return Func ? Func->Invoke(L) : 0;