【问题标题】:How to access variables of the class that runs the Lua script from Lua如何从 Lua 访问运行 Lua 脚本的类的变量
【发布时间】:2018-07-08 18:38:16
【问题描述】:

我想知道是否可以从 Lua 脚本中使用的绑定 C++ 类访问运行 Lua 脚本的类的变量。

从下面的示例中,我想知道是否可以从绑定的Test 类中以某种方式访问​​myLua 类中的name 变量。

这是我的代码。

main.cpp:

extern "C" 
{
    int luaopen_my(lua_State* L);
}

class myLua {

public:

    struct myData
    {
        std::string name;
        lua_State *L;
    };
    myLua(std::string name)
    {
        data = make_shared<myData>();
        data->name = name;
        data->L = luaL_newstate();
        lua_State *L = data->L;
        luaL_openlibs(L);
        luaopen_my(L);
        lua_settop(L, 0);

        const char *script =
        "function setup() \
           test = my.Test() \
           test:callHello() \
         end \
         function hello(name) \
           print('hello is called by : ' .. name) \
         end";
        //------------Added----------------
        lua_pushlightuserdata(L, data.get());
        myLua::myData *b = static_cast<myLua::myData *>(lua_touserdata(L, 1));
        cout << "RESULT1 : " << b->name << endl;
        //---------------------------------

        const int ret = luaL_loadstring(L, script);
        if (ret != 0 || lua_pcall(L, 0, LUA_MULTRET, 0) != 0)
        {
            std::cout << "failed to run lua script" << std::endl;
            return;
        }
        lua_getglobal(L, "setup");
        if (lua_pcall(L, 0, 0, 0))
        {
            std::cout << "failed to call setup function" << std::endl;
            return;
        }
    }
    shared_ptr<myData> data;
};

void main() 
{
    myLua lua1("Apple");
    myLua lua2("Orange");
}

绑定.h:

class Test
{
public:
    void callHello(lua_State *L) {

        //------------Added----------------
        myLua::myData *b = static_cast<myLua::myData *>(lua_touserdata(L, -1));
        cout << "RESULT2 : " << b->name << endl;
        //---------------------------------

        lua_getglobal(L, "hello");
        lua_pushstring(L, "ClassName");
        if (lua_pcall(L, 1, 0, 0))
        {
            std::cout << "failed to call hello function" << std::endl;
            return;
        }
    };
};

bindings.i :(用于使用 SWIG 绑定 bindings.h

%module my
%{
    #include "bindings.h"
%}

%include <stl.i>
%include <std_string.i>
%include <std_vector.i>
%include <std_map.i>
%include <typemaps.i>

%typemap(default) (lua_State *L) 
{
    $1 = L;
}
typedef std::string string;

%include "bindings.h"

当前结果:

hello is called by : ClassName
hello is called by : ClassName

我想要的结果:

hello is called by : Apple
hello is called by : Orange

也许我可以以某种方式将变量注册到lua_State*

如果有类似的东西我觉得会很棒

lua_registerdata(L, &amp;name);

然后用类似的东西得到它

string name = lua_getregistereddata(L);

添加代码的结果:

RESULT1 : Apple
RESULT2 : \360n\240\300`\255\276\255\336\336\300ݺ\220\300`DD\255\276\255\336\336\300ݺ\300\217\300`\340_\300`D\376
hello is called by : ClassName
RESULT1 : Orange
RESULT2 : \360n\300`\255\276\255\336\336\300ݺ\200\236\300`DD\255\276\255\336\336\300ݺ@\236\300``w\300`D\376
hello is called by : ClassName

【问题讨论】:

  • 如果你每次都创建一个新状态,只需将name 变量推送到全局表中。 lua_pushstring(L, "orange"); lua_setglobal(L, "g_contextName")。那么 callHello 就是lua_getglobal(L, "hello"); lua_getglobal(L, "g_contextName"); if (lua_pcall(L, 1, 0, 0))
  • @JamesPoag 谢谢,但是如果name 变量不是字符串而是其他类型,例如结构指针呢?
  • a 的范围/生命周期是多少? a 是否超出范围? &amp;a 是否仍指向有效内存? data 更好,因为它是在堆上分配的。具体来说,您可以使用data.get() 来获取原始指针。请记住,它不是引用计数。
  • lua_pushlightuserdata(&amp;L, data.get()),然后检查指针值是否与您稍后使用 lua_touserdata 检索的值相同
  • 你能更新你的例子吗?我无法想象 Lua 堆栈顶部仍然有 lightuserdata。这就是为什么我建议将 lightuserdata 存储在全局表 (setglobal) 中。

标签: c++ lua swig


【解决方案1】:

按值传递

我建议您将name 作为参数传递给setupcallHello。这解决了对象生命周期的问题。

注: 从 C++ 调用 Lua 函数,然后从 Lua 调用 C++ 函数似乎非常低效。你确定你的设计吗?您真的需要通过 Lua 进行这种额外的间接寻址吗?

bindings.h

#pragma once

#include <iostream>
#include <string>

class Test {
public:
    void callHello(std::string const &name, lua_State *L) {
        lua_getglobal(L, "hello");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call hello function\n"
                      << lua_tostring(L, -1) << '\n';
            return;
        }
    }
};

test.cpp

#include <iostream>
#include <string>

#include <lua.hpp>

extern "C" int luaopen_my(lua_State *L);

class myLua {
public:
    myLua(std::string const &name) {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        luaopen_my(L);

        const char *script = "function setup(name)\n"
                             "    local test = my.Test()\n"
                             "    test:callHello(name)\n"
                             "end\n"
                             "function hello(name)\n"
                             "    print('hello is called by : ' .. name)"
                             "end";

        if (luaL_dostring(L, script) != 0) {
            std::cout << "failed to run lua script\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_getglobal(L, "setup");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call setup function\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_close(L);
    }
};

int main() {
    myLua lua1("Apple");
    myLua lua2("Orange");
}

通过 lightuserdata 传递

根据您的要求,您还可以将指向字符串的指针作为 lightuserdata 推送到注册表中,并在 callHello 函数中获取它。由于各种原因,使用注册表是危险的。钥匙可能会发生碰撞,您必须绝对确定该钥匙没有在其他地方使用过。指向 C++ 数据的指针可能会悬空,而 Lua 不会也无法知道这一点,并且会很高兴地给你一个无效的指针。取消引用会导致难以调试的分段错误。

注: 我认为这是一个糟糕的设计,应该避免。为了不必传递参数的方便而放弃内存安全听起来不是一个好的权衡。

bindings.h

#pragma once

#include <iostream>
#include <string>

class Test {
public:
    void callHello(lua_State *L) {
        // Fetch light userdata from the registry with key "name" and
        // pray that it is there
        lua_pushstring(L, "name");
        lua_gettable(L, LUA_REGISTRYINDEX);
        std::string name;
        if (lua_islightuserdata(L, -1) == 1) {
            name = *static_cast<std::string *>(lua_touserdata(L, -1));
            lua_pop(L, 1);
        } else {
            lua_pushstring(L, "userdata corrupted or absent");
            lua_error(L);
            return;
        }

        // Call hello function with fetched name
        lua_getglobal(L, "hello");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call hello function\n"
                      << lua_tostring(L, -1) << '\n';
            return;
        }
    }
};

test.cpp

#include <iostream>
#include <string>

#include <lua.hpp>

extern "C" int luaopen_my(lua_State *L);

class myLua {
public:
    myLua(std::string name) {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        luaopen_my(L);

        const char *script = "function setup()\n"
                             "    local test = my.Test()\n"
                             "    test:callHello()\n"
                             "end\n"
                             "function hello(name)\n"
                             "    print('hello is called by : ' .. name)"
                             "end";

        if (luaL_dostring(L, script) != 0) {
            std::cout << "failed to run lua script\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        // Push light userdata into the registry with key "name"
        lua_pushstring(L, "name");
        lua_pushlightuserdata(L, static_cast<void *>(&name));
        lua_settable(L, LUA_REGISTRYINDEX);

        lua_getglobal(L, "setup");
        if (lua_pcall(L, 0, 0, 0) != 0) {
            std::cout << "failed to call setup function\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_close(L);
    }
};

int main() {
    myLua lua1("Apple");
    myLua lua2("Orange");
}

公共位

无需调整 SWIG 接口文件,无论哪种情况都保持不变。

my.i

%module my
%{
    #include "bindings.h"
%}

%include <std_string.i>
%include <typemaps.i>

%typemap(default) (lua_State *L) 
{
    $1 = L;
}

%include "bindings.h"

两种情况下都可以编译和运行(例如)

$ swig -lua -c++ my.i
$ clang++ -Wall -Wextra -Wpedantic -I/usr/include/lua5.2/ my_wrap.cxx test.cpp -llua5.2
$ ./a.out 
hello is called by : Apple
hello is called by : Orange

【讨论】:

  • @ZackLee 您可以在不通过 Lua 函数传递 name 的情况下做到这一点,但是您正在创建一个不应该存在并且可以轻松避免的所有权问题。你确定要这样做吗?
  • @ZackLee 请参阅我更新答案的第二部分。
  • @ZackLee 不同的 Lua 状态有不同的注册表,所以你不必担心串扰。但在同一个 Lua 状态下,您可能会意外覆盖现有字段。您可以在写入前检查字段是否存在,但在读取时无法知道该字段是否已被覆盖。如果您加载第三方模块,它可能会将自己的密钥插入与您的冲突的注册表中。因此,最好在使用字符串作为键时也添加前缀,例如"ZackLee_name" 而不是 "name"(前缀 "lua""lualib" 以及任何数字键都是保留的)。
  • @JamesPoag 在我看来,这比使用注册表更糟糕,因为这样你也可以与任何常规 Lua 变量发生冲突(并且你的“key”必须是一个字符串)。
  • @JamesPoag 你是对的,当使用足够模糊的前缀时,你也可能会避免全局变量的冲突。但是,您必须与 Lua 界面的用户沟通,应避免接触具有特定前缀的变量。此外,用户可能会忽略该警告以查看发生了什么并且程序开始崩溃。这将导致大量的错误报告。最好使用注册表将其从 Lua 界面中隐藏。
猜你喜欢
  • 1970-01-01
  • 2016-06-19
  • 2015-08-30
  • 2011-08-11
  • 1970-01-01
  • 2014-11-05
  • 2015-11-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多