如何获取从 C++ 发送到 Lua 函数的表的更新值?

Posted

技术标签:

【中文标题】如何获取从 C++ 发送到 Lua 函数的表的更新值?【英文标题】:How to get updated value of table sent from C++ to Lua function? 【发布时间】:2018-11-08 08:33:28 【问题描述】:

我正在尝试将浮点向量从 C++ 函数传递给 Lua 函数作为表参数,然后在 Lua 函数调用后获取向量的更新值。

这是简单的示例代码。

void myFunc(lua_State *L, std::vector<float> &vec) 

    lua_getglobal(L, "myFunc");
    lua_newtable(L);

    for (size_t i=0; i<vec.size(); ++i) 

        lua_pushinteger(L, i+1);
        lua_pushnumber(L, vec[i]);
        lua_settable(L, -3);
    
    if (lua_pcall(L, 1, 0, 0) != 0) 

        std::cout << "Error : Failed to call myFunc" << std::endl;
    

然后我可以像下面这样调用这个函数。

std::vector<float> vec = 1,2,3,4,5; //an array that will be sent to Lua as table
    myFunc(L, vec); //call "myFunc" function in Lua and pass the array as an argument

    /* <Lua function which will be called>
     function myFunc(t)
        for i=1, #t do
            t[i] = t[i] * 2
        end
     end
     */

    //how to update elements of "vec" here so it now becomes 2,4,6,8,10?

正如我在代码中评论的那样,我想在调用 Lua 函数后更新vector&lt;float&gt; vec 的元素。

是否可以将数组作为参考传递给 Lua 函数? (就像它在 C++ 函数中的工作原理一样)

如果没有,是否可以获取 Lua table(t) 的值,以便在调用函数后将它们写回 C++ 中的浮点向量?

谢谢!

【问题讨论】:

您有两个选择: (1) 有另一个 C++ 函数,您可以将更新后的表从 Lua 传递给该函数,然后更新向量。 (2) 将向量作为用户数据传递给 Lua,该元表已重载 __len__index 元方法,这些元方法调用 C++ 函数,就地更新用户数据内的向量(这最接近于“通过引用传递数组” )。 lua_pcall之后你应该插入另一个循环for (size_t i=0; i&lt;vec.size(); ++i)来更新向量(一个类似于lua_pcall之前的循环的循环) @HenriMenke 谢谢。我想我找到了一种解决方案。我将lua_pcall 更改为lua_pcall(L, 1, 1, 0),所以它返回一个表格。然后在其后添加以下代码。 for (size_t i=0; i&lt;vec.size(); ++i) lua_pushinteger(L, i+1); lua_gettable(L, -2); vec[i] = lua_tonumber(L, -1); lua_pop(L, 1); 这与您的选项(1)相似吗?我想知道是否有比这更快的解决方案,因为它将用于处理音频。 【参考方案1】:

将 std::vector 与 Lua 表相互转换

正如in chat 所讨论的,可能需要将函数参数从std::vector&lt;float&gt; 转换为Lua 表,并将Lua 表的返回值转换为std::vector&lt;float&gt;。这样做的好处是在 Lua 端是完全透明的。

as_table 函数从一对迭代器创建一个新表,from_table 将堆栈顶部的 Lua 表转换为一对迭代器。

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

#include <lua.hpp>

template <typename T, typename U>
void as_table(lua_State* L, T begin, U end) 
    lua_newtable(L);
    for (size_t i = 0; begin != end; ++begin, ++i) 
        lua_pushinteger(L, i + 1);
        lua_pushnumber(L, *begin);
        lua_settable(L, -3);
    


template <typename T, typename U>
void from_table(lua_State* L, T begin, U end) 
    assert(lua_istable(L,-1));
    for (size_t i = 0; begin != end; ++begin, ++i) 
        lua_pushinteger(L, i + 1);
        lua_gettable(L, -2);
        *begin = lua_tonumber(L, -1);
        lua_pop(L, 1);
    


int main(int argc, char *argv[]) 
    if (argc != 2) 
        std::cerr << "Usage: " << argv[0] << " <script.lua>\n";
        return 1;
    

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    if (luaL_dofile(L, argv[1]) != 0) 
        std::cerr << "lua_dofile failed: " << lua_tostring(L, -1) << '\n';
        lua_close(L);
        return 1;
    

    lua_getglobal(L, "perform");

    std::vector<float> iv(2000, 1);
    std::vector<float> ov(2000, 2);

    as_table(L, iv.begin(), iv.end());
    as_table(L, ov.begin(), ov.end());

    if (lua_pcall(L, 2, 1, 0) != 0) 
        std::cerr << "lua_pcall failed: " << lua_tostring(L, -1)
                  << '\n';
        lua_close(L);
        return 1;
    

    std::vector<float> w(2000);
    from_table(L, w.begin(), w.end());

    assert(std::all_of(w.begin(), w.end(),
                       [](float p)  return p == 3.0f; ));

这是与上面的测试用例一起使用的小 Lua 脚本。

function perform(v1,v2)
    local n = math.min(#v1,#v2)
    local v = 
    for i = 1,n do
        v[i] = v1[i] + v2[i]
    end
    return v
end

std::vector 作为用户数据

如果大部分数据操作都是在 Lua 端完成的,那么将向量作为表推送是有利的,因为 Lua 表实际上非常快。然而,如果大部分计算是在 C++ 端完成的,而 Lua 端只会在 C++ 实现的函数之间传递数据,那么这种方法会增加很大的开销,因为我们会花费大量时间在 C++ 之间来回转换Lua 表和std::vector。为此,Lua 提供了 userdata,这是一种包装 C/C++ 数据结构的方法,使它们看起来像本机 Lua 数据类型。缺点是,当提供从 Lua 中检查用户数据的函数时,这些函数通常很慢,因为必须重复检查参数并且必须调用多个嵌套函数。将它与元表结合起来,为数组访问和长度操作提供语法糖,你会发现自己陷入了性能地狱。

也就是说,我已经构建了一个将向量作为用户数据推送并设置其元表的示例。这个过程也在“Lua 编程”一书中的28.1 – Userdata 章节中进行了描述(阅读它!)。

#include <iostream>
#include <vector>

#include <lua.hpp>

std::vector<float>& checkvector(lua_State *L, int index) 
    std::vector<float> *v = *static_cast<std::vector<float> **>(
        luaL_checkudata(L, index, "std::vector<float>"));
    luaL_argcheck(L, v != nullptr, index, "invalid pointer");
    return *v;


static int newvector(lua_State *L) 
    size_t size = luaL_checkinteger(L, 1);
    luaL_argcheck(L, size >= 0, 1, "invalid size");
    *static_cast<std::vector<float> **>(lua_newuserdata(
        L, sizeof(std::vector<float> *))) = new std::vector<float>(size);

    luaL_getmetatable(L, "std::vector<float>");
    lua_setmetatable(L, -2);
    return 1;


void pushvector(lua_State *L, std::vector<float> const &v) 
    std::vector<float> *udata = new std::vector<float>();
    *udata = v;
    *static_cast<std::vector<float> **>(lua_newuserdata(
        L, sizeof(std::vector<float> *))) = udata;

    luaL_getmetatable(L, "std::vector<float>");
    lua_setmetatable(L, -2);


static int deletevector(lua_State *L) 
    delete &checkvector(L, 1);
    return 0;


static int setvector(lua_State *L) 
    std::vector<float> &v = checkvector(L, 1);
    size_t index = luaL_checkinteger(L, 2) - 1;
    luaL_argcheck(L, index < v.size(), 2, "index out of range");
    luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
    float record = lua_tonumber(L, 3);

    v.at(index) = record;

    return 0;


static int getvector(lua_State *L) 
    std::vector<float> &v = checkvector(L, 1);
    size_t index = luaL_checkinteger(L, 2) - 1;
    luaL_argcheck(L, index < v.size(), 2, "index out of range");

    lua_pushnumber(L, v.at(index));

    return 1;


static int getsize(lua_State *L) 
    std::vector<float> &v = checkvector(L, 1);

    lua_pushinteger(L, v.size());

    return 1;


static int vectortostring(lua_State *L) 
    std::vector<float> &v = checkvector(L, 1);

    lua_pushfstring(L, "std::vector<float>(%d)", v.size());

    return 1;


static const struct luaL_Reg vector_float_lib[] = 
    "new", newvector,
    nullptr, nullptr // sentinel
;

static const struct luaL_Reg vector_float_meta[] = 
    "__tostring", vectortostring,
    "__newindex", setvector,
    "__index", getvector,
    "__len", getsize,
    "__gc", deletevector,
    nullptr, nullptr // sentinel
;

int luaopen_vector_float(lua_State *L) 
    luaL_newmetatable(L, "std::vector<float>");
    luaL_setfuncs(L, vector_float_meta, 0);
    luaL_newlib(L, vector_float_lib);
    return 1;



static int send_vector(lua_State *L) 
    std::vector<float> v =  1, 2, 3, 4 ;
    pushvector(L,v);
    return 1;


static int retrieve_vector(lua_State *L) 
    std::vector<float> &v = checkvector(L, 1);
    for (auto const &p : v) 
        std::cout << p << '\n';
    
    return 0;


int main(int argc, char *argv[]) 
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    luaL_requiref(L, "vector", luaopen_vector_float, 1);
    lua_pop(L, 1);

    lua_pushcfunction(L,send_vector);
    lua_setglobal(L,"send_vector");

    lua_pushcfunction(L,retrieve_vector);
    lua_setglobal(L,"retrieve_vector");

    if (argc != 2) 
        std::cerr << "Usage: " << argv[0] << " <script.lua>\n";
        return 1;
    

    luaL_dofile(L, argv[1]);

    lua_close(L);

这样可以执行下面的Lua脚本

local v = send_vector()
for i = 1,#v do
    v[i] = 2*v[i]
end
retrieve_vector(v)

给定一个全局函数transform_vector,例如

function transform_vector(v)
    for i = 1,#v do
        v[i] = 2*v[i]
    end
    return v
end

可以使用向量参数调用此函数并检索向量结果,就像使用任何其他 Lua 函数一样。

std::vector<float> v =  1, 2, 3, 4 ;
lua_getglobal(L,"transform_vector");
pushvector(L,v);
if (lua_pcall(L,1,1,0) != 0) 
    // handle error

std::vector<float> w = checkvector(L, -1);

【讨论】:

谢谢。我试过了,效果很好。但是,我想做的是将 vector&lt;float&gt;(eg 1,2,3,4,5) 作为参数从 C++ 传递给 Lua 函数,然后检索修改后的数据(例如 2,4,6, 8,10) 所以我可以在 C++ 中再次使用它。我不明白您的解决方案如何做到这一点。 @ZackLee 查看更新后的答案。我还尝试将对向量的引用推送到 Lua 堆栈上,但这与 GC 配合得不好。此外,C++ 方面的参考可能会悬空,这会炸毁你的程序。所以最好按值压入堆栈。我将向量作为函数的返回值推送,当然你也可以将它作为全局推送。 感谢您的更新和解释。是否不能像我在帖子中所写的那样将向量作为参数发送? (请找到function myFunc(t))。我正在尝试使 Lua 代码尽可能简单和高效。因此,我想将向量作为表参数发送,并尽可能在 C++ 中处理retrieve_vector()。我还想知道您的解决方案是否会比我的更有效。 (请找到我的评论) @ZackLee userdata 方法比将每个元素都推入表更有效,因为它不必复制这么多数据。您可以像调用任何其他 Lua 函数一样调用该函数。我将添加一个示例。 @ZackLee 您可以发起bounty 来奖励现有答案并获得额外积分,但我完全可以接受您的感谢 :)

以上是关于如何获取从 C++ 发送到 Lua 函数的表的更新值?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Lua 中获取表的最新 x 条目?

如何从Lua中的表中获取值?

如何在 QT C++ 中从表的列中获取 SQL 中的所有值

如何在VS2010中运行Lua文件?

lua,迭代和调用n = 3层表的所有同名函数

类与对象