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 structure
Array* 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 metatable
luaL_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 method
lua_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 metatable
luaL_newmetatable(L, ARRAY_MT);
// Set metamethods
luaL_setfuncs(L, array_metamethods, 0);
// Add methods to metatable
luaL_newlib(L, array_methods);
lua_setfield(L, -2, "__index");
// Pop metatable from stack
lua_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 array
luaL_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
.