在没有额外分配的情况下从 Lua 修改 main() 中的 C++ 数组

Modifying a C++ array in main() from Lua without extra allocation

我正在草拟一个小的 C++ 程序,它将数组传递给 Lua 并在那里修改它们,我打算在程序中读取一个 lua 脚本,这样我就可以修改它而无需需要重新编译程序

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

为了简化这个界面,我尝试了 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 中输出[10] 的不是C++ space 中输入[256] 的值,而是输入[0]。 C++ 输出数组未从 Lua 内更新,cout 显示它保持为我们初始化 (0)。 为了确认这一点,我们将 output[10] 的这个值压入堆栈,这在 C++ 中不是 input[256],并从 C++ 中检索。 你们能纠正我或指出我应该在哪里实现这一目标吗?

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

为了阐明程序正在做什么(或应该做什么),在阅读了 Robert 和 Joseph 的考虑后,我 post 下面是 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

I am sketching a small C++ program that will pass arrays to Lua

The data will be float and the size will be really large,

我的建议:

  • 将缓冲区保留在C端(例如作为全局变量)
  • 公开一个C-function到LUAGetTableValue(Index)
  • 公开一个C-function到LuaSetTableValue(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 访问。

我建议您创建一个通过 __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);
}