Problems with LUA 5.02 and user objects

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

Problems with LUA 5.02 and user objects

jdarling
I've been playing around with wrapping Delphi classes in Lua.
Everything seems to work fine except that LUA throws an exception when
I call one of my wrapped methods.  I'd try to give this in C, but my
skills are just too rusty so I hope others can see the problem even if
its not in your lang of choice :).

I have created my wrapper using:

inst := TLUAObject.Create(nil);
inst.InstanciateFromLUA(L);

LuaSetTableLightUserData(L, 1, HandleStr, inst);
LuaSetMetaFunction(L, 1, '__index', Lua_instIndex);
LuaSetMetaFunction(L, 1, '__newindex', Lua_instNewIndex);
result := 1


Now everything works fine for Lua_instIndex and for that matter as long
as I'm not working with a method everything works fine for
Lua_instNewIndex, but when I try and work with a method called using
Instance:Method() I get an error from lua: Attempt to call method
'MethodName' (a number value)

Everything works fine, the code in the object executes and the objects
pointer and self reference are correct, so it looks like some how I'm
not telling LUA that I handled it properly.  Any ideas?

I can try an put together a small sample application if that would help.
 Below is my code for Index also, as it may give some insight.

function instIndex(L: Plua_State): integer; cdecl;
var
  exMethod : String;
  m        : TMethod;
  s        : TObject;
  v        : Variant;
begin
  exMethod := lua_tostring(L, 2);
  s := GetInstance(L, 1);
  result := 1;
  if IsPublishedProp(s, exMethod) then
    begin
      v := GetPropValue(s, exMethod, false);
      LuaPushVariant(L, v);
    end
  else
    begin
      m.Data := s;
      m.Code := s.ClassType.MethodAddress(exMethod);
      if Assigned(m.Code) then
        result := TLuaObjectMethod(m)(l)
      else
        result := 0;
    end;
end;

 - Jeremy

"Help I suffer from the oxymoron Corporate Security."


Reply | Threaded
Open this post in threaded view
|

Re: Problems with LUA 5.02 and user objects

Romulo Bahiense
Hi,

I think the problem is here:

>       if Assigned(m.Code) then
>         result := TLuaObjectMethod(m)(l)
>       else
>         result := 0;


You are calling the method in the instIndex function, returning it's
result. Lua expects you to return that function instead.

Suppose you have a class TFoo and a method bar(). When you instantiate a
Lua userdata for the TFoo class, and write something like this:

-- I don't know how your constructors looks like...
local foo = TFoo.Create()
foo:bar()

foo's __index metamethod should return a function to be executed by the
Lua VM and not on the "C side" (Delphi side ;).

Basically you would have to replace those lines with:

if Assigned(m.Code) then
begin
   lua_pushcfunction(TLuaObjectMethod(m));
   Result:= 1;
end
else
   Result := 0;

But a problem arises here, because a method is different from a Lua C
Function. A method is a function that requires an implicit "Self"
reference as the first argument and n other arguments as the
function/procedure parameters, while a Lua C Function is a function that
accepts only a pointer to a lua_State structure (record) and returns an
integer (also, it has to be cdecl'ed). I think the best solution would
be to create a generic LuaCFunction which would have as it's first two
upvalues the parts of a TMethod record:

function dummycclosure(L: PLuaState): integer; cdecl;
var
   m: TMethod;
begin
   m.Data:= lua_touserdata(L, lua_upvalueindex(1));
   m.Code:= lua_touserdata(L, lua_upvalueindex(2));
   Result:= TLuaObjectMethod(m)(L);
end;

And your instIndex would be:

if Assigned(m.Code) then
begin
   lua_pushlightuserdata(L, m.Data);
   lua_pushlightuserdata(L, m.Code);
   lua_pushcclosure(dummycclosure, 2);
   Result:= 1;
end
else
   Result := 0;

This solution should work fine, but it creates a C closure each time it
is called.

There are other solutions for you problem, as creating binding functions
for your class methods and then putting them into a table, perhaps
replacing the __index function with that table, and settings it's
__index metamethod for that __index function (confusing?).

I have tried to create a binding mechanism sometime ago but I had to
abandon it (cost x benefit). Most of the time, it is easier to create
the binding functions manually than to create a run-time system[1]
generic enough to accommodate both Delphi's and Lua's particularities. I
wish you to be more lucky than me :)

--rb

1. I tried to implement a run-time system to take advantage of Delphi's
RTTI. I think that an offline engine would be a bit simpler to implement.

ps: I have not tested any of the code I wrote.
Reply | Threaded
Open this post in threaded view
|

RE: Problems with LUA 5.02 and user objects

jdarling
In reply to this post by jdarling
I was afraid that the answer would be somewhat complex.  All of this
just to wrap up 3 classes, though they are classes from hell with many
methods and properties.  Guess it may be time to find one of those
ScriptingLanguageX-->Delphi Code Wrapper generators and modify it to
fix the LUA Library :).  Since I've hit this once I know I'll need it
again :)

 - Jeremy

"Help I suffer from the oxymoron Corporate Security."


> -------- Original Message --------
> Subject: Re: Problems with LUA 5.02 and user objects
> From: Romulo Bahiense <[hidden email]>
> Date: Mon, April 03, 2006 2:45 pm
> To: Lua list <[hidden email]>
>
> Hi,
>
> I think the problem is here:
>
> >       if Assigned(m.Code) then
> >         result := TLuaObjectMethod(m)(l)
> >       else
> >         result := 0;
>
>
> You are calling the method in the instIndex function, returning it's
> result. Lua expects you to return that function instead.
>
> Suppose you have a class TFoo and a method bar(). When you instantiate a
> Lua userdata for the TFoo class, and write something like this:
>
> -- I don't know how your constructors looks like...
> local foo = TFoo.Create()
> foo:bar()
>
> foo's __index metamethod should return a function to be executed by the
> Lua VM and not on the "C side" (Delphi side ;).
>
> Basically you would have to replace those lines with:
>
> if Assigned(m.Code) then
> begin
>    lua_pushcfunction(TLuaObjectMethod(m));
>    Result:= 1;
> end
> else
>    Result := 0;
>
> But a problem arises here, because a method is different from a Lua C
> Function. A method is a function that requires an implicit "Self"
> reference as the first argument and n other arguments as the
> function/procedure parameters, while a Lua C Function is a function that
> accepts only a pointer to a lua_State structure (record) and returns an
> integer (also, it has to be cdecl'ed). I think the best solution would
> be to create a generic LuaCFunction which would have as it's first two
> upvalues the parts of a TMethod record:
>
> function dummycclosure(L: PLuaState): integer; cdecl;
> var
>    m: TMethod;
> begin
>    m.Data:= lua_touserdata(L, lua_upvalueindex(1));
>    m.Code:= lua_touserdata(L, lua_upvalueindex(2));
>    Result:= TLuaObjectMethod(m)(L);
> end;
>
> And your instIndex would be:
>
> if Assigned(m.Code) then
> begin
>    lua_pushlightuserdata(L, m.Data);
>    lua_pushlightuserdata(L, m.Code);
>    lua_pushcclosure(dummycclosure, 2);
>    Result:= 1;
> end
> else
>    Result := 0;
>
> This solution should work fine, but it creates a C closure each time it
> is called.
>
> There are other solutions for you problem, as creating binding functions
> for your class methods and then putting them into a table, perhaps
> replacing the __index function with that table, and settings it's
> __index metamethod for that __index function (confusing?).
>
> I have tried to create a binding mechanism sometime ago but I had to
> abandon it (cost x benefit). Most of the time, it is easier to create
> the binding functions manually than to create a run-time system[1]
> generic enough to accommodate both Delphi's and Lua's particularities. I
> wish you to be more lucky than me :)
>
> --rb
>
> 1. I tried to implement a run-time system to take advantage of Delphi's
> RTTI. I think that an offline engine would be a bit simpler to implement.
>
> ps: I have not tested any of the code I wrote.

Reply | Threaded
Open this post in threaded view
|

RE: Problems with LUA 5.02 and user objects

jdarling
In reply to this post by jdarling
Sorry for duplicate messages on the same topic, but by chance do you
still have some sample Delphi code lying around of how you wrapped up
objects?  This has been a real pain in my side for about a week now,
and the best I've got working is a full API that is then wrapped up on
the LUA side by creating object wrappers.  Not eligant at all and easy
to break :(

 - Jeremy

"Help I suffer from the oxymoron Corporate Security."


> -------- Original Message --------
> Subject: Re: Problems with LUA 5.02 and user objects
> From: Romulo Bahiense <[hidden email]>
> Date: Mon, April 03, 2006 2:45 pm
> To: Lua list <[hidden email]>
>
> Hi,
>
> I think the problem is here:
>
> >       if Assigned(m.Code) then
> >         result := TLuaObjectMethod(m)(l)
> >       else
> >         result := 0;
>
>
> You are calling the method in the instIndex function, returning it's
> result. Lua expects you to return that function instead.
>
> Suppose you have a class TFoo and a method bar(). When you instantiate a
> Lua userdata for the TFoo class, and write something like this:
>
> -- I don't know how your constructors looks like...
> local foo = TFoo.Create()
> foo:bar()
>
> foo's __index metamethod should return a function to be executed by the
> Lua VM and not on the "C side" (Delphi side ;).
>
> Basically you would have to replace those lines with:
>
> if Assigned(m.Code) then
> begin
>    lua_pushcfunction(TLuaObjectMethod(m));
>    Result:= 1;
> end
> else
>    Result := 0;
>
> But a problem arises here, because a method is different from a Lua C
> Function. A method is a function that requires an implicit "Self"
> reference as the first argument and n other arguments as the
> function/procedure parameters, while a Lua C Function is a function that
> accepts only a pointer to a lua_State structure (record) and returns an
> integer (also, it has to be cdecl'ed). I think the best solution would
> be to create a generic LuaCFunction which would have as it's first two
> upvalues the parts of a TMethod record:
>
> function dummycclosure(L: PLuaState): integer; cdecl;
> var
>    m: TMethod;
> begin
>    m.Data:= lua_touserdata(L, lua_upvalueindex(1));
>    m.Code:= lua_touserdata(L, lua_upvalueindex(2));
>    Result:= TLuaObjectMethod(m)(L);
> end;
>
> And your instIndex would be:
>
> if Assigned(m.Code) then
> begin
>    lua_pushlightuserdata(L, m.Data);
>    lua_pushlightuserdata(L, m.Code);
>    lua_pushcclosure(dummycclosure, 2);
>    Result:= 1;
> end
> else
>    Result := 0;
>
> This solution should work fine, but it creates a C closure each time it
> is called.
>
> There are other solutions for you problem, as creating binding functions
> for your class methods and then putting them into a table, perhaps
> replacing the __index function with that table, and settings it's
> __index metamethod for that __index function (confusing?).
>
> I have tried to create a binding mechanism sometime ago but I had to
> abandon it (cost x benefit). Most of the time, it is easier to create
> the binding functions manually than to create a run-time system[1]
> generic enough to accommodate both Delphi's and Lua's particularities. I
> wish you to be more lucky than me :)
>
> --rb
>
> 1. I tried to implement a run-time system to take advantage of Delphi's
> RTTI. I think that an offline engine would be a bit simpler to implement.
>
> ps: I have not tested any of the code I wrote.

Reply | Threaded
Open this post in threaded view
|

Re: Problems with LUA 5.02 and user objects

Romulo Bahiense
Hi,

[hidden email] wrote:
 > Sorry for duplicate messages on the same topic, but by chance do you
 > still have some sample Delphi code lying around of how you wrapped up
 > objects?

Sorry for the delay. I'm quite busy fighting a disturbing hardware problem.

About your problem, I don't have at hand one isolated example for your,
because it is a bit entangled with my closed library.

But a solution I use is something like:

One table containing the class' methods, ieg:

TFooMethods = {
   bar = <cfunction>,
   baz = <cfunction>,
   ...
}

(This table is populated on the luaopen_mylibrary function)

And the object's (userdata's) metamethod is a function that:

Check if the requested name is a RTTI property. If it is, then return it
as you was doing with your example. If it is not, then it will get that
TFooMethods table from the registry and return it's value on the key
specified by the first meta __index argument.

An example (again, not tested) would be:

function lua__index(L: PLuaState): Integer; cdecl;
var
   Obj: TObject;
   outvar: Variant;
begin
   Result:= 1;
   // Get a Delphi object from Lua world
   Obj:= GetObjFromLuaStack(L, 1);

   // Check if the field name is a property of that object
   if GetVCLProp(Obj, lua_tostring(L, 2), outvar) then
     PushLuaVariant(L, outvar)
   else
   begin
     // If it is not, get the (Obj.ClassName+'Methods')
     // table from registry
     lua_pushstring(L, Obj.ClassName+'Methods');
     lua_gettable(L, LUA_REGISTRYINDEX);
     if lua_istable(L, -1) then
     begin
       // If it is a table, then get it's value
       lua_pushvalue(L, 2);
       lua_gettable(L, -2)
     end
     else
       // Otherwise, push nil (nothing can be found with the
       // specified field name)
       lua_pushnil(L);
   end;
end;

And the functions in the TFooMethods table would be something like:

function lua_TFoo_bar(L: PLuaState): integer; cdecl;
begin
     Result:= 0;
     with GetTFooFromLuaStack(L, 1) do
       Bar(lua_tostring(L, 2));
end;

But this approach requires you to create one Lua C Function for each
method in your class. This may sound bad, but from my experience, it is
better that way -- less mess in your code and more performance in
runtime. Plus, you are able to write more specialized code to interface
with Lua, taking advantage of it's nature of dynamic types, anonymous
functions (first-class values) and multiple return values.

A problem you may find is that you can't use RTTI's published methods
(a.k.a. events). This is a pain the neck. A solution would be to
implement some kind of RTTIJIT or TPC (Tinny Pascal Compiler ^^), but I
don't have the knowledge nor the time, yet :)

Good luck!
--rb