2

I'm using luaL_newlib(luaState, afbFunction) where afbFunction is a static luaL_Reg array. Unfortunately, luaL_Reg only support two fields: name and func. As a result, there is no way to pass a handle to a function and implement something like:

static const luaL_Reg afbFunction[] = {
    {"notice" , LuaPrintMsg, MSG_NOTICE},
    {"info"   , LuaPrintMsg, MSG_INFO},
    {"warning", LuaPrintWar, MSG_WARN},
    {NULL, NULL}  /* sentinel */
};

I have two motivations for not having as many C entry points as there are Lua functions:

  1. I have a generic wrapper for all C functions to process input/output parameters value from Lua (table) to C (jsonc).
  2. The number of Lua functions to interface come from a config file and may change without having to rebuild the code. To make a long story short, the config file has a sharelib path followed by the list of function to expose.

Does someone have a solution to have a unique C entry point for multiple Lua functions? When using luaL_newlib(), the C function receives as 2nd argument the lib table, but it seems to receive the full table and not the corresponding entry, also even testing the label is not possible.

Marc Balmer
  • 1,780
  • 1
  • 11
  • 18
Fulup
  • 545
  • 3
  • 14

2 Answers2

2

Use upvalues!

If you don't use luaL_newlib (which is a macro defined as…

#define luaL_newlib(L,l)  \
  (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

…) but explicitly call luaL_setfuncs, you can make each function have some number of upvalues. It's fine to initialize all of them with nil, you can later patch them up. While you can't extend luaL_Reg with extra fields, you can define an extra struct and have a separate array for the other parameters.

Minimal sample implementation (may need more error checking):

(should be copy&paste-able as-is, text blocks should become comments)


#include <lua.h>
#include <lauxlib.h>
/*

First, define a struct that has the function's name first and anything you need after that. This example just uses a string, but you could use int, void*, ... and even have more than one value.

*/
typedef struct xlua_uvinfo {
    const char *name;  /* this is how we identify the functions */
    const char *uv;  /* this can change arbitrarily */
} xlua_uvinfo;
/*

Then we'll have a function that patches the libtable on the stack to put in the real upvalue data. You'll need to adjust the types (lua_pushX), order, and/or names of the fields that you push.

Note that as we're identifying functions by name, not all functions need to get upvalues. (So you can include normal functions in the luaL_Reg that will simply end up with dummy upvalue slots that will not be filled in here.)

*/
static void setupvalues( lua_State *L, const xlua_uvinfo *l ) {
    for (; l->name != NULL; l++) {
        /* get the function by name */
        if (lua_getfield( L, -1, l->name ) == LUA_TFUNCTION) {
            /* this needs to change according to what you have in the struct */
            lua_pushstring( L, l->uv );
            lua_setupvalue( L, -2, 1 );
        }
        /* pop the function */
        lua_pop( L, 1 );
    }
}
/*

Here's a dummy implementation of your function. I use string prefixes – that's easy to debug / inspect.

*/
#define MSG_NOTICE "NOTE"
#define MSG_INFO "INFO"
#define MSG_WARN "WARN"

static int LuaPrintMsg( lua_State *L ) {
   const char *prefix = lua_tostring( L, lua_upvalueindex(1) );
   printf( "%s: %s\n", prefix, lua_tostring( L, 1 ) );
   return 0;
}

/* plain luaL_Reg array */
static const luaL_Reg afbFunction[] = {
    {"notice" , LuaPrintMsg},
    {"info"   , LuaPrintMsg},
    {"warning", LuaPrintMsg},
    {NULL, NULL}
};
/*

And now the part that changes: Add an extra array for the upvalues and then adjust the luaopen_X function.

*/
/* extra upvalue array (must also be terminated by { NULL, NULL } !) */
static const xlua_uvinfo afbUpvalues[] = {
    {"notice" , MSG_NOTICE},
    {"info"   , MSG_INFO},
    {"warning", MSG_WARN},
    {NULL, NULL}
};

int luaopen_foo( lua_State *L ) {
    /* as mentioned above, we can't use `luaL_newlib`, expand manually */
    luaL_checkversion( L );
    /* create the empty table, pre-allocated to the right size */
    luaL_newlibtable( L, afbFunction );
    /* now push a dummy upvalue (more if you add extra upvalues!) */
    lua_pushnil( L );
    /* register the functions, giving 1 upvalue to each */
    luaL_setfuncs( L, afbFunction, 1 );
    /* now set the actual upvalues */
    setupvalues( L, afbUpvalues );
    return 1;
}

If you save that as foo.c, compile as (on Linux) gcc -shared -fPIC -o foo.so foo.c and then run lua -l foo, you'll get the desired behavior:

foo.info "foo"
--> INFO: foo
foo.notice "bar"
--> NOTE: bar
foo.warning "baz"
--> WARN: baz
nobody
  • 4,074
  • 1
  • 23
  • 33
0

While previous response provided by "nobody" works, I finally used a different solution to solve my problem. Here after my solution.

My goal is to provide to developers something as simple as possible to extend Lua with their own set of commands. My program takes a JSON configuration file as input. The config contains a plugin section:

"plugin": {
                "label" : "MyPlug",
                "sharelib": "ctl-audio-plugin-sample.ctlso",
                "lua2c": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"]
            }

Where:

  1. sharelib is my user plugin name containing Lua commands
  2. lu2c the list of commands expose by the plugin as Lua command.
  3. label a namespace for Lua commands. In my sample case "MyPlug:Lua2cHelloWorld1+MyPlug:Lua2cHelloWorld2 commands.

My plugin uses a C macro to make Lua interface transparent to developer.My project uses Json-C also my wrapper import/export Lua table to json_object and vice versa automatically. Here after and a simple example.

 CTLP_LUA2C (Lua2cHelloWorld2, label, argsJ, context) {
    MyPluginCtxT *pluginCtx= (MyPluginCtxT*)context;

    if (!context || pluginCtx->magic != MY_PLUGIN_MAGIC) {
        AFB_ERROR("CONTROLER-PLUGIN-SAMPLE:Lua2cHelloWorld2 (Hoops) Invalid Sample Plugin Context");
        return -1;
    };
    pluginCtx->count++;
    AFB_NOTICE ("CONTROLER-PLUGIN-SAMPLE:Lua2cHelloWorld2 SamplePolicyCount action=%s args=%s count=%d"
               ,label, jsonToString(argsJ), pluginCtx->count);
    return 0;
}

Where:

  • CTLP_LUA2C is my macro
  • Lua2cHelloWorld2 my C routine and Lua command
  • label a string that contains "Lua2cHelloWorld2"
  • argsJ the json-c object containing the import of Lua table provided as input paramters.
  • context the handle created at Plugin init time with CTLP_ONLOAD routine.

Technically CTLP_LUA2C create a static C function in my example (Lua2cHelloWorld2). Then it creates a public wrapping function name luac_Lua2cHelloWorld2, the wrapper is strait forward.

return((*Lua2cWrap)(luaState, MACRO_STR_VALUE(FuncName), FuncName))

Where:

  • FuncName would be replaced by Lua2cHelloWorld2 in my example.
  • Lua2cWrap is provided during plugin initialisation time from CTLP_REGISTER. Most people may have here directly luawrapper public function name . Nevertheless in my project (Automotive Grade Linux Audio Controller) for security motivations no public entry point are expose by the daemon and a specific import mechanism is required.

My code is available here.

  1. Macro are define in ctl-binding.h
  2. Sample of plugin in ctl-plugin-sample.c
  3. The parsing of JSON config file in ctl-dispatch.c (search for pluginJ
  4. The Lua wrapper in ctl-lua.c (search for Lua2cWrapper)

General remarks on Lua/C integration model: Integrating C into Lua is far to complex and Lua interface library clearly miss few utilities.

An extended version of NewLib to create Lua commands should support something like.

{"xxxx"     , Callback, handle, "free information on command"},
{"yyyy"     , Callbacl, handle, "info....},

At minimum functions create with NewLib should receive as 1st parameter only the item concerning the targeted function and not the full table as in 5.3. Receiving the fuul table is perfectly useless and prevent using a generic callback.

A generic model to move from Lua table to C table should be implemented. My project uses Json-C and obviously many C data representation could be propose. Nevertheless something is clearly missing and current Lua/API for importing/exporting data from Lua to C is simply a nightmare. My code provided simple import/export functions for Lua<->json_object.

// Pop all arguments from Lua and move them in a JsonC object    
json_object *argsJ= LuaPopArgs(luaState, LUA_FIST_ARG);

// Pop a single arguments from Lua and make it a JsonC object
json_object *contextJ = LuaPopOneArg(luaState, LUA_FIST_ARG + 2);

// Push C arguments from C to Lua
count+= LuaPushArgument(responseJ);
count+= LuaPushArgument(contextCB->context);
int err=lua_pcall(luaState, count, LUA_MULTRET, 0);

Note that I will probably never have succeeded in handle nest import table from Lua->C without the help of Nick see here.

The last integration point I crucially miss is a system to Push/Pop opaque handle from C routine to Lua. I implemented three routines to handle that issue

  1. LuaCtxPush Create context handle
  2. LuaCtxCheck retrieve context handle and verify its valid
  3. LuaCtxFree release the handle

This allows to implement very simply function that require a C context to be carried as an opaque handle by Lua. As in following example

STATIC int LuaAfbSuccess(lua_State* luaState) {
    // retrieve C context carry as opaque handle by Lua
    LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG);
    if (!afbContext) goto OnErrorExit;

    // import remaining arguments into JsonC
    json_object *responseJ= LuaPopArgs(luaState, LUA_FIST_ARG+1);
    if (responseJ == JSON_ERROR) return 1;

    // send success response to client    
    afb_req_success(afbContext->request, responseJ, NULL);

    // free C context
    LuaCtxFree(afbContext);
    return 0;

 OnErrorExit:  
        lua_error(luaState);
        return 1;
}
Fulup
  • 545
  • 3
  • 14