如何从Delphi注册正确的Lua用户数据?

How to register Lua userdata correct from Delphi?

我仍然对将Delphi用户数据注册到Lua感到困惑。为了教给我原理,我尝试实现了Date(Time)类型。

首先,此类型应具有Lua可访问的三个功能:

  • new函数可创建此类型的变量。
  • getdate函数。
  • setdate函数。
  • 最后,这个小小的Lua脚本应该可以工作了:

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

    我试图自己实现它(使用Lua编程),但是在第2行收到一条错误消息:_attempt to index global 'DT' (a userdata value)_。我可能在userdata注册上做错了一些,但是在查找错误时遇到了麻烦。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    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函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    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的效果(错误消息;))是相同的。在脚本启动之前,它们在我的Delphi程序中被直接调用(我通过在" RegisterDateTime"和" newdatetime"中设置Breakpoints来确保这一点。这两个函数均按此顺序调用。所以我的错误必定是这两个函数之一。几乎可以肯定这是一件简单的事情,但我不愿看到。


    今天,我按下了该项目的"重设按钮",并完全重新启动了LuaDateTime-Type的实现,这一天我正确了。 现在,我想将我的解决方案发布给其他有相同问题的人作为示例。

    昨天我最大的错误是忘记设置我的元表的__index字段。 Lua用户数据的有效Delphi实现如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    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脚本:

    1
    2
    3
    4
    5
    6
     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)

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