Passing 1D Arrays from C/C++ to Lua Using Userdata with Metatables
To pass a 1D array from C/C++ to Lua while maintaining type safety and providing array-like access, you can use Lua userdata with metatables. Here’s a comprehensive approach:
1. Basic Implementation
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>typedef struct {size_t size;double* data; // Using double as example, can be any type
} Array;// Metatable name for our array type
static const char* ARRAY_MT = "ArrayMT";// Create a new array and push it as userdata to Lua stack
static int new_array(lua_State* L) {size_t size = luaL_checkinteger(L, 1);// Allocate userdata and array structureArray* arr = (Array*)lua_newuserdata(L, sizeof(Array));arr->size = size;arr->data = (double*)malloc(size * sizeof(double));// Initialize array (optional)for (size_t i = 0; i < size; i++) {arr->data[i] = 0.0;}// Set metatableluaL_getmetatable(L, ARRAY_MT);lua_setmetatable(L, -2);return 1;
}// Index operation (array.get or array[index])
static int array_index(lua_State* L) {Array* arr = (Array*)luaL_checkudata(L, 1, ARRAY_MT);if (lua_isinteger(L, 2)) {// Array access (1-based index in Lua)lua_Integer idx = lua_tointeger(L, 2);if (idx < 1 || idx > (lua_Integer)arr->size) {return luaL_error(L, "array index out of bounds");}lua_pushnumber(L, arr->data[idx - 1]);return 1;}// Else try to access a methodlua_getmetatable(L, 1);lua_pushvalue(L, 2);lua_rawget(L, -2);return 1;
}// Newindex operation (array[index] = value)
static int array_newindex(lua_State* L) {Array* arr = (Array*)luaL_checkudata(L, 1, ARRAY_MT);lua_Integer idx = luaL_checkinteger(L, 2);double value = luaL_checknumber(L, 3);if (idx < 1 || idx > (lua_Integer)arr->size) {return luaL_error(L, "array index out of bounds");}arr->data[idx - 1] = value;return 0;
}// Get array size
static int array_size(lua_State* L) {Array* arr = (Array*)luaL_checkudata(L, 1, ARRAY_MT);lua_pushinteger(L, arr->size);return 1;
}// GC method to free memory
static int array_gc(lua_State* L) {Array* arr = (Array*)luaL_checkudata(L, 1, ARRAY_MT);free(arr->data);return 0;
}// Register the array type
static void register_array(lua_State* L) {static const luaL_Reg array_metamethods[] = {{"__index", array_index},{"__newindex", array_newindex},{"__len", array_size},{"__gc", array_gc},{NULL, NULL}};static const luaL_Reg array_methods[] = {{"size", array_size},{NULL, NULL}};// Create metatableluaL_newmetatable(L, ARRAY_MT);// Set metamethodsluaL_setfuncs(L, array_metamethods, 0);// Add methods to metatableluaL_newlib(L, array_methods);lua_setfield(L, -2, "__index");// Pop metatable from stacklua_pop(L, 1);
}// Module registration
int luaopen_array(lua_State* L) {register_array(L);static const luaL_Reg lib[] = {{"new", new_array},{NULL, NULL}};luaL_newlib(L, lib);return 1;
}
2. Usage in Lua
local array = require("array")-- Create a new array of size 5
local arr = array.new(5)-- Set values (1-based indexing)
arr[1] = 10.5
arr[2] = 20.3
arr[3] = 30.7-- Get values
print(arr[1]) --> 10.5
print(arr[2]) --> 20.3-- Get size
print(#arr) --> 5
print(arr:size()) --> 5
3. Enhanced Version with Type Safety
For better type safety and support for different data types, you can:
- Create separate metatables for different array types
- Add bounds checking
- Implement more array operations (slice, map, etc.)
4. Passing Existing C Arrays
If you want to wrap an existing C array without copying:
static int wrap_array(lua_State* L) {double* data = (double*)lua_touserdata(L, 1);size_t size = luaL_checkinteger(L, 2);Array* arr = (Array*)lua_newuserdata(L, sizeof(Array));arr->size = size;arr->data = data; // Use existing arrayluaL_getmetatable(L, ARRAY_MT);lua_setmetatable(L, -2);return 1;
}
Key Points
- Userdata holds both the array pointer and size information
- Metatables provide object-oriented access and operator overloading
__gc
ensures proper cleanup of allocated memory- 1-based indexing matches Lua conventions
- The implementation can be extended to support more features
Remember to compile this as a shared library and load it in Lua using require
.