Re: Newby question: How to change __index userdata method in C?

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Sebastian Wolff
Thanks for your attempt to help. I am afraid, however, that you opened
more questions than provided answers to my problem. Reading the Lua
manual can be very frustrating. There are no code snippets given except
pushing some number values around. But how to use the push_cclosure
functions, lua_getmetatable, etc. in general usecases completely stays
in the dark (except one tries to  understands  a l l  of Lua in the  
complete picture)

> Your new metamethods are closures, and as such they have an
> environment, and upvalues, even if implemented in C. Therefore you can
> save a reference to the previous metamethod in one of these places.

The thing is that I do not have any idea how to get the function
pointer to the old __index function from Lua. The metatable I am
referring to has been created using some cryptic wrapper code. The
object is some userdata. In order to get the __index method handle I
would need to create the userdata, push it to the stack, then get its
metatable and push it to the stack. Then I would need to get the field
__index of this metatable and somehow obtain the function pointer. But
since I have no idea how to create the userdata (without digging into
the wrapper code) I can only get it by calling a Lua script. However, I
could pass Lua code that creates local variables. But afterwards
examining the variables is not possible since they are supposed to be
garbage collected.

> You can use a Lua script such as the one you propose, but instead of
> making the new functions accessible in a global variables, you can
> pass them as parameters to the script. To pass them from C, push the
> functions on the stack before calling the script, and pass appropriate
> arguments to lua_call. From within the script, to access the
> parameters passed when the script was called you have to use the three
> dots "...". You would simply prefix the script with a line or two like
> :
>
> local myown_index_method,myown_newindex_method = ...

I wanted to try this workflow by simply pushing a number from C to a
Lua script that calls the print function. That means, I should pass
"print(...)" to luaL_loadstring and before I have to push the number.
Well, it failed naturally.  Do you have some code snippets from which I
learn how to use the interface?


Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Roberto Ierusalimschy
> Reading the Lua manual can be very frustrating. There are no code
> snippets given except pushing some number values around. But how to
> use the push_cclosure functions, lua_getmetatable, etc. in general
> usecases completely stays in the dark (except one tries to understands
> a l l of Lua in the complete picture)

The Lua manual is a "reference" manual. Its intend is not to teach the
language, but to document it. There are several other books intended
to teach Lua.

-- Roberto
Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Sebastian Wolff
In reply to this post by Sebastian Wolff
I am sorry for letting you know my frustration and my last "moody" and
aggressive reply. Maybe it is better to clarify what my problem is:

I want to exchange the existing __index and __newindex metamethods by
some C functions. This could be done like this:

        static lua_CFunction TMMatrix_original_index;
        static lua_CFunction TMMatrix_original_newindex;

        int TMMatrix_redefined_index(lua_State *L)
        {
                printf("CALLED redefined __index!\n");
                return (*TMMatrix_original_index)(L);
        }
        int TMMatrix_redefined_newindex(lua_State *L)
        {
                printf("CALLED redefined __newindex!\n");
                return (*TMMatrix_original_newindex)(L);
        }

I then register these functions. SWIG created a module "tm" which is
represented as a table in Lua:

        const luaL_reg lua_mymatrix_functions[] =
        {
                {"TMMatrix_redefined_index", TMMatrix_redefined_index},
                {"TMMatrix_redefined_newindex", TMMatrix_redefined_newindex},
                {NULL,NULL},
        };
        luaL_register(L, "tm", lua_mymatrix_functions);

Then I call the following Lua script using luaL_dostring:

        local metaarray = getmetatable(tm.TMMatrix(1,1))
        __oldindex = metaarray.__index
        __oldnewindex = metaarray.__newindex
        metaarray.__index = tm.TMMatrix_redefined_index
        metaarray.__newindex = tm.TMMatrix_redefined_newindex

which creates a variable of userdata type tm.TMMatrix and changes its
metatable.
I have stored the old metamethods in global variables. Thanks to your
advice, Jerome, I can now save them in global C variables:

        lua_getglobal(L, "__oldindex");
        TMMatrix_original_index = lua_tocfunction (L, -1);
        lua_pop(L,1);

        lua_getglobal(L, "__oldnewindex");
        TMMatrix_original_newindex = lua_tocfunction (L, -1);
        lua_pop(L,1);

Executing the Lua script
        A = tm.TMMatrix(2,2)\n"
        y = A[1]
        A:func1(2)
        A[0] = 2
Will now call my own C functions and they in turn will call the
original wrapper C functions.

So far so good. It is working, but the code is the opposite of being
elegant. (1) I would like to eliminate the need of the global Lua
variables __index and __oldindex.
(2) I would like to eliminate the script itself.
(3) I would like to eliminate the need of registering my 2 new functions.

For (1) and (2) I think what could help me is to know how I can get the
metatable of a locally created object. Unfortunately I do not know how
to create the userdata value "TMMatrix(3,3)" from C since that code is
behind some blackbox wrapper, i.e. performing the Lua code
        local metaarray = getmetatable(tm.TMMatrix(1,1))
on C level. Once I have the metatable on stack I should be able to
retrieve the metatable field "__index" (at least I hope so).

For (3) it would be nice to be able to perform the Lua code
        metaarray.__index = tm.TMMatrix_redefined_index
in such a way that I do not need to register the function
"tm.TMMatrix_redefined_index". You said this is possible (I would agree
with that), but I didn't figure out, how - Maybe, if I can solve (1)
and (2), then I can simply change the function pointer on C level which
is stored on the Lua stack?

Thanks Sebastian


Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Jerome Vuarand
In reply to this post by Sebastian Wolff
2009/12/3 Sebastian Wolff <[hidden email]>:

>> Your new metamethods are closures, and as such they have an
>> environment, and upvalues, even if implemented in C. Therefore you can
>> save a reference to the previous metamethod in one of these places.
>
> The thing is that I do not have any idea how to get the function pointer to
> the old __index function from Lua. The metatable I am referring to has been
> created using some cryptic wrapper code. The object is some userdata. In
> order to get the __index method handle I would need to create the userdata,
> push it to the stack, then get its metatable and push it to the stack. Then
> I would need to get the field __index of this metatable and somehow obtain
> the function pointer. But since I have no idea how to create the userdata
> (without digging into the wrapper code) I can only get it by calling a Lua
> script. However, I could pass Lua code that creates local variables. But
> afterwards examining the variables is not possible since they are supposed
> to be garbage collected.

A Lua function, even if implemented in C, has an environment and
upvalues. As such it's more than a C function pointer, and for that
reason, you cannot extract a C function pointer from a Lua function
implemented in C.

However, if you want to call that function, and you have it on the Lua
C API stack, you don't have to extract the function pointer and call
it. You can use the lua_call or lua_pcall functions to invoke that
function.

If you want to keep a reference to that function in C structures,
things are a little more complicated. It involves using
luaL_ref/luaL_unref, and storing the lua_State pointer along with the
reference int. Ask if you want to learn about that method.

>> You can use a Lua script such as the one you propose, but instead of
>> making the new functions accessible in a global variables, you can
>> pass them as parameters to the script. To pass them from C, push the
>> functions on the stack before calling the script, and pass appropriate
>> arguments to lua_call. From within the script, to access the
>> parameters passed when the script was called you have to use the three
>> dots "...". You would simply prefix the script with a line or two like
>> :
>>
>> local myown_index_method,myown_newindex_method = ...
>
> I wanted to try this workflow by simply pushing a number from C to a Lua
> script that calls the print function. That means, I should pass "print(...)"
> to luaL_loadstring and before I have to push the number. Well, it failed
> naturally.  Do you have some code snippets from which I learn how to use the
> interface?

Here is a simple C snippet creating a function implemented in Lua, and
invoking it with a single number parameter, and no result value:

if (luaL_loadstring(L, "print(...)"))
{
  fprintf(stderr, "loadstring error: %s\n", lua_tostring(L, -1));
  lua_pop(L, 1);
  return;
}
lua_pushnumber(L, 42);
if (lua_pcall(L, 1, 0, 0))
{
  fprintf(stderr, "pcall error: %s\n", lua_tostring(L, -1));
  lua_pop(L, 1);
  return;
}
Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Jerome Vuarand
In reply to this post by Sebastian Wolff
2009/12/3 Sebastian Wolff <[hidden email]>:

> So far so good. It is working, but the code is the opposite of being
> elegant. (1) I would like to eliminate the need of the global Lua variables
> __index and __oldindex.
> (2) I would like to eliminate the script itself.
> (3) I would like to eliminate the need of registering my 2 new functions.
>
> For (1) and (2) I think what could help me is to know how I can get the
> metatable of a locally created object. Unfortunately I do not know how to
> create the userdata value "TMMatrix(3,3)" from C since that code is behind
> some blackbox wrapper, i.e. performing the Lua code
>        local metaarray = getmetatable(tm.TMMatrix(1,1))
> on C level. Once I have the metatable on stack I should be able to retrieve
> the metatable field "__index" (at least I hope so).

/* get tm.TMMatrix */
lua_getglobal(L, "tm");
lua_getfield(L, -1, "TMMatrix");
lua_replace(L, -2);
/* push arguments */
lua_pushnumber(L, 1);
lua_pushnumber(L, 1);
/* call tm.TMMatrix, with one result on the stack */
lua_call(L, 2, 1);
/* get the matrix metatable */
lua_getmetatable(L, -1);
/* remove the matrix, keep the metatable */
lua_replace(L, -2);

> For (3) it would be nice to be able to perform the Lua code
>        metaarray.__index = tm.TMMatrix_redefined_index
> in such a way that I do not need to register the function

Following the code above (assuming the metatable is on the top of the stack):

lua_getfield(L, -1, "__index");
lua_pushcclosure(L, my_own_index, 1); /* keep the old index as first upvalue */
lua_setfield(L, -2, "__index");
lua_getfield(L, -1, "__newindex");
lua_pushcclosure(L, my_own_newindex, 1); /* keep the old newindex as
first upvalue */
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1); /* remove the metatable from the stack */

> "tm.TMMatrix_redefined_index". You said this is possible (I would agree with
> that), but I didn't figure out, how - Maybe, if I can solve (1) and (2),
> then I can simply change the function pointer on C level which is stored on
> the Lua stack?

In your new function, you can access the old index from upvalues:

static int my_own_index(lua_State* L)
{
  /* get the old index */
  lua_pushvalue(L, lua_upvalueindex(1));
  /* put it below the parameters on the stack */
  lua_insert(L, 1);
  /* call it, with as many parameters as you have, and keeping all results */
  lua_call(L, lua_gettop(L)-1, LUA_MULTRET); /* -1 is because you
inserted the oldindex function itself */
  /* return as many results as the old index gave you */
  return lua_gettop(L);
}

Same for newindex.

Now, if you want to avoid creating an object of type TMMatrix, that's
possible. All you have to do is try to find the metatable directly. I
don't know where SWIG is storing metatables, but maybe people with
some SWIG for Lua experience can help you there.
Reply | Threaded
Open this post in threaded view
|

Re: Newby question: How to change __index userdata method in C?

Sebastian Wolff
Thank you very much, Jerome!

I just tried your code. That works really well and will help me in many
many cases! The concept of upvalues was totally unknown to me. It looks
so simple - now that I see it in front of me. Thanks!

Best regards!
Sebastian