UE4 Unlua源码解析7 - Lua通过UE命名空间访问C++类型的实现原理

Posted 珞珈大胖强TURBO

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 Unlua源码解析7 - Lua通过UE命名空间访问C++类型的实现原理相关的知识,希望对你有一定的参考价值。

Lua通过UE命名空间访问C++类型的实现原理


举例 UE4.UKismetSystemLibrary.PrintString(“hello”)

我们来看Unlua提供的例子的HelloWorld

代码是

local hello = “HelloWorld”
UE4.UKismetSystemLibrary.PrintString(hello)

这个例子我们最终会调到UE4的方法,并且成功执行,那么首先,我们要解答的问题就是为什么Lua这么写最终能调用到C++的方法,

而且Lua传的参数是Lua的字符串,为什么C++能成功执行,这简单的代码,背后到底做了什么事情

1.1 UE4发生了 什么

Unlua.lua中声明了UE4,可以看到UE4其实就是全局表_G,而且他的元表是global_mt,Index元方法为global_index,我们去看看global_index

第73行拿到传入的索引k,检查第一个字母是不是UE的类的首字母,是就调用RegisterClass,如果是E,就调用RegisterEnum,然后调用rawget,rawget方法是不使用Index元方法的取值函数,可以猜测上面的函数执行完之后,t表也就是UE4表里已经就有了索引k的值了。

具体的执行逻辑接着看

1.2 UE4.UKismetSystemLibrary发生了什么

由上节讲到的,UE4.UKismetSystemLibrary的时候,因为UE4是表,所以就去表里找UKismetSystemLibrary,但是第一次肯定是找不到的,于是走到UE4的元表的元方法Index,也就是执行global_index,传进来的t是UE4,k是“UKismetSystemLibrary”

由之前的分析可以看到代码会走到RegisterClass,然后传入k

RegisterClass在lua测的声明如下

看到LuaContext.cpp里

lua api lua_register前面讲到了,将C函数f设置为全局名称name的新值,lua端可以通过name调用C方法f

所以这行代码将C函数Global_RegisterClass注册,在Lua端通过Lua全局名称RegisterClass调用

Global_RegisterClass和RegisterClass逐行解释之前讲过了,此处只说结果

Global_RegisterClass 读虚拟栈的大小,当小于1的时候返回,否则调用RegisterClass,参数传(虚拟机,栈底的值转成C 的string)

此时虚拟栈的内容是传进来类名“UKismetSystemLibrary”,以及参数的数量1,所以传递给RegisterClass的参数是(虚拟机L,“UKismetSystemLibrary”)

RegisterClass逐行解释之前讲过了,总之是两大部分

1 根据UE4反射生成UClass的描述信息FClassDesc ,然后存储到UnLua的存储全局反射数据的GReflectionRegistry对象中。

2 根据生成的FClassDesc 为其在Lua中注册table,然后设置table的元表为自己,然后往table里塞一些元方法

RegisterClass结束后,UE4.UKismetSystemLibrary就是一个Lua端的表了,这个表的元表是自己,并且元方法Index是LuaCore中的方法Class_Index

类图如下

1.3 UE4.UKismetSystemLibrary.PrintString发生了什么

截至目前,UE4.UKismetSystemLibrary就能拿到上一步生成的Lua table了,此时调用.PrintString的时候,可以看到表里没用这个数据,所以走到元表的Index元方法,之前我们知道,元方法为LuaCore中的Class_Index,所以重点看这个方法即可

其中最重要的是走到GetField

GetField和RegiestClass很像,就像是注册类一样,GetField主要也做两件事

1

​ 如果传进来的是属性,就根据UE4反射生成FPropertyDesc

​ 如果传进来的是方法,就根据UE4反射生成FFunctionDesc,并且把FPropertyDesc或FFunctionDesc存在FClassDesc中

2

​ 如果是属性 就在表里存FPropertyDesc,然后将FPropertyDesc压栈,作为返回值返回

​ 如果是方法,就在表里存FFunctionDesc+C方法Class_CallUFunction的闭包进去,FFunctionDesc作为Upvalue。然后将FFunctionDesc+C方法Class_CallUFunction的闭包压栈,作为返回值返回

此时的类图如下

1.4 UE4.UKismetSystemLibrary.PrintString(“Hello”)发生了什么

截至目前,相当于调用LuaCore的Class_CallUFunction

传进来的参数是lua的string hello

根据之前的解释C++通过闭包里的FFunctionDesc执行方法CallUE,参数是Lua传进去的,通过

1)执行PreCall,根据【UFunctionDesc】和Lua参数,将Lua参数转换成C++参数,再根据【UFunctionDesc】反射信息,将C++参数写入到一个缓存区中

2)执行 UObject::ProcessEvent(FinalFunction, Params),参数FinalFunction是UFunction,参数Params是前面保存有C++参数的缓存区。ProcessEvent执行时,即调用了真正我们想调用的C++ Create函数,然后把C++返回值放入了Params缓存区中

3)执行PostCall,从Params缓存区中读出C++返回值,转换成Lua返回值,Push进Lua栈,返回给Lua。

结束,至此,屏幕应该输出Hello

以上是关于UE4 Unlua源码解析7 - Lua通过UE命名空间访问C++类型的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

UE4 Unlua源码解析7 - Lua通过UE命名空间访问C++类型的实现原理

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析11 - 非UE4反射支持的静态类导出给Lua使用原理

UE4 Unlua源码解析1 - 读源码的前置知识