2

I'm writing C code to check and modify struct members in lua, I'm using metatables

It works for simple definitions, but in array I couldn't make it work, I have the following code

struct pinfo {
    int time;
    bool hide_name;
    int type_info[10];
};

struct mystruct {
    int id;
    int value;
    int option[20];
    struct pinfo pf[5];
    ....
};


int Get_mystruct(lua_State *L) {
    struct mystruct *ms = (struct mystruct*)lua_touserdata(L, 1);

    lua_pushlightuserdata(L, ms);
    luaL_getmetatable(L, "mystruct");
    lua_setmetatable(L, -2);

    return 1;
}

int mystruct_index(lua_State *L) {
    struct mystruct *ms = (struct mystruct*)lua_touserdata(L, 1);
    const char *field = lua_tostring(L, 2);


    if (strcmp(field, "id") == 0) {
        lua_pushinteger(L, ms->id);
    }
    else if (strcmp(field, "value") == 0) {
        lua_pushinteger(L, ms->value);
    }
    else if (strcmp(field, "option") == 0) {
        lua_pushinteger(L, ms->option[lua_tointeger(L, 3)]);
    }
    else {
        lua_pushnil(L);
    }

    return 1;
}
    
int mystruct_newindex(lua_State  *L) {
    struct mystruct *ms = (struct mystruct*)lua_touserdata(L, 1);
    const char *field = lua_tostring(L, 2);
    int value = (int)lua_tointeger(L, 3);


    if (strcmp(field, "id") == 0) {
        ms->id = value;
    }
    else if (strcmp(field, "value") == 0) {
        ms->value = value;
    }
    else if (strcmp(field, "option") == 0) {
        ms->option[lua_tointeger(L, 3)] = (int)lua_tointeger(L, 4);
    } else {
        lua_pushnil(L);
    }

    return 0;
}

void mystruct_create(lua_State *L) {
    luaL_Reg mystructData[] = {
        { "GetMyStruct", Get_mystruct},
        { "__index", mystruct_index},
        { "__newindex", mystruct_newindex},
        {NULL, NULL}
    };

    luaL_newmetatable(L, "mystruct");
    luaL_setfuncs(L, mystructData, 0);
    lua_setfield(L, -2, "MyStructLua");
}

In lua I use it as follows

local ms = MyStructLua.GetMyStruct(my_ptr)

print(ms.id) // Returns 0, not defined
ms.id = 1000 
print(ms.id) // Returns 1000, has been defined

Now when I go to define the array a problem occurs when I try for example

for o = 0, 19 do
    mp.option[o] = 1
end

When I go to set the value I get the error bad argument #3 to 'index' (number expected, got no value)

I would like to know how I can work with simple and complex arrays in this metatable, I have struct which is a member array of mystruct how can I also add it to _newindex and _index

4
  • mp.option[o] = ... is two, separate index operations ([new]index o in the result of indexing 'option' in mp). The second section of this answer describes the behaviour of nested indexing (although the context of that question is very different). You need the result of indexing 'option' in the first table to return a different table (a proxy) that has its own __newindex metamethod, and then perform the assignment (get the userdata via a separate reference / upvalue). Commented Dec 25, 2023 at 2:34
  • There are a few ways to set up the behaviour described above, so for a solid answer it would be very helpful if you could edit your question to include a proper minimal reproducible example - one that fully compiles without error (expected Lua runtime error OK). This means all appropriate #include directives, a main function, and enough driving code to create a working environment that can be modified with an example (and preferably hard-code your Lua code - i.e., use luaL_dostring). Commented Dec 25, 2023 at 2:43
  • Aside: c_array[lua_tointeger(L, n)] is an invitation for undefined behaviour (when the given index is outside the bounds of your array). Commented Dec 25, 2023 at 2:45
  • @Oka So I have to create another index for array ? In this case, how could I do this in C? Commented Dec 25, 2023 at 4:35

1 Answer 1

1

Oka has pointed out that a.b[o] = d contains two index operations, so you have to create metatables for each array. BTW, as I commented under the previous deleted question, I suggest that you use third-party libraries to complete the binding, as it is quite complex to implement. I will put aside the pf field for now, the basic principle is the same. Let's begin with a mistake:

lua_pushlightuserdata(L, ms);
luaL_getmetatable(L, "mystruct");
lua_setmetatable(L, -2);

This looks quite reasonable, but I believe the result is not what you want, that's because from the manual you can read:

Tables and full userdata have individual metatables, although multiple tables and userdata can share their metatables. Values of all other types share one single metatable per type; that is, there is one single metatable for all numbers, one for all strings, etc. By default, a value has no metatable, but the string library sets a metatable for the string type.

If you set the metatable mystruct for your struct, you cannot set another metatable for other arrays. The solution is to create full userdata to store the struct and the arrays. You just need to save pointers, so I write two functions to simplify these operations.

void newpointerud(lua_State* L, const char* mt, int nuv, const void* data) {
    auto p = (const void**)lua_newuserdatauv(L, sizeof(void*), nuv);
    luaL_setmetatable(L, mt);
    *p = data;
}

void* getpointerud(lua_State* L, int idx) {
    return *(void**)lua_touserdata(L, 1);
}

Next, you need to write the corresponding meta methods for int[] and create a metatable.

// ..options[idx]
int mt_c_int_array_index(lua_State* L) {
    int* options = (int*)getpointerud(L, 1);
    int idx = lua_tointeger(L, 2);
    if (idx >= 0 && idx < 20)
        lua_pushinteger(L, *(options + idx));
    else
        lua_pushnil(L);
    return 1;
}

// ..options[idx] = value
int mt_c_int_array_newindex(lua_State* L) {
    int* options = (int*)getpointerud(L, 1);
    int idx = lua_tointeger(L, 2);
    if (idx >= 0 && idx < 20)
        *(options + idx) = lua_tointeger(L, 3);
    else
        luaL_error(L, "Out of index");
    return 0;
}

.....
luaL_Reg mt_c_int_array[] = {
    { "__index", mt_c_int_array_index },
    { "__newindex", mt_c_int_array_newindex },
    {NULL, NULL}
};
luaL_newmetatable(L, "mt_c_int_array");
luaL_setfuncs(L, mt_c_int_array, 0);

Third is to use full userdata to package your struct and arrays. Here, you usually don't want to create a new userdata every time you access the option array. You can use the user value of the userdata to pre save the userdata of the option array.

newpointerud(L, "mystruct", 1, ms); // 1 user value
newpointerud(L, "mt_c_int_array", 0, ms->option);
lua_setiuservalue(L, -2, 1);

Finally in your mystruct_index function, return this user value.

....
else if (strcmp(field, "option") == 0) {
    lua_getiuservalue(L, 1, 1);
}
....
Sign up to request clarification or add additional context in comments.

2 Comments

I now understand how it works, I actually have a lot of arrays in my C Code, this is going to be a bit of work. Regarding third-party libraries, Swig is unfeasible to use at the moment, I really liked Sol2's style but it only works in C++ as far as I've seen, is there anything else that works well in C code that you could recommend?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.