如何在C中创建一个Lua模块内的Lua模块?

3
我有一个名为Actor的对象,我想给它一个Script
我不希望有多个共享对象,而是想要一个单一的顶层模块,包含自己的依赖项。
换句话说,我希望能够这样做:
Actor  = require 'Actor'
Script = require 'Actor.Script'

script = Script("To be, or not to be ...");
actor  = Actor();

这两个函数都只是返回一个创建ActorActor.Script类型的userdata的函数。我的问题是,虽然我可以加载此代码,但它并未按预期工作。似乎Script只是以某种方式返回Actor userdata

print(script) => Actor 0x7fb7a240e998
print(actor)  => Actor 0x7fb7a240a478

我原本期望的是:

print(script) => Actor.Script 0x7fb7a240e998
print(actor)  => Actor 0x7fb7a240a478

如果我将代码拆分成两个不同的模块,我会得到预期的结果,但是我真的很想把它放在一个单独的模块中。
我正在使用OSX和Clang进行编译:
clang -Wall -I./ -I/usr/local/include/ -bundle -undefined dynamic_lookup actor.c script.c -o Actor.so -L./ -L/usr/local/lib

这是我使用的代码:

actor.c:

#include "common.h"

#define ACTOR_LIB "Actor"

typedef struct Actor {
    struct Script *script;
} Actor;

/*
 * Allocate, initialize, and push a new Actor onto the stack.
 * Returns a pointer to that Actor.
 */
Actor*
lua_newactor (lua_State *L)
{
    Actor *actor = lua_malloc(L, sizeof(Actor));
    actor->script = NULL;
    return actor;
}

/*
 * Make sure the argument at index N is a actor and return it if it is.
 */
Actor*
lua_checkactor (lua_State *L, int index)
{
    return (Actor *) luaL_checkudata(L, index, ACTOR_LIB);
}

static int
actor_new (lua_State* L)
{
    Actor *actor = lua_newactor(L);
    lua_pushobject(L, actor, ACTOR_LIB);
    return 1;
}

static int
actor_print (lua_State* L)
{
    Actor *actor = lua_checkactor(L, 1);
    lua_pushfstring(L, "%s %p", ACTOR_LIB, actor);
    return 1;
}

static const luaL_Reg actor_methods[] = {
    {"__tostring", actor_print},
    { NULL, NULL }
};

int 
luaopen_Actor (lua_State * L)
{
    /* create metatable */
    luaL_newmetatable(L, ACTOR_LIB);

    /* metatable.__index = metatable */
    lua_pushvalue(L, -1);
    lua_setfield(L, -1, "__index");

    /* register methods */
    luaL_setfuncs(L, actor_methods, 0);

    /* Actor() => new Actor */
    lua_pushcfunction(L, actor_new);

    return 1;
}

script.c:

#include "common.h"

#define SCRIPT_LIB "Actor.Script"

typedef struct Script {
    const char *string;
} Script;

/*
 * Allocate a new Script to be passed around.
 */
Script *
lua_newscript (lua_State *L, const char *string)
{
    if (string == NULL)
        luaL_error(L, "`string` cannot be empty!");

    Script *script = (Script*) lua_malloc(L, sizeof(Script));
    script->string = string;
    return script;
}

/*
 * Make sure the argument at index N is a Script and return it if it is.
 */
Script *
lua_checkscript (lua_State *L, int index)
{
    return (Script *) luaL_checkudata(L, index, SCRIPT_LIB);
}

static int 
script_new (lua_State *L)
{
    const char *filename = luaL_checkstring(L, 1);
    Script *script = lua_newscript(L, filename);
    lua_pushobject (L, script, SCRIPT_LIB);
    return 1;
}

static int
script_print (lua_State* L)
{
    Script *script = lua_checkscript(L, 1);
    lua_pushfstring(L, "%s %p", SCRIPT_LIB, script);
    return 1;
}

static const luaL_Reg script_methods[] = {
    {"__tostring", script_print},
    { NULL, NULL }
};

int 
luaopen_Actor_Script (lua_State *L)
{
    /* create metatable */
    luaL_newmetatable(L, SCRIPT_LIB);

    /* metatable.__index = metatable */
    lua_pushvalue(L, -1);
    lua_setfield(L, -1, "__index");

    /* register methods */
    luaL_setfuncs(L, script_methods, 0);

    /* Push a function: Script(...) => new script */
    lua_pushcfunction(L, script_new);

    return 1;
}

common.h:

#ifndef COMMON
#define COMMON

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

/*
 * Allocates size_t memory on the given Lua state.
 * lua_newuserdata automatically pushes it, so we pop it.
 */
static
void *
lua_malloc (lua_State *L, size_t size)
{
    void *p = lua_newuserdata(L, size);
    lua_pop(L, 1);
    return p;
}

/*
 * Associate an object pointer with given name and push that onto the stack.
 */
static
void
lua_pushobject (lua_State *L, void *object_pointer, const char *metatable_name)
{
    lua_pushlightuserdata(L, object_pointer);
    luaL_getmetatable(L, metatable_name);
    lua_setmetatable(L, -2);
}

#endif

3
不要立即释放 lua_newuserdata() 创建的值,它是唯一能防止您的用户数据被垃圾回收的东西。此外,所有轻量级用户数据共享一个公共元表,这就是为什么所有悬空指针^W^W“对象”都是相同类型的原因。 - siffiejoe
@siffiejoe,你能指导我如何在C中为Lua创建多个对象以及元表的工作原理吗? - Leroy
1
只需在common.h中摆脱lua_poplua_pushlightuserdata,你的代码就可以工作了。你唯一的问题是在应该使用完整的userdata时使用了lightuserdata。 - siffiejoe
1个回答

1
在这种情况下,模块和子模块函数在C中是正确的。它遵循了luaopen_Parent_Child_..._Descendant作为C中函数名的形式,以及在Lua中的Parent.Child.Descendant
问题在于我混淆了lua_pushlightuserdatalua_newuserdata在推送到堆栈上的内容方面。 lua_pushlightuserdata都共享相同的元表,因此我无法拥有单独的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接