如何从Delphi中注册Lua userdata?

时间:2011-12-01 11:12:04

标签: delphi lua lua-userdata

我仍然对将Delphi userdata注册到Lua感到困惑。教我学习实现日期(时间)类型的原则。

一开始,这种类型应该有三个Lua可以访问的功能:

  1. 用于创建此类变量的new函数。
  2. getdate功能。
  3. setdate功能。
  4. 最后这个小Lua-Script应该可以工作:

    DT = DateTime.new()
    DT:setdate(1, 1, 2011)
    day, month, year = DT:getdate()
    print("Day: " .. day .. " Month: " .. month .." Year: " .. year)
    

    我试图自己实现它(使用Programming in Lua书),但我在第2行收到错误说:_attempt to index global 'DT' (a userdata value)_。我可能在用户数据注册时出错了但我有找不到错误。

    我希望你能帮助我找到它,这是我已经得到的:

    Const
      MetaPosDateTime = 'DateTime';
    
    Type
      tLuaDateTime = tDateTime;
      pLuaDateTime = ^tLuaDateTime;
    
      Function newdatetime(aState : pLua_State) : longint; cdecl;
      Var
        NewData : pLuaDateTime;
      Begin
        Result := 0;
        NewData := lua_newuserdata(aState, SizeOf(tLuaDateTime)); 
        NewData^ := now; 
        luaL_newmetatable(aState, MetaPosDateTime);
        lua_setmetatable(aState, -2);    
        Result := 1;
      End;
    
      Function setdate(aState : pLua_State) : longint; cdecl;
      Var
        DT : pLuaDateTime;
        ParamType : integer;
        day, month, year : lua_Integer;
      Begin
        Result := 0;
        DT := luaL_checkudata(aState, 1, MetaPosDateTime);
        luaL_argcheck(aState, DT <> Nil, 1, 'DataTime expected');
        ParamType := lua_type(aState, 2); 
        If (ParamType = LUA_TTABLE) Then 
          Begin
            { GetData from Table }
          End
        Else
          Begin // param order must be: day, month, year
            day := luaL_checkinteger(aState, 2);
            month := luaL_checkinteger(aState, 3);
            year := luaL_checkinteger(aState, 4);
          End;
        DT^:= EncodeDate(year, month, day);
      End;
    
      Function getdate(aState : pLua_State) : longint; cdecl;
      Var
        DT : pLuaDateTime;
        Day, Month, Year : Word;
      Begin
        DT := luaL_checkudata(aState, 1, MetaPosDateTime);
        luaL_argcheck(aState, DT <> Nil, 1, 'DataTime expected');
        DecodeDate(DT^, Year, Month, Day);
        lua_pushinteger(aState, Day);
        lua_pushinteger(aState, Month);
        lua_pushinteger(aState, Year);
      End;
    
    Procedure RegisterDateTime(aState : pLua_State; aName: string);
    Var
      Funcs : packed Array[0..3] of luaL_reg;
    Begin
      Funcs[0].name := 'new';
      Funcs[0].func := newdatetime;
      Funcs[1].name := 'setdate';
      Funcs[1].func := setdate;
      Funcs[2].name := 'getdate';
      Funcs[2].func := getdate;
      Funcs[3].name := Nil;
      Funcs[3].func := Nil;
      luaL_register(aState, PAnsiChar(aName), Funcs[0]);
    End;
    

    因为我不确定luaL_register函数(它只能通过创建一个必须用require调用的库来工作吗?)我还尝试用这个替换RegisterDateTime函数:< / p>

    Type
      tLuaFuncDef = Record
        FuncName : string;
        Func : Lua_CFunction;
      End;
    
    tLuaFuncList = Array of tLuaFuncDef;
    
    Procedure RegisterLuaObject(aState : pLua_State; aObjectName: string; aFuncList: tLuaFuncList);
    Var
      i : Integer;
    Begin
      If (aObjectName = '') Or (High(aFuncList) < 0) Then
        Exit;
    
      lua_newtable(aState); 
      For i := Low(aFuncList) To High(aFuncList) Do 
        If Assigned(aFuncList[i].Func) And Not (aFuncList[i].FuncName = '') Then
          Begin
            lua_pushcfunction(aState, aFuncList[i].Func);
            lua_setfield(aState, -2, pAnsiChar(aFuncList[i].FuncName));
          End;
      lua_SetGlobal(aState, pAnsiChar(aObjectName));
    End;
    
    Procedure RegisterDateTime(aState : pLua_State, aName: string);
    Var
      FuncList : tLuaFuncList;
    Begin
      SetLength(FuncList, 3);
      FuncList[0].FuncName := 'new';
      FuncList[0].Func := newdatetime;
      FuncList[1].FuncName := 'setdate';
      FuncList[1].Func := setdate;
      FuncList[2].FuncName := 'getdate';
      FuncList[2].Func := getdate;
      RegisterLuaObject(aState, aName, FuncList);
    End;
    

    不幸的是,两个版本的RegisterDateTime的效果(errormessage;)都是一样的。在脚本启动之前,我们在Delphi程序中直接调用它们(我通过在“RegisterDateTime”和“newdatetime”中设置断点来确保这一点。这两个函数都按此顺序调用。所以我的错误必须是这两个函数之一。我我几乎可以肯定这是一件简单的事情,但我不敢看到它。:(

1 个答案:

答案 0 :(得分:3)

今天我推动了这个项目的大重置按钮,并完全重新启动了我的LuaDateTime-Type的实现,这一天我做对了。现在我想以任何遇到同样问题的人为例发布我的解决方案。

昨天我最大的错误就是忘记设置myatable的__index字段。 Lua userdata的Delphi实现如下所示:

implementation
Uses
  LuaLib,
  LauXLib,    
  SysUtils;

Type
  tLuaDateTime = tDateTime;
  pLuaDateTime = ^tLuaDateTime;

Const
  PosMetaTaleLuaDateTime = 'metatables.LuaDateTime';
  PosLuaDateTime = 'datetime';    

Function checkLuaDateTime(L : Plua_State) : pLuaDateTime; // raises error if first (self) parameter is not of type metatables.LuaDateTime
Begin
  Result := luaL_checkudata(L, 1, PosMetaTaleLuaDateTime);
End;

Function newLuaDateTime(L : pLua_State) : LongInt; cdecl;
Var
  a : pLuaDateTime;
Begin
  a := lua_newuserdata(L, SizeOf(tLuaDateTime)); // Get Mem of Usertype
  a^ := now; // Init Value
  lua_getfield(L, LUA_REGISTRYINDEX, PosMetaTaleLuaDateTime); 
  lua_setmetatable(L, -2);

  Result := 1;
End;

Function setLuaDateTime(L : pLua_State) : LongInt; cdecl;
Var
  a : pLuaDateTime;
  day, month, year : Integer;
Begin
  a := checkLuaDateTime(L);
  // get params day, month and year
  day := luaL_checkint(L, 2);
  month := luaL_checkint(L, 3);
  year := luaL_checkint(L, 4);

  // check Param Values
  luaL_argcheck(L, (day >= 1) and (day < 32), 2, 'day out of range');
  luaL_argcheck(L, (month >= 1) and (month < 13), 3, 'month out of range');

  a^ := EncodeDate(year, month, day);

  Result := 0;
End;

Function getLuaDateTime(L : pLua_State) : LongInt; cdecl;
Var
  a : pLuaDateTime;
  day, month, year : Word;
Begin
  a := checkLuaDateTime(L);
  DecodeDate(a^, year, month, day);

  // push 3 results of function
  lua_pushinteger(L, day);
  lua_pushinteger(L, month);
  lua_pushinteger(L, year);

  Result := 3;
End;

Function LuaDateTime2string(L : pLua_State) : LongInt; cdecl;
Var
  a : pLuaDateTime;
Begin
  a := checkLuaDateTime(L);
  lua_pushstring(L, pAnsiChar(FormatDateTime('c', a^)));
  Result := 1;
End;

Const
  LuaDateTimeLib_f : packed Array[0..1] of luaL_reg = // Normal functions (no self)
    (
      (name: 'new'; func: newLuaDateTime),
      (name: Nil; func: Nil)
    );

  LuaDateTimeLib_m : packed Array[0..3] of luaL_reg = // methods of class (need self)
    (
      (name: '__tostring'; func: LuaDateTime2string),
      (name: 'set'; func: setLuaDateTime),
      (name: 'get'; func: getLuaDateTime),
      (name: Nil; func: Nil)
    );

Function luaopen_LuaDateTime(L : pLua_State) : LongInt; cdecl;
Begin
  luaL_newmetatable(L, PosMetaTaleLuaDateTime);
  // Metatable.__index = Metatable
  lua_pushvalue(L, -1);
  lua_setfield(L, -2, '__index');
  luaL_register(L, Nil, LuaDateTimeLib_m[0]);

  luaL_register(L,PosLuaDateTime, LuaDateTimeLib_f[0]);
  Result := 1;
End;

你必须从Delphi调用luaopen_LuaDateTime来注册Lua-State中的类型。完成后,您可以像这样运行Lua脚本:

 dt = datetime.new()
 day, month, year = dt:get()
 print ("Day: " .. day .. " Month: " .. month .. " Year: " .. year)
 dt:set(1, 2, 1903)
 day, month, year = dt:get()
 print ("Day: " .. day .. " Month: " .. month .. " Year: " .. year)

我希望这对其他人有帮助。