Конференция "Игры" » Вызов метода из Lua [Delphi, Windows]
 
  • ggg © (21.12.08 19:29) [0]
    Решил встроить Lua в свой проект и тут же наткнулся на то обстоятельство, что из скрипта не могу вызвать метод объекта.
    Т.е. могу (спс @!!ex и Servy) но несколько извращенным способом через чтение какого-нибудь свойства объекта.


    // регистрация метода в Lua
    procedure TLUAParser.AddObjectMethod(const MethodName: string; AObject: TObject;
     const AObjectMethodName: string);
    var
     script: string;
     p1: string;
    begin
     Inc(FLastBinderIndex);

     lua_pushnumber(FLUAState, FLastBinderIndex);
     lua_setglobal(FLUAState, 'Parser_LastBinderIndex');

     p1 := IntToStr(LongInt(AObject));
     script :=
       MethodName + ' = function() ' +
       '   Parser_CallDelphiObjectsMethod(\"' + p1 + '\", \"' +  AObjectMethodName + '\");' +
       '   return 1; ' +
       ' end ';

     LoadFromMemory(script);
    end;

    function CallDelphiObjectsMethod(L: Plua_State): Integer; cdecl;
    var
     N: Integer;
     O: TObject;
     Method: AnsiString;
    begin
     N := lua_gettop(L);
     if (N <> 2) then
       raise ELuaException.CreateFmt('CallDelphiObjectsMethod failed. 2 arguments' +
           ' expected, but %d found.', [N]);

     if (not lua_isstring(L, 1)) then
       raise ELuaException.CreateFmt('Not a string type argument passed to ' +
           'CallDelphiObjectsMethod. Received type is %s.', [lua_typename(L, lua_type(L, 1))]);

     if (not lua_isstring(L, 2)) then
       raise ELuaException.CreateFmt('Not a string argument passed to ' +
           'CallDelphiObjectsMethod. Received type is %s.', [lua_typename(L, lua_type(L, 2))]);

     O := TObject(StrToInt(string(lua_tostring(L, 1))));
     Method := lua_tostring(L, 2);

     if Assigned(O.MethodAddress(string(Method))) then
     begin
       ShowMessage(format('Method %s found at %s. All is ok :)',
           [Method, O.ClassName]));
     end
     else
       raise ELuaException.CreateFmt('Method %s not found at %s',
           [Method, O.ClassName]);

     Result := 1;
    end;
    ...
    FParser.AddObjectMethod('print', Form1, 'print');



    При исполнении скрипта, в котором вызывается print(); появляется сообщение, что метод найден в нужном классе. Проблема в том, что я не умею вызвать метод объекта О по названию метода Method. Помнится, где-то на этом форуме встречал как это делается, но тема потерялась и всё забылось :(
    Собственно вопрос: Как вызвать метод, имея ссылку на объект и название метода.

    В случае если так сделать не удастся, есть еще вариант с глобальным (точнее "внутримодульным") массивом, в который будут храниться ссылки на методы. Тогда связывающая функция будет передавать не ссылку на объект и название метода, а только индекс из этого массива.

    Второй вариант не нравится из-за этого массива, как-то он не вписывается в концепцию ООП, хотя и работать вроде должно быстрее и проблем меньше.
  • @!!ex © (21.12.08 20:01) [1]
    Я ниче не понял по тексту и по коду. Могу только сказать как у меня сделано:

    lua_pushfunction(State,@ClassMethod);
    lua_setglobal(State,'ClassMethod');

    type
     TMyClass = class
     public
       Procedure ClassMethod(Param:integer);
     end;

    procedure ClassMethod(State:TLUAState); stdcall;
    var
     Obj:TMyClass;
    begin
     Obj:=TMyClass(lua_tonumber(State,-2));
     Obj.ClassMethod(trunc(lua_tonumber(State,-1)));
     lua_pop(State,-2);
    end;




    вызов метода из скрипта:
    ClassMethod(MyClass,10);

    Как скрипт узнает MyClass - это другой вопрос. Лично я передаю его в функции.
  • ggg © (22.12.08 05:15) [2]
    Но ведь тебе на каждый метод надо писать отдельную функцию связки?

    По моему коду. Вот я задумал заменить функцию print на свой метод формы Form1.print. Я получаю ссылку на объект Form1:
    p1 := IntToStr(LongInt(AObject));
    Название функции AObjectMethodName='print' у меня есть.
    В луа выполняю такой скрипт:

    print = function ()
      Parser_CallDelphiObjectsMethod(p1, AObjectMethodName);
      return 1;
    end


    Таким образом стандартный print я заменяю на вызов своей функции CallDelphiObjectsMethod и в качетсве параметров передаю ссылку на объект:
    O := TObject(StrToInt(string(lua_tostring(L, 1)))); // Form1
    и имя метода:
    Method := lua_tostring(L, 2); // "print"
    Теперь проблема в том, чтобы этот метод вызвать.
  • ggg © (22.12.08 05:54) [3]
    Кстати, можно узнать откуда такая работа с lua_pop?
    По документации http://www.lua.org/manual/5.1/manual.html
    Pops n elements from the stack.
    И в примерах нигде с "-" эту ф-цию не встречал.
  • @!!ex © (22.12.08 09:15) [4]
    > [2] ggg ©   (22.12.08 05:15)

    Не вижу проблем в функциях прослойках. Ето первое.
    Поиск метода в списке - это медленно. Ето второе. Мой вариант буедт быстрее. и при большом количестве методов - заметно быстрее.


    > [3] ggg ©   (22.12.08 05:54)

    У меня тоже сначала положительные числа стояли... Потом выяснилось что это не работает... где то увидел что отрицательные. заменил. заработало.
  • @!!ex © (22.12.08 09:16) [5]
    > У меня тоже сначала положительные числа стояли... Потом
    > выяснилось что это не работает... где то увидел что отрицательные.
    > заменил. заработало.

    Это не говорит, что "-" это правильно. Вполне возможно что это НЕ правильно. Слишком мало я еще ханимаюсь Луа.
  • ggg © (22.12.08 17:34) [6]
    Да, мой вариант помедленнее, зато универсальнее. Если скорость не критична, вполне можно использовать.


    unit LUAParser;
    type
     TLUAExternObjectMethodAddress = function (const P: TLUAParser): Integer of object;

     TLUAExternObjectMethod = record
       Name: string;
       Parser: TLUAParser;
       ParamCount: Integer;
       Address: TLUAExternObjectMethodAddress;
     end;
    ...
    var
     ParserExternObjectMethods: array of TLUAExternObjectMethod;
    ..
    function CallDelphiObjectsMethod(L: Plua_State): Integer; cdecl;
    var
     N: Integer;
     methodIndex: Integer;
    begin
     N := lua_gettop(L);
     if (N < 1) then
       raise ELuaException.CreateFmt('CallDelphiObjectsMethod failed. 1 argument' +
           ' expected, but %d found.', [N]);

     if (not lua_isnumber(L, -1)) then
       raise ELuaException.CreateFmt('Not a number type argument passed to ' +
           'CallDelphiObjectsMethod. Received type is %s.', [lua_typename(L, lua_type(L, -1))]);

     methodIndex := trunc(lua_tonumber(L, -1));

     if (0 <= methodIndex) and (methodIndex < Length(ParserExternObjectMethods)) then
     begin
    //    ShowMessage('Method found. All is ok :)');
       lua_pop(L, 1);
       with ParserExternObjectMethods[methodIndex] do
         if Assigned(Address) then
           Address(Parser);
     end
     else
       raise ELuaException.CreateFmt('Method %d not found', [methodIndex]);

     Result := 1;
    end;
    ...
    procedure TLUAParser.AddObjectMethod(const MethodName: string;
     const AMethod: TLUAExternObjectMethodAddress;
     const ParamCount: Integer);
    var
     script: string;
     prmStr: string;
     i, n: Integer;
     methodIndex: Integer;

     function ifNotNull(const s: string): string;
     begin
       if (s <> '') then
         Result := ','
       else
         Result := ''
     end;

    begin
     methodIndex := Length(ParserExternObjectMethods);
     SetLength(ParserExternObjectMethods, methodIndex + 1);
     ParserExternObjectMethods[methodIndex].Name := MethodName;
     ParserExternObjectmethods[methodIndex].Parser := Self;
     ParserExternObjectmethods[methodIndex].Address := AMethod;
     ParserExternObjectmethods[methodIndex].ParamCount := ParamCount;

     n := Length(FExternObjectMethods);
     SetLength(FExternObjectMethods, n + 1);
     FExternObjectMethods[n] := methodIndex;

     if ParamCount > 0 then
     begin
       prmStr := 'p1';
       for i := 2 to ParamCount do
         prmStr := prmStr + ',p' + IntToStr(i);
     end
     else
       prmStr := '';

     script :=
       MethodName + ' = function(' + prmStr + ') ' +
       '   return Parser_CallDelphiObjectsMethod(' + prmStr +
           ifNotNull(prmStr) + IntToStr(methodIndex) + ');' +
       ' end ';

     LoadFromMemory(script);
    end;



    С lua_pop вроде всё нормально, убирает n последних элементов из стека.
  • Servy © (23.12.08 05:29) [7]
    > Проблема в том, что я не умею вызвать метод объекта О по
    > названию метода Method. Помнится, где-то на этом форуме
    > встречал как это делается, но тема потерялась и всё забылось
    > :(


    В меру моего недосконального знания, Делфи позволяет runtime получить указатель на адрес метода по имени. Однако, чтобы вызвать метод, ему надо передать параметры с использованием calling conversion, принятой этим методом, а сделать это правильным образом не представляется возможным, так как храниться только адрес метода, но не его параметры и способ их передачи.

    Соответственно (возможно бесполезное) желание порулить делфевыми объектами из lua полностью успехом увенчаться не может. Однако, есть альтернативные варианты, одну из вариаций вы изложили в [6], с "ручной" регистрацией всех нужных методов (для каждого метода нужно вызвать AddObjectMethod). Однако, реализация в [6] довольно странная:

    1. Там регистрируется не просто метод объекта, а метод в связке с объектом. TLUAExternObjectMethodAddress занимает 8 байт, хранит указатель на экземпляр класса, для которого вызывать метод (который передавать в Self), и адрес собственно метода. Таким образом, я могу вызывать метод print только для одного экземпляра - какое же это ООП?

    2. Список параметров странный. Как будет выглядеть вызов зарегестрированного TForm1.print() в луа? Судя по всем "print(text);" - тоже совсем не похоже на ООП, объект (экземпляр) отсутствует :).

    3. Все методы получают по сути один параметр - окружение луа, а настоящие параметры должны оттуда выковыривать сами, что не представляется удобным).

    Так что, решение [1] выглядит более разумным.

    Еще бы генератор прослоек написать, чтобы все время руками их не набивать ;). Да как т.н. эксперт в Делфи встроить, чтобы по хоткею переходники генерировала, было б удобно.

    И Obj:=TMyClass(lua_tonumber(State,-2));
    tonumber немного смущает, видимо объект по сути представляет число в типах lua. А для числа определены, например, сложение и вычитание :). Понятно, что если к числу, которое на самом деле указатель, прибавить что-то, то есть хороший шанс в программе словить AV. Ясно, конечно, что скриптописатель "сам дурак", однако есть возможность непозволить ему напортачить - тип userdata в луа, все операции для которого вы устанавливаете (или не устанавливаете) самостоятельно.


    > вызов метода из скрипта:
    > ClassMethod(MyClass,10);


    Если я правильно помню мануал по луа, то эта запись эквивалентна следующей:

    MyClass.ClassMethod(10);



    Этот вариант записи в контексте ООП выглядит более привычно.
  • @!!ex © (23.12.08 14:38) [8]
    > Если я правильно помню мануал по луа, то эта запись эквивалентна
    > следующей:
    >
    > MyClass.ClassMethod(10);
    >
    > Этот вариант записи в контексте ООП выглядит более привычно.

    Вот только MyClass не является классом с точки зрения Lua, так что такая запись работать не будет.
  • @!!ex © (23.12.08 14:40) [9]
    Хотя стоп. Может я и не прав... Проверю.
    Только не
    MyClass.ClassMethod(10);
    а
    MyClass:ClassMethod(10);
  • Servy © (23.12.08 15:22) [10]
    > [9]


    Да нет, судя по всему это меня подвела память.

    A call v:name(args) is syntactic sugar for v.name(v,args), except that v is evaluated only once.

    Таким образом, v должен быть либо таблицей, у которого есть ключ name со значением типа function, либо для v должен быть перекрыт "index" метаметод, который будет возвращать нужную функцию. То есть, без дополнительных ухищрений, такой синтаксис использовать нельзя.
  • @!!ex © (23.12.08 15:25) [11]
    > [10] Servy ©   (23.12.08 15:22)

    Да, именно это я и имел ввиду.
  • @!!ex © (24.12.08 10:49) [12]
    В принципе ничего не мещает передавать MyClass не как указатель на класс, а как таблицу.
    Сути это сильно не поменяет. Только какую функцию вызывать будет уже решать таблица, а не сам кодер.

    Тогда код функции будет выглядеть немного страшно:
    function TMyClass:ClssMethod(Value)
     TMyClass_ClassMethod(self.classPointer, Value);
    end;

    Зато вызов вполне себе красиво:
    MyClass:ClassMethod(10);
    И все. красота.

    Однако добавление метода в Lua уже не будет таким тривиальным. Потому что надо будет построить таблицу и ее передать...
  • @!!ex © (24.12.08 16:51) [13]
    сделал. Работает.
    http://pda.delphimaster.net/?n=3&id=1230126644
 
Конференция "Игры" » Вызов метода из Lua [Delphi, Windows]
Есть новые Нет новых   [134430   +4][b:0][p:0.004]