【问题标题】:Modifying a C++ array in main() from Lua without extra allocation从 Lua 修改 main() 中的 C++ 数组,无需额外分配
【发布时间】:2021-02-18 18:08:04
【问题描述】:

我正在绘制一个小型 C++ 程序,它将将数组传递给 Lua 并在那里对其进行修改,我打算在程序中读取一个 lua 脚本,这样我就可以修改它而无需重新编译程序

我的第一个障碍是确保 Lua 能够修改已经分配的数组,而不是让它们在 Lua 空间中再次分配。数据将是浮动的,大小会非常大,但我暂时从小开始。

为了简化这个界面,我尝试了 LuaBridge 2.6,但它没有提供预期的结果。下面是一个完全“工作”的程序。

#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <lua5.3/lua.hpp>
#include <LuaBridge/LuaBridge.h>

int main(void)
    {
    const uint32_t      LENGTH = 512 * 256;
    std::vector <float> input(LENGTH),
                        output(LENGTH);

    memset(output.data(), 0, LENGTH * sizeof(float));   // Zero the output
    for(uint32_t i = 0; i < LENGTH; i++)                // Populate input
        input[i] = (float)i + 0.5f;

    lua_State *luastate = luaL_newstate();
    luabridge::push(luastate, input.data());    // Supposedly passing a pointer to the first element of input, according to LuaBridge manual chap 3-3.1
    luabridge::push(luastate, output.data());   // Same for output

    luaL_dostring(luastate, "output[10] = input[256]");     // Expecting to assign this value in the C++ arrays, not in the Lua space
    lua_getglobal(luastate, "output[10]");                  // Find this assigned value in the Lua stack
    lua_Number val = lua_tonumber(luastate, 1);             // Retrieving this value from Lua to C++

    std::cout << input[256] << ", " << output[10] << ", " << val << std::endl;  // The values of val and in output[10] don't match

    lua_close(luastate);

    return 0;
    }

请注意,没有任何匹配项。 Lua 中的 output[10] 不是 C++ 空间中 input[256] 的值,而是 input[0]。 C++ 输出数组没有从 Lua 内部更新,cout 显示它保持我们初始化的状态 (0)。 为了确认这一点,我们将 output[10] 的这个值推送到堆栈中,它不是 C++ 中的 input[256],而是从 C++ 中检索的。 你们能纠正我或指出我应该在哪里实现这一目标吗?

======= 2020 年 8 月 11 日更新 =======

为了阐明程序正在做什么(或应该做什么),在阅读了 Robert 和 Joseph 的考虑之后,我在下面发布了 C++ 部分和它调用的 lua 脚本的更新版本。请注意,我放弃了 LuaBridge,因为我第一次尝试没有成功:

C++:

#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <luajit-2.0/lua.hpp>  // LuaJIT 2.0.4 from Ubuntu 16.04

int main(void)
    {
    const uint32_t      LENGTH = 256 * 512;
    std::vector <float> input(LENGTH),
                        output(LENGTH);

    memset(output.data(), 0, LENGTH * sizeof(float));
    for(uint32_t i = 0; i < LENGTH; i++)
        input[i] = (float)i + 0.5f;

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

    // Here I have to pass &input[0], &output[0] and LENGTH
    // to Lua, which in turn will pass to whatever functions
    // are being called from a .so lib opened in Lua-side

    luaL_dofile(luastate, "my_script.lua");    
    lua_close(luastate);

    return 0;
    }

Lua 脚本如下所示:

local ffi = require("ffi")
local mylib = ffi.load("/path_to_lib/mylib.so")

-- Here I import and call any fuctions needed from mylib.so
-- without needing to recompile anything, just change this script
-- At this point the script has to know &input[0], &output[0] and LENGTH

ffi.cdef[[int func1(const float *in, float *out, const uint32_t LEN);]]
ffi.cdef[[int func2(const float *in, float *out, const uint32_t LEN);]]
ffi.cdef[[int funcX(const float *in, float *out, const uint32_t LEN);]]

if(mylib.func1(input, output, LENGTH) == 0) then
    print("Func1 ran successfuly.")
else
    print("Func1 failed.")
end

【问题讨论】:

  • 此行错误:lua_getglobal(luastate, "output[10]");
  • 我使用的是 LuaJIT 2.0.4,它是用于 Ubuntu 16.04 的 Synaptic 中可用的。请注意,它具有我用于上述更新的 Lua 标头。在之前的尝试中(更新前)我使用了 Lua 5.3 和 LuaBridge 2.6,但我暂时放弃了这条路线。

标签: c++ lua luajit luabridge


【解决方案1】:

我正在绘制一个将数组传递给 Lua 的小型 C++ 程序

数据会是浮动的,大小会非常大,

我的建议:

  • 将缓冲区保留在 C 端(例如作为全局变量)
  • 将 C 函数公开给 LUA GetTableValue(Index)
  • 向 Lua 公开 C 函数 SetTableValue(Index, Value)

应该是这样的:

static int LUA_GetTableValue (lua_State *LuaState)
{
  float Value;

  /* lua_gettop returns the number of arguments */
  if ((lua_gettop(LuaState) == 1) && (lua_isinteger(LuaState, -1)))
  {
    /* Get event string to execute (first parameter) */
    Offset = lua_tointeger(LuaState, -1);

    /* Get table value */
    Value  = LUA_FloatTable[Offset];

    /* Push result to the stack */
    lua_pushnumber(Value);
  }
  else
  {
    lua_pushnil(LuaState);  
  }

  /* return 1 value */
  return 1;
}

而且你还需要注册函数:

lua_register(LuaState, "GetTableValue", LUA_GetTableValue);

我让你写SetTableValue,但应该很接近。 这样做,缓冲区位于 C 端,可以通过专用函数从 Lua 访问。

【讨论】:

  • 我已对问题添加了更新,答案如下。我还要感谢您最初的建议,因为它表明了在 C 端进行偏移的必要性。因此,我为您的考虑竖起了大拇指。
【解决方案2】:

我建议您创建一个通过__index__newindex 公开数组的用户数据,类似这样(编写为像 Lua 本身一样的 C 和 C++ 多语言):

#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif
#include <lua5.3/lua.h>
#include <lua5.3/lauxlib.h>
#ifdef __cplusplus
}
#endif

struct MyNumbers {
    lua_Number *arr;
    lua_Integer len;
};

int MyNumbers_index(lua_State *L) {
    struct MyNumbers *t = (struct MyNumbers *)luaL_checkudata(L, 1, "MyNumbers");
    lua_Integer k = luaL_checkinteger(L, 2);
    if(k >= 0 && k < t->len) {
        lua_pushnumber(L, t->arr[k]);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

int MyNumbers_newindex(lua_State *L) {
    struct MyNumbers *t = (struct MyNumbers *)luaL_checkudata(L, 1, "MyNumbers");
    lua_Integer k = luaL_checkinteger(L, 2);
    if(k >= 0 && k < t->len) {
        t->arr[k] = luaL_checknumber(L, 3);
        return 0;
    } else {
        return luaL_argerror(L, 2,
                             lua_pushfstring(L, "index %d out of range", k));
    }
}

struct MyNumbers *MyNumbers_new(lua_State *L, lua_Number *arr, lua_Integer len) {
    struct MyNumbers *var = (struct MyNumbers *)lua_newuserdata(L, sizeof *var);
    var->arr = arr;
    var->len = len;
    luaL_setmetatable(L, "MyNumbers");
    return var;
}

int main(void) {
    const lua_Integer LENGTH = 512 * 256;
    lua_Number input[LENGTH], output[LENGTH];

    memset(output, 0, sizeof output);
    for(lua_Integer i = 0; i < LENGTH; ++i)
        input[i] = i + 0.5f;

    lua_State *L = luaL_newstate();

    luaL_newmetatable(L, "MyNumbers");
    lua_pushcfunction(L, MyNumbers_index);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, MyNumbers_newindex);
    lua_setfield(L, -2, "__newindex");
    /* exercise for the reader: implement __len and __pairs too, and maybe shift the indices so they're 1-based to Lua */
    lua_pop(L, 1);

    MyNumbers_new(L, input, LENGTH);
    lua_setglobal(L, "input");
    MyNumbers_new(L, output, LENGTH);
    lua_setglobal(L, "output");

    luaL_dostring(L, "output[10] = input[256]");
    lua_getglobal(L, "output");
    lua_geti(L, -1, 10);
    lua_Number val = lua_tonumber(L, -1);

    printf("%f, %f, %f\n", input[256], output[10], val);

    lua_close(L);
}

使用这种方法,Lua 中没有任何数据的副本,您自己的 MyNumbers_ 函数控制对它们的所有访问。


如果您希望能够通过 LuaJIT 的 FFI 使用数组,而不是直接在 Lua 中操作它们,那么您可以在轻量级用户数据中传递它们的地址,如下所示:

#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif
#include <luajit-2.0/lua.h>
#include <luajit-2.0/lualib.h>
#include <luajit-2.0/lauxlib.h>
#ifdef __cplusplus
}
#endif

int main(void) {
    const lua_Integer LENGTH = 256 * 512;
    lua_Number input[LENGTH], output[LENGTH];

    memset(output, 0, sizeof output);
    for(lua_Integer i = 0; i < LENGTH; ++i)
        input[i] = i + 0.5f;

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

    lua_pushlightuserdata(L, input);
    lua_setglobal(L, "input");
    lua_pushlightuserdata(L, output);
    lua_setglobal(L, "output");
    lua_pushinteger(L, LENGTH);
    lua_setglobal(L, "LENGTH");

    luaL_dofile(L, "my_script.lua");
    lua_close(L);
}

【讨论】:

  • 我正在阅读您的方法和 Robert 的上述方法,并后退一步以确保我理解两者所呈现的概念。在我看来,这只是“将这些数组的第一个元素的地址传递给 Lua 并让它按照脚本所说的执行”的问题,但似乎并非如此。使用 LuaJIT,我已经成功地完成了我的意图:打开一个 .so 并映射函数以处理浮点输入 [] 并写入浮点输出 [],并在 C/C++ 端执行其他操作。但是你所说的关于用户数据的内容似乎是这样的。我正在努力。
  • @JayY 这里是关键点:从一个指针到它的第一个元素访问整个数组需要指针算术,而Lua没有指针算术,所以遵循访问数组的代码必须不是用 Lua 编写,而是用 C(或你的情况下是 C++)编写。
  • 感谢您指出这一点。在我的特定情况下,工作流程是:C++(分配和填充浮点数组)-> Lua(打开一个 C++ 库并将这些数组引用传递给它)-> C++ Lib(将执行这些数组的所有处理)。在这种情况下,Lua 正在打开库并调用函数来处理数组,所以我不必重新编译主机程序和库,只需修改脚本,也许我不必担心推送处理指针算术的函数,因为它是由库处理的?感谢您的耐心解释。
  • @JayY 你指的是什么库?
  • 我已经用更好的信息和代码的当前状态更新了原始帖子。顺便说一句,我无法在评论开头用@Joseph 标记你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
相关资源
最近更新 更多