luaL_setmetatable() overrides metatables from other values - c

I have different metatables in my project. But if I create a value A and assign the metatable "X" and creates a second value B and attach metatable "Y", A gets the Y metatable, too! Here is a simplified C function for demonstration:
#include <errno.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// shoudl create t["obj"] = <userdata> with metatable = "Obj" but gets "OtherType"
int create_table_with_object(lua_State *L)
{
lua_newtable(L);
lua_pushlightuserdata(L, (void*)0x1234);
luaL_setmetatable(L, "Obj"); // This Type is already registered with lua_newmetatable()
lua_setfield(L, -2, "obj");
luaL_newmetatable(L, "OtherType");
lua_pushinteger(L, 70);
lua_setfield(L, -2, "ICameFromOtherType");
lua_pop(L, 1); // just a dummy table
// If we create another userdata object, the first one
// gets the same type as this one!
// Obj -> changes to "OtherType"
// ### CRITICAL SECTION STRT ###
lua_pushlightuserdata(L, (void*)0x5555);
luaL_setmetatable(L, "OtherType");
lua_setglobal(L, "JustADummyObj"); // this removes the value from the stack!
// ### CRITICAL SECTION END ###
return 1;
}
int main(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_loadfile(L, "bug.lua");
lua_pushcfunction(L, create_table_with_object);
lua_setglobal(L, "create_table_with_object");
luaL_newmetatable(L, "Obj");
lua_pop(L, 1);
int error;
if(error = lua_pcall(L, 0, 0, 0))
{
fprintf(stderr, "Fatal error: \n");
fprintf(stderr, "%s\n", lua_tostring(L, -1));
return 1;
}
lua_close(L);
return 0;
}
Lua Code:
local a = create_table_with_object()
print(getmetatable(a.obj).__name)
The output is "OtherType" but it should be "Obj". It seems that the second call to lua_setmetatable() overrides the table from other values?!

Ok, solved!
lightuserdata in Lua shares one metatable (instead of one metatable per value). So changing the table for a lightuserdata value changes all other lightuserdata values!

Related

Error occurs attempt to index field in lua_getfield

I have a lua code where it is showing index error the error occurs when reading result
I'm using lua_gettop(L) for the index
My table is:
Avatar =
{
one = {
vals = 51,
result = 300,
}
}
My code is:
void read_test(void) {
lua_State *L;
L = luaL_newstate();
luaL_openlibs(L);
if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0))
{
printf("Error 'test.lua'\n");
return;
}
lua_getglobal(L, "Avatar");
lua_getfield(L, lua_gettop(L), "one");
lua_getfield(L, lua_gettop(L), "vals");
lua_getfield(L, lua_gettop(L), "result");
lua_close(L);
printf("Read complete.\n");
}
When reading the table, an error occurs
lua_getfield(L, lua_gettop(L), "result");
attempt to index field
What is the correct way for me to work this?
lua_getglobal(L, "Avatar");
lua_getfield(L, lua_gettop(L), "one");
lua_getfield(L, lua_gettop(L), "vals");
lua_getfield(L, lua_gettop(L), "result");
Each of these lua_getfield calls pushes a new item onto the stack. First you push Avatar, then you index into that the one field, then the vals field, but the last line is effectively trying to read Avatar.one.vals.result, which does not exist.
You probably need to call lua_remove(L, -1) to pop the top of the stack (vals) before your last line that attempts to index result

How to call lua functions with C thread multiple times

The following code crashes each time at a different part and for a different reason. Tried everything. I want to be able to call "sleep" from lua script and to call lua functions multiple times without my C program having to wait for lua finishes. In my example, I "simulate" 500 mouse clicks at position x = 100 and y = 200 at the speed of 1 click per millisecond.
#include <windows.h>
#include <lua.hpp>
struct MouseClick{
lua_State *L;
int x, y;
};
int sleep(lua_State *L)
{
Sleep(lua_tointeger(L, 1));
return 0;
}
void onClicK(lua_State *L, int x, int y)
{
lua_getglobal(L, "onClick");
lua_newtable(L);
lua_pushinteger(L, x); lua_setfield(L, -2, "x");
lua_pushinteger(L, y); lua_setfield(L, -2, "y");
lua_pcall(L, 1, 0, 0);
}
void callThread(LPVOID arg)
{
MouseClick *m = (MouseClick*) arg;
onClicK(m->L, m->x, m->y);
}
int main()
{
lua_State *L = luaL_newstate();
if (luaL_loadfile(L, "test.lua"))
{
printf("Script not loaded.\n");
return 1;
}
luaL_openlibs(L);
lua_register(L, "sleep", sleep);
luaL_dofile(L, "test.lua");
for(int i = 0; i < 500; i++, Sleep(1))
{
MouseClick m = {lua_newthread(L), 100, 200};
CreateThread(0, 0, (LPTHREAD_START_ROUTINE) callThread, &m, 0, 0);
}
getchar();
}
Lua side:
function onClick(c)
print(c.x, c.y)
sleep(3000)
end

use lua's lightuserdata to register timer callback

I would like to wrap the C timer (not alarm) and use it within lua, in a way that I
could specify a callback function to be triggered after one second have passed.
In order to use multiple timer, a timer ID and callback will be stored to a table,
but a Segmentation fault occured when 'lua_rawset' was called, so I use stack_dump
check the lua stack, a nil was returned by 'lua_rawget' on line 66(lr_register_timer,
marked by FIXME), what is wrong here? Sorry, my English is poor.
Cheers.
lua code:
local lt = luatimer
lt.register_timer(1, function(id)
io.stdout:write("id" .. id .. "timeout\n");
end)
C code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "timer.h"
static lua_State *L;
static void stack_dump(lua_State *L, FILE *fp)
{
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) {
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: fprintf(fp, "'%s'", lua_tostring(L, i)); break;
case LUA_TBOOLEAN: fprintf(fp, lua_toboolean(L, i) ? "true" : "false"); break;
case LUA_TNUMBER: fprintf(fp, "%g", lua_tonumber(L, i)); break;
default: fprintf(fp, "%s", lua_typename(L, t)); break;
}
fprintf(fp, " ");
}
fprintf(fp, "\n");
}
struct timer_env {
int id;
struct event *ev;
};
static void callback_timer_wrap(int id, void *arg)
{
struct timer_env *env = arg;
/* get timer id table */
lua_pushlightuserdata(L, &L);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushinteger(L, env->id);
lua_gettable(L, -2);
/* call lua handler with one result */
lua_pushinteger(L, env->id);
if (lua_pcall(L, 1, 1, 0) == 0) {
lua_pop(L, 1); /* pop result */
}
}
/* id, callback */
static int lr_register_timer(lua_State *L)
{
struct timer_env *env;
int id;
int err;
id = (int)luaL_checkinteger(L, 1);
if (!lua_isfunction(L, 2) || lua_iscfunction(L, 2))
return 0;
/* set lua handler */
lua_pushlightuserdata(L, &L);
lua_rawget(L, LUA_REGISTRYINDEX); /* FIXME */
lua_pushvalue(L, 1); /* key: id */
lua_pushvalue(L, 2); /* value: callback */
stack_dump(L, stderr);
/* FIXME crashed */
lua_rawset(L, -3);
lua_pop(L, 1);
env = malloc(sizeof(*env));
memset(env, 0, sizeof(*env));
env->id = id;
if ((err = register_timer(id, callback_timer_wrap, env)) < 0)
free(env);
lua_pushinteger(L, err);
return 1;
}
static const luaL_Reg luatimer_lib[] = {
{ "register_timer", lr_register_timer },
{ NULL, NULL }
};
static int luaopen_luatimer(lua_State *L)
{
luaL_register(L, "luatimer", luatimer_lib);
/* timer id table */
lua_pushlightuserdata(L, &L); /* key */
lua_newtable(L); /* value */
lua_rawset(L, LUA_REGISTRYINDEX);
return 1;
}
int luaenv_init(void)
{
L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, luaopen_luatimer);
lua_pushstring(L, "luatimer");
lua_call(L, 1, 0);
return 0;
}
void luaenv_exit(void)
{
if (L)
lua_close(L);
}
Thank you very much, I make a stupid mistake that I use the same name L in local vars and global vars. I'm Sorry
Thanks greatwold and immibis

lua c read nested tables

below is the lua table i need to read from C:
listen = {
{ port = 1234, address = "192.168.1.1", userdata = "liunx" },
{ port = 1235, address = "192.168.1.2", userdata = "liunx1" },
{ port = 1236, address = "192.168.1.3", userdata = "liunx2" }
}
below is the c code:
#include <lua.h> /* Always include this when calling Lua */
#include <lauxlib.h> /* Always include this when calling Lua */
#include <lualib.h> /* Prototype for luaL_openlibs(), */
/* always include this when calling Lua */
#include <stdlib.h> /* For function exit() */
#include <stdio.h> /* For input/output */
void bail(lua_State *L, char *msg){
fprintf(stderr, "\nFATAL ERROR:\n %s: %s\n\n",
msg, lua_tostring(L, -1));
exit(1);
}
int main(void)
{
lua_State *L;
L = luaL_newstate(); /* Create Lua state variable */
luaL_openlibs(L); /* Load Lua libraries */
if (luaL_loadfile(L, "cfg.lua"))
bail(L, "luaL_loadfile() failed");
if (lua_pcall(L, 0, 0, 0))
bail(L, "lua_pcall() failed");
// how to read???
lua_getglobal(L, "listen");
lua_close(L);
return 0;
}
I want to travel this table which may contain a few number of data in while loop, but really do not know how to do it, so any tips?
Thanks very much for your tips!Below are the worked code:
#include <lua.h> /* Always include this when calling Lua */
#include <lauxlib.h> /* Always include this when calling Lua */
#include <lualib.h> /* Prototype for luaL_openlibs(), */
/* always include this when calling Lua */
#include <stdlib.h> /* For function exit() */
#include <stdio.h> /* For input/output */
void bail(lua_State *L, char *msg)
{
fprintf(stderr, "\nFATAL ERROR:\n %s: %s\n\n",
msg, lua_tostring(L, -1));
exit(1);
}
int main(void)
{
lua_State *L;
static struct {
const char * name;
int type;
} fields[] = {
{"port", LUA_TNUMBER},
{"address", LUA_TSTRING},
{"userdata", LUA_TSTRING},
{NULL, 0}
};
L = luaL_newstate(); /* Create Lua state variable */
luaL_openlibs(L); /* Load Lua libraries */
if (luaL_loadfile(L, "cfg.lua"))
bail(L, "luaL_loadfile() failed");
if (lua_pcall(L, 0, 0, 0))
bail(L, "lua_pcall() failed");
lua_getglobal(L, "listen");
luaL_checktype(L, -1, LUA_TTABLE);
int i;
for (i = 1; ; i++) {
lua_rawgeti(L, -1, i);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
break;
}
// an element of the 'listen' table should now be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// read the content of that element
int field_index;
for (field_index = 0; (fields[field_index].name != NULL
&& fields[field_index].name != NULL); field_index++) {
lua_getfield(L, -1, fields[field_index].name);
luaL_checktype(L, -1, fields[field_index].type);
// you should probably use a function pointer in the fields table.
// I am using a simple switch/case here
switch(field_index) {
case 0:
printf("port: %d\n", (int)lua_tonumber(L, -1));
// do what you want with port
break;
case 1:
printf("address: %s\n", lua_tostring(L, -1));
break;
case 2:
// handle userdata
printf("userdata: %s\n", lua_tostring(L, -1));
break;
}
// remove the field value from the top of the stack
lua_pop(L, 1);
}
// remove the element of the 'listen' table from the top of the stack.
lua_pop(L, 1);
}
lua_close(L);
return 0;
}
You are not too far. The key here is to understand how the Lua API use the stack for everything.
Here is an untested code sample which should get you going:
// this will be used to validate our table
static struct {
const char * name;
int type;
} fields[] = {
{"port", LUA_TNUMBER},
{"address", LUA_TSTRING},
{"userdata", LUA_TSTRING},
NULL
};
lua_getglobal(L, "listen");
// the 'listen' table should be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// iterate the listen table
int i;
for (i = 1; ; i++) {
lua_rawgeti(L, -1, i);
// when you get nil, you're done
if (lua_isnil(L, -1)) {
lua_pop(L,1);
break;
}
// an element of the 'listen' table should now be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// read the content of that element
int field_index;
for (field_index = 0; fields[field_index] != NULL; field_index++) {
lua_getfield(L, -1, fields[field_index].name);
luaL_checktype(L, -1, fields[field_index].type);
// you should probably use a function pointer in the fields table.
// I am using a simple switch/case here
switch(field_index) {
case 0:
port = lua_tonumber(L, -1);
// do what you want with port
break;
case 1:
address = lua_tostring(L, -1);
break;
case 2:
// handle userdata
break;
}
// remove the field value from the top of the stack
lua_pop(L, 1);
}
// remove the element of the 'listen' table from the top of the stack.
lua_pop(L, 1);
}
I suggest you use those documentations: Lua API table Lua API ref

Iterate through a table in Lua, in a function called from C

I'm trying to call a Lua function from C. The Lua function creates a table and then iterates through it. It works as expected when called from Lua, but not when I call it from a C program. Is there any reason why I can't do what I'm trying here?
test.lua:
function f()
t = {["a"] = "aaa", ["b"] = "bbb", ["c"] = "ccc"}
for z, v in t do
print(t .. " " .. v)
end
end
test.c:
#include <string.h>
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main(void)
{
char read_buffer[1024];
lua_State *L = lua_open();
luaL_openlibs(L);
if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0))
fprintf(stderr, "Error loading test.lua");
lua_getglobal(L, "f");
if(lua_pcall(L, 0, 0, 0) != 0)
fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
strncpy(read_buffer, lua_tostring(L, -1), sizeof(read_buffer));
lua_pop(L, 1);
printf("got from lua: %s\n", read_buffer);
lua_close(L);
return 0;
}
Thanks!
I'm noticing a couple of issues above that's causing problems.
Over here:
function f()
t = {["a"] = "aaa", ["b"] = "bbb", ["c"] = "ccc"}
for z, v in t do
print(t .. " " .. v)
end
end
You can't use a lua table in the for in loop like that unless you make t callable in someway (eg. using __call for instance). More likely you're probably just trying to iterate through it, in which case you would use pairs:
for z, v in pairs(t) do
-- etc.
The other error is that you're trying to concat strings onto the table. Not sure what you were intending here. Perhaps you wanted to print the table address? You can use tostring for that.
The second issue I notice is in your C code:
if(lua_pcall(L, 0, 0, 0) != 0)
So you're not expecting f to return anything unless there's an error. But right after that you try to convert the top item into a string:
strncpy(read_buffer, lua_tostring(L, -1), sizeof(read_buffer));
lua_pop(L, 1);
printf("got from lua: %s\n", read_buffer);
which doesn't make a whole lot of sense -- there may not be anything on the stack at this point. This is clearly a logic error in your code. If this is suppose to be a part of the error handling you should enclose it in braces so it's properly scoped:
if(lua_pcall(L, 0, 0, 0) != 0)
{
fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
strncpy(read_buffer, lua_tostring(L, -1), sizeof(read_buffer));
lua_pop(L, 1);
printf("got from lua: %s\n", read_buffer);
}

Resources