该文章主要记录本人在分析Unlua源代码所作的一些笔记,里面包含一些用到的lua语法的说明,以及动态绑定和静态绑定,并且还分析了C++和lua的互调流程。
lua语法 注册表 Lua的注册表可以被想象成一个全局的哈希表(或映射),键通常是字符串(如元表的名称)或者是特殊的指针或引用(对于需要唯一键的数据)。在注册表中,每个键都关联一个值,这个值可以是任何Lua支持的类型(如表、函数、用户数据等)。
用于从注册表中检索一个元表。这个函数主要用于获取之前已经注册到 Lua 的全局注册表中的元表,这些元表通常是与特定的用户数据类型或类关联的。
C++ 1 luaL_getmetatable (L, "TSharedPtr" );
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){ 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; 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 ); ULuaFunction ::GetOverridableFunctions (Class , BindInfo .UEFunctions ); 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 (); LuaFunction = static_cast<ULuaFunction *>(StaticDuplicateObjectEx (DuplicationParams )); LuaFunction ->Next = OverridesClass ->Children ; OverridesClass ->Children = LuaFunction ; LuaFunction ->StaticLink (true ); LuaFunction ->Initialize (); LuaFunction ->Override (Function , Class , bAddNew); LuaFunction ->Bind (); 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) { 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)) 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) { 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函数分析可以得出大概如下:
其中包含对应的Class的和变量名称或者函数名称。
对于注册绑定的功能位于GetField函数里面,接下来分析下该函数
C++ 1 2 3 4 5 6 7 8 9 10 FORCEINLINE static int32 GetField (lua_State* L){ lua_getmetatable (L, 1 ); lua_pushvalue (L, 2 ); int32 Type = lua_rawget (L, -2 ); if (Type == LUA_TNIL ) 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 ); lua_pushstring (L, "__name" ); auto Type = lua_rawget (L, -2 ); check (Type == LUA_TSTRING ); const char* ClassName = lua_tostring (L, -1 ); const char* FieldName = lua_tostring (L, 2 ); lua_pop (L, 1 ); 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 (); if (!bCached) { PushField (L, Field ); lua_pushvalue (L, 2 ); lua_pushvalue (L, -2 ); lua_rawset (L, -4 ); } if (bInherited) { lua_remove (L, -2 ); lua_pushvalue (L, 2 ); lua_pushvalue (L, -2 ); lua_rawset (L, -4 ); } } else { if (ClassDesc ->IsClass ()) { luaL_getmetatable (L, "UClass" ); lua_pushvalue (L, 2 ); lua_rawget (L, -2 ); lua_remove (L, -2 ); } else { lua_pushnil (L); } } }
下面着重对下面这个代码的栈分析一下:
C++ 1 2 3 4 if (!bCached){ PushField (L, Field ); }
初始的栈是这样的 看到 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 ); }
最终栈顶是一个TSharedPtr的userdata,接下来继续看PushField函数
C++ 1 2 3 4 5 6 7 8 if (Function ->IsLatentFunction ()){ lua_pushcclosure (L, Class _CallLatentFunction, 1 ); } else { lua_pushcclosure (L, Class _CallUFunction, 1 ); }
这一部分是表示将一个C++函数和一个upvalue作为一个闭包推入栈顶,并从栈中弹出upvalue
现在回到GetFieldInternal函数
C++ 1 2 3 4 5 6 7 if (!bCached){ PushField (L, Field ); lua_pushvalue (L, 2 ); lua_pushvalue (L, -2 ); lua_rawset (L, -4 ); }
执行完之后栈里面又剩下Inst和Name,后面执行这个清理栈
C++
接下来是调用的步骤: 前面已经看到对于非延迟的函数其会调用的Class_CallUFunction函数:
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int32 Class _CallUFunction(lua_State *L) { 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 ); 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 ); lua_rawset (L, -3 );
Lua调用静态导出的函数 根据上面的就能看出来,函数调用的时候最终会调用到这个C闭包函数InvokeFunction
C++ 1 2 IExportedFunction *Func = (IExportedFunction *)lua_touserdata (L, lua_upvalueindex (1 ));return Func ? Func ->Invoke (L) : 0 ;