Skip to content

Instantly share code, notes, and snippets.

@kapodamy
Last active June 13, 2022 17:41
Show Gist options
  • Save kapodamy/433d6a057e0092662cc04e0612caf2cd to your computer and use it in GitHub Desktop.
Save kapodamy/433d6a057e0092662cc04e0612caf2cd to your computer and use it in GitHub Desktop.

UserDataExample in Lua 5.4

Manipulate structs from lua using userdata, these guides https://lua-users.org/wiki/UserDataExample and https://lua-users.org/wiki/UserDataWithPointerExample explains how but they are outdated. Recent versions of Lua does not have the luaL_openlib or luaL_register functions, consequently, you have to do it manually.

C code:

#include <stdlib.h>

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

typedef struct {
    int width; int height; void* somebuffer;
} SomeStruct;

const char FOOBAR[] = "FOOBAR";


static int script_new_object(lua_State* L) {
    SomeStruct* ptr = (SomeStruct*)lua_newuserdata(L, sizeof(SomeStruct));
    ptr->width = 640;
    ptr->height = 480;
    ptr->somebuffer = malloc(12345);

    luaL_getmetatable(L, FOOBAR);
    lua_setmetatable(L, -2);

    return 1;
}

static int script_method(lua_State* L) {
    if (lua_isnil(L, 1)) return luaL_error(L, "null instance");

    SomeStruct* ptr = (SomeStruct*)luaL_checkudata(L, 1, FOOBAR);
    int density = (int)luaL_checkinteger(L, 2);

    int result = ptr->width * ptr->height * density;

    lua_pushinteger(L, (lua_Integer)result);
    return 1;
}

static int script_gc(lua_State* L) {
    printf("__gc was called!\n");
    SomeStruct* userdata = (SomeStruct*)luaL_checkudata(L, 1, FOOBAR);
    free(userdata->somebuffer);
    return 0;
}

static int script_tostring(lua_State* L) {
    luaL_checkudata(L, 1, FOOBAR);
    lua_pushstring(L, "[this is a FOOBAR instance]");
    return 1;
}


static const luaL_Reg OBJECT_METHODS[] = {
    {"test_method", script_method},
    {NULL, NULL}
};

static const luaL_Reg OBJECT_METAFUNCTIONS[] = {
    {"__gc", script_gc},
    {"__tostring", script_tostring},
    {NULL, NULL}
};


static inline void register_object(lua_State* L) {
    // replacement of "luaL_openlib(L, FOOBAR, OBJECT_METHODS, 0);"
    lua_newtable(L);
    luaL_setfuncs(L, OBJECT_METHODS, 0);
    lua_pushvalue(L,-1);// this does the trick
    lua_setglobal(L, FOOBAR);

    luaL_newmetatable(L, FOOBAR);

    // replacement of "luaL_openlib(L, 0, OBJECT_METAFUNCTIONS, 0);"
    luaL_setfuncs(L, OBJECT_METAFUNCTIONS, 0);

    lua_pushliteral(L, "__index");
    lua_pushvalue(L, -3);
    lua_rawset(L, -3);

    lua_pushliteral(L, "__metatable");
    lua_pushvalue(L, -3);
    lua_rawset(L, -3);

    lua_pop(L, 1);
}


int main(int argc, char* argv[]) {
    lua_State* L = luaL_newstate();
    if (!L) {
      printf(stderr, "luaL_newstate() failed\n");
      return 1;
    }
  
    luaL_openlibs(L);

    // register FOOBAR
    register_object(L);

    // expose "script_new_object" as global function
    lua_pushcfunction(L, script_new_object);
    lua_setglobal(L, "create_object");

    int status = luaL_dofile(L, "testscript.lua");// load lua script

    if (status != LUA_OK) {
        const char* error_message = lua_tostring(L, -1);
        fprintf(stderr, "luaL_dofile() failed: %s\n", error_message);

        lua_close(L);
        return 1;
    }

    lua_close(L);
    return 0;
}

Lua test code (luascript.lua)

print("Lua: running...");

-- test 1
local foobar = create_object();
print("Lua: " .. tostring(foobar));

-- test 2
foobar = nil;
collectgarbage();

-- test 3
foobar = create_object();
local result = foobar:test_method(32);
print("Lua: call to test_method has returned " .. tostring(result) .. ". expected=9830400");

print("Lua: ¡all test are done, bye bye!");


And finally the output should looks like:
Lua: running...
Lua: [this is a FOOBAR instance]
__gc was called!
Lua: call to test_method has returned 9830400. expected=9830400
Lua: ¡all test are done, bye bye!
__gc was called!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment