UnLua & C++ 交互

Lua 中调用一般的 UFUNCTION 反射比较简单直接,这里以从 Lua 中调用 CFUNCTION 为例简单介绍两端交互。

具体为在 Lua 中的调用:local Table = UE4.UTestUtils.LuaCFunction(Val0, Val1),并返回一个 Lua Table : { "Val0" = Val0, "Val1" = Va1 }

定义

首先通过 ADD_STATIC_CFUNCTION 静态导出这个原生的 FGlueFunction

具体的实现可以在 UnLuaEx.h 中找到。

1
2
3
4
5
6
7
8
9
10
11
// Test.h
struct lua_State;

UCLASS()
class UTestUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

public:
static int LuaCFunction(lua_State* L);
}
1
2
3
4
5
// Test.cpp
BEGIN_EXPORT_REFLECTED_CLASS(UTestUtils)
ADD_STATIC_CFUNTION(LuaCFunction)
END_EXPORT_CLASS(UTestUtils)
IMPLEMENT_EXPORTED_CLASS(UTestUtils)

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int UTestUtils::LuaCFunction(lua_State* L)
{
const int Top = lua_gettop(L);
if (Top == 2)
{
int32 Val0 = lua_tointeger(L, 1);
int32 Val1 = lua_tointeger(L, 2);
UUnLuaFunctionLibrary::CreateLuaTable(L, "Val0", Val0, "Val1", Val1);
return 1;
}

luaL_error(L, "Call UTestUtils::LuaCFunction error! argc = %d", Top);
return 0;
}

CFUNCTIONLua 中调用时,会传入当前的 lua_State* L ,可以通过这个 LLuaStack 进行访问与写入。

首先这里的 Top = lua_gettop(L) 会根据 cast_int(L->top - (L->ci->func + 1)) 计算出返回参数的个数,比如这里的 (Val0, Val1) 就是 2 个参数。

接着通过 lua_tointeger(L, i) 将第 i 个参数取出。

然后通过 UUnLuaFunctionLibrary::CreateLuaTable,创建一个 Lua Table,进行赋值与写入,这个方法是实现的重点。

最后如果合法,return 1,返回写入 LuaStack 中返回值的个数,也就是有 1LuaTable 被写入了栈。

具体的与 lua 层交互的原始方法实现可以在 lapi.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
template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

template<typename K, typename V, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args)
{
PushKeyValue(L, Forward<K>(Key), Forward<V>(Value));
PushKeyValue(L, Forward<Types>(Args)...);
}

template<typename K, typename V>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value)
{
UnLua::Push(L, Forward<K>(Key));
UnLua::Push(L, Forward<V>(Value));
lua_rawset(L, -3);
}

这个 UUnLuaFunctionLibrary::CreateLuaTableL 对应的 LuaStack 中写入了一个赋值好的 LuaTable

首先通过 lua_newtable(L) 创建了一个空的 LuaTable,然后进行 PushKeyValue 的递归调用。

针对一次 PushKeyValue,首先会往 LuaStack 中压入 KeyValue,压入后先前的 LuaTable 处于在 StatckIndex=-3 的位置。

image-20240527120544564

然后针对 StackIndex = -3位置(也就是当前栈里的 LuaTable)调用 lua_rawset 方法,将 KeyValue 弹出并打包成参数塞进 LuaTable

这里的 lua_rawset 实际上类似 lua_settable。对于 lua_settable,会找到元方法 __newindex 并调用,对于 lua_rawset,则会调用默认的 __newindex 方法:

1
2
3
__newindex = function(table, key, value)
rawset(table, key, value)
end

这样,就完成了一个这样的方法实现。

给出 UUnluaFunctionLibrary 的更多扩展:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaArray(lua_State* L, const Types&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue<1>(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

// ------------------------

template<typename K, typename V>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value)
{
UnLua::Push(L, Forward<K>(Key));
UnLua::Push(L, Forward<V>(Value));
lua_rawset(L, -3);
}

template<typename K, typename V, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args)
{
PushKeyValue(L, Forward<K>(Key), Forward<V>(Value));
PushKeyValue(L, Forward<Types>(Args)...);
}

template<int N, typename T>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg)
{
UnLua::Push(L, N);
UnLua::Push(L, Forward<T>(Arg));
lua_rawset(L, -3);
}

template<int N, typename T, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg0, Types&&... Args)
{
PushKeyValue<N>(L, Forward<T>(Arg0));
PushKeyValue<N + 1>(L, Forward<Types>(Args)...);
}

template<typename K, typename T>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, TArray<T>& Array)
{
UnLua::Push(L, Forward<K>(Key));

lua_newtable(L);
for (int Index = 0; Index < Array.Num(); Index++)
{
UnLua::Push(L, Index + 1);
UnLua::Push(L, Array[Index]);
lua_rawset(L, -3);
}

lua_rawset(L, -3);
}
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Json 相关操作

template<typename K>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, const TSharedPtr<FJsonValue>& Value)
{
UnLua::Push(L, Forward<K>(Key));
PushValue(L, Value);
lua_rawset(L, -3);
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonObject>& JsonObject)
{
lua_newtable(L);
for (const auto& KeyValue : JsonObject->Values)
{
PushKeyValue(L, KeyValue.Key, KeyValue.Value);
}
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TArray<TSharedPtr<FJsonValue>>& JsonValues)
{
lua_newtable(L);
for (auto i = 0; i < JsonValues.Num(); i++)
{
PushKeyValue(L, i + 1, JsonValues[i]);
}
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonValue>& Value)
{
switch (Value->Type)
{
case EJson::String: UnLua::Push(L, Value->AsString()); break;
case EJson::Number: UnLua::Push(L, Value->AsNumber()); break;
case EJson::Boolean: UnLua::Push(L, Value->AsBool()); break;
case EJson::Array: PushValue(L, Value->AsArray()); break;
case EJson::Object: PushValue(L, Value->AsObject()); break;
default: lua_pushnil(L); break;
}
}

// ------------------------

int UUnLuaFunctionLibrary::GetLuaTableFromJsonPath(lua_State* L)
{
const int Top = lua_gettop(L);
if (Top != 1)
{
LogWarning(TEXT("Args count error. argc = %d"), Top);
return 0;
}

auto JsonFilePath = FString(UTF8_TO_TCHAR(lua_tostring(L, 1)));
if (!FPaths::FileExists(JsonFilePath))
{
LogWarning(TEXT("Json file path '%s' not found."), *JsonFilePath);
return 0;
}

FString FileContent;
if (FFileHelper::LoadFileToString(FileContent, *JsonFilePath))
{
TSharedPtr<FJsonObject> JsonObject;
auto JsonReader = TJsonReaderFactory<>::Create(FileContent);
if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
{
PushValue(L, JsonObject);
}
else
{
LogWarning(TEXT("Parse json from file '%s' failed."), *JsonFilePath);
return 0;
}
}
else
{
LogWarning(TEXT("Load file path '%s' failed."), *JsonFilePath);
return 0;
}

return 1;
}

参考

Lua 5.4 Reference4.6 – Functions and Types

Lua 虚拟栈交互流程

Unlua 解析