Results 1 to 19 of 19

Thread: [LAPE] variantInvoke with object.function

  1. #1
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default [LAPE] variantInvoke with object.function

    Hi,
    I guess it's not possible, but it's always better to ask.

    I want to use variantInvoke() on function ascribed to type. Like:

    Simba Code:
    program new;

    type TBum = record
    a,b : integer;
    end;

    procedure TBum.create(x,y : integer);
    begin
      self.a := x;
      self.b := y;
    end;

    function TBum.exec ( c : integer) : integer;
    begin
      Result := self.a * self.b +c;
    end;

      var Bim :TBum;
    begin
      Bim.create(1,2);
      writeln ( variantInvoke('Bim.exec',[300]) );
    end.

    It doesn't work, but maybe there is some other way?

  2. #2
    Join Date
    Sep 2010
    Posts
    5,762
    Mentioned
    136 Post(s)
    Quoted
    2739 Post(s)

    Default

    I got:

    Simba Code:
    program new;


    type TBum = record
    a,b : integer;
    end;

    type functionPointer = function(params:array of variant):integer;


    procedure TBum.create(x,y : integer);
    begin
      self.a := x;
      self.b := y;
    end;

    function TBum.exec ( c : integer) : integer;
    begin
      Result := self.a * self.b +c;
      writeln(result);
    end;

    procedure invokeF(p:^functionPointer;params:array of variant);
    var
      t:function(m:array of variant):integer;
    begin
      t := @p^;
      variantInvoke('t', params);
    end;

      var Bim :TBum;
      func:function(i:integer):integer;
      f:^functionPointer;
    begin
      Bim.create(1,2);
      func := @Bim.exec(300);
      f := @func;
      invokeF(f, [300]);
    end.

    I'm not sure you will be able to tweak it to work exactly like variant invoke though. Maybe you don't even need to use pointers IDK if someone has a easier way please post it lol. IDK how to create a pointer to a generic function that could have any number of parameters or any type result so I don't think there is a way to make it work like variant invoke

  3. #3
    Join Date
    Nov 2011
    Location
    England
    Posts
    3,072
    Mentioned
    296 Post(s)
    Quoted
    1094 Post(s)

    Default

    Nope, Variant invoke isn't made for types & methods.

    All it is internally is a giant case statement

  4. #4
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default

    @Robert

    Simba Code:
    program new;


    type TBum = record
    a,b : integer;
    end;

    procedure TBum.create(x,y : integer);
    begin
      self.a := x;
      self.b := y;
    end;

    function TBum.exec ( c,d : integer) : integer;
    begin
      Result := self.a * self.b +c - d;
      writeln(result);
    end;

    procedure Invoke2 (p : pointer);
    var
      func : pointer;
      f :^ Pointer;
    begin
      func := p;
      f := @func;
      writeln (variantInvoke('f', []));
    end;

    var Bim :TBum;
    begin
      Bim.create(1,2);
      Invoke2( @Bim.exec(110,10) );
    end.

    I've used non-specific pointer so you can pass any number/type of parameters. But neither of version can recover a result. Working on it




    I'm one step further, I see that VariantInvoke is not needed at all.
    Simba Code:
    program new;

    type TBum = record
    a,b : integer;
    end;

    procedure TBum.create(x,y : integer);
    begin
      self.a := x;
      self.b := y;
    end;

    function TBum.exec ( c,d : integer) : integer;
    begin
      Result := self.a * self.b +c - d;
      writeln('exec: '+tostr(result));
    end;

    function ExecF ( P : Pointer ) : Variant;
    var PP : pointer;
    begin
        PP := P;
        Result := integer(PP^);
    end;
       var Bim :TBum;
    begin
      Bim.create(1,2);
      writeln (ExecF (@Bim.exec(10,4) ));
    end.

    The only advantage of VariantInvoke is that it knows what type of Result each function outputs . So if we don't use it, but that function above and we want to make it more multi-purpose, we need to make second parameter telling which type to cast and build case-tree.


    This way function can be executed only one time.
    Last edited by bg5; 02-07-2015 at 03:22 PM.

  5. #5
    Join Date
    Sep 2010
    Posts
    5,762
    Mentioned
    136 Post(s)
    Quoted
    2739 Post(s)

    Default

    Quote Originally Posted by bg5 View Post
    ...
    The second version was recovering a result for me

  6. #6
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default

    Quote Originally Posted by Robert View Post
    The second version was recovering a result for me
    When I compile your code it prints a result, but from this line:
    Simba Code:
    func := @Bim.exec(300);
    not from invoke() function.

  7. #7
    Join Date
    Oct 2006
    Location
    Netherlands
    Posts
    3,285
    Mentioned
    105 Post(s)
    Quoted
    494 Post(s)

    Default

    The problem is that a variant cant hold a pointer to a record. iirc lape uses the first param as the object.
    Working on: Tithe Farmer

  8. #8
    Join Date
    Nov 2011
    Location
    England
    Posts
    3,072
    Mentioned
    296 Post(s)
    Quoted
    1094 Post(s)

    Default

    Quote Originally Posted by masterBB View Post
    The problem is that a variant cant hold a pointer to a record. iirc lape uses the first param as the object.
    Wouldn't make a difference. Variant Invoke is nothing special it's just a giant case statement loaded after parsing but before "code building".

    Simba Code:
    case Name of
      'Lowercase': begin if Length(Params) = 1 then Result := Lowercase(Params[0]); end;
      'ArcTan2': begin if Length(Params) = 2 then Result := ArcTan2(Params[0], Params[1]) end;
    end;

    So no, It wont work with anything local so quit using it!
    Last edited by Olly; 02-07-2015 at 04:49 PM.

  9. #9
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default

    Yes, I've found already that function must be registered somewhere in code (with all it's parameters and output type) or it doesn't exist ( for variantinvoke)
    So I surrender

    Simba Code:
    var
      Bim :TBum;

    function BimExecWrap (i : integer) : integer; begin Result:= Bim.exec(i); end;

    begin
      Bim.create(1,2);
      writeln ( variantInvoke('BimExecWrap',[200]) );
    end.


    However I still don't understand one thing:
    https://github.com/MerlijnWajer/Simb...=VariantInvoke
    ?
    Last edited by bg5; 02-07-2015 at 05:02 PM.

  10. #10
    Join Date
    Nov 2011
    Location
    England
    Posts
    3,072
    Mentioned
    296 Post(s)
    Quoted
    1094 Post(s)

    Default

    Quote Originally Posted by bg5 View Post
    Yes, I've found already that function must be registered somewhere in code (with all it's parameters and output type) or it doesn't exist ( for variantinvoke)
    So I surrender

    Simba Code:
    var
      Bim :TBum;

    function BimExecWrap (i : integer) : integer; begin Result:= Bim.exec(i); end;

    begin
      Bim.create(1,2);
      writeln ( variantInvoke('BimExecWrap',[200]) );
    end.


    However I still don't understand one thing:
    https://github.com/MerlijnWajer/Simb...=VariantInvoke
    ?
    It's lape. https://github.com/SRL/Lape/blob/mas...utils.pas#L151

  11. #11
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    I've always said that VariantInvoke was designed extremely horrible.. It should NOT be checking for compatibility or anything. It shouldn't even be using Variants. It should actually be using assembly and moving each argument onto the stack.


    Simba Code:
    Function CallFunc(Address: PtrUInt; ArgC: UInt32; Arg: Array of PtrUInt): Integer;
    {$ASMMODE INTEL}
    begin
      asm
        mov ecx, ArgC
        mov edx, Arg
        @start:
        dec ecx
        push dword ptr[edx + ecx * 4]
        jnz @start
        call [Address]
        mov @Result, eax
      end;
    end;

    procedure foo(Arg: Integer; Arg2: String; Arg3: Array of Integer); cdecl;
    var
      I: Integer;
    begin
      writeln(Arg);
      writeln(Arg2);

      write('[');
      write(Arg3[0]);
      write(', ');
      write(Arg3[1]);
      writeln(']');
    end;

    var
      Arg1: Integer;
      Arg2: String;
      Arg3: Array of Integer;
    begin
      Arg1 := 100;
      Arg2 := 'hello';

      SetLength(Arg3, 2);
      Arg3[0] := 1024;
      Arg3[1] := 2048;

      CallFunc(PtrUInt(@Foo), 3, [Arg1, PtrUInt(Arg2), PtrUInt(Arg3)]);
    end.


    That results in:

    Progress Report:
    100
    hello
    [1024, 2048]


    This way, you can push classes onto the stack, their functions and their arguments.. You'd be able to call any function including native ones with any amount of parameters.. For lape, all that's needed is to translate the string 'TBum.exec' to its function address and invoke it via:

    CallFunc(FuncNameToAddress, ArgumentCount, Args);


    Add that to a plugin and you've got yourself a function that can invoke any function. The only thing you'd need is a way to get the address of a lape function via its name lookup.
    Last edited by Brandon; 02-07-2015 at 06:50 PM.
    I am Ggzz..
    Hackintosher

  12. #12
    Join Date
    Sep 2010
    Posts
    5,762
    Mentioned
    136 Post(s)
    Quoted
    2739 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    I've always said that VariantInvoke was designed extremely horrible.. It should NOT be checking for compatibility or anything. It shouldn't even be using Variants. It should actually be using assembly and moving each argument onto the stack.


    Simba Code:
    Function CallFunc(Address: PtrUInt; ArgC: UInt32; Arg: Array of PtrUInt): Integer;
    {$ASMMODE INTEL}
    begin
      asm
        mov ecx, ArgC
        mov edx, Arg
        @start:
        dec ecx
        push dword ptr[edx + ecx * 4]
        jnz @start
        call [Address]
        mov @Result, eax
      end;
    end;

    procedure foo(Arg: Integer; Arg2: String; Arg3: Array of Integer); cdecl;
    var
      I: Integer;
    begin
      writeln(Arg);
      writeln(Arg2);

      write('[');
      write(Arg3[0]);
      write(', ');
      write(Arg3[1]);
      writeln(']');
    end;

    var
      Arg1: Integer;
      Arg2: String;
      Arg3: Array of Integer;
    begin
      Arg1 := 100;
      Arg2 := 'hello';

      SetLength(Arg3, 2);
      Arg3[0] := 1024;
      Arg3[1] := 2048;

      CallFunc(PtrUInt(@Foo), 3, [Arg1, PtrUInt(Arg2), PtrUInt(Arg3)]);
    end.


    That results in:

    Progress Report:
    100
    hello
    [1024, 2048]


    This way, you can push classes onto the stack, their functions and their arguments.. You'd be able to call any function including native ones with any amount of parameters.. For lape, all that's needed is to translate the string 'TBum.exec' to its function address and invoke it via:

    CallFunc(FuncNameToAddress, ArgumentCount, Args);


    Add that to a plugin and you've got yourself a function that can invoke any function. The only thing you'd need is a way to get the address of a lape function via its name lookup.
    Would it be possible to call a function via a string like (callfunc('test', 0, []))

  13. #13
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by Robert View Post
    Would it be possible to call a function via a string like (callfunc('test', 0, []))
    Sure why not?

    Just push 0 arguments onto the stack:

    Simba Code:
    Function CallProc(Address: PtrUInt): PtrUInt;
    {$ASMMODE INTEL}
    begin
      asm
        call [Address]
        mov @Result, eax
      end;
    end;

    You can actually make these functions very generic to call ANY function.

    A. Check ArgC. If it's 0, push nothing and just "call [Address]".
    B. If Cdecl, just call it with CallFunc(Address, ArgCount, [Arg1, Arg2, Arg3]); Pop nothing off the stack.
    C. If Stdcall, just call it with CallFunc(Address, ArgCount, [Arg3, Arg2, Arg1]); Pop all args off the stack.

    All in all, you'd have one function or two at most for calling anything you want.


    Again, for Lape, you'd need to get the address of the function specified by said string.
    Last edited by Brandon; 02-07-2015 at 08:57 PM.
    I am Ggzz..
    Hackintosher

  14. #14
    Join Date
    Oct 2006
    Location
    Netherlands
    Posts
    3,285
    Mentioned
    105 Post(s)
    Quoted
    494 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Simba Code:
    Function CallFunc(Address: PtrUInt; ArgC: UInt32; Arg: Array of PtrUInt): Integer;
    {$ASMMODE INTEL}
    begin
      asm
        mov ecx, ArgC
        mov edx, Arg
        @start:
        dec ecx
        push dword ptr[edx + ecx * 4]
        jnz @start
        call [Address]
        mov @Result, eax
      end;
    end;
    Completly offtopic but I got curious . Could you confirm if I am on the right track understanding that code, I have never done assembly but just googled those commands:

    mov ecx, ArgC
    mov edx, Arg
    That basically moves argument count and the address of the first element of the argument in the ecx and edx register.

    dec ecx
    Lowers the count. Making the stack fill backwards.

    push dword ptr[edx + ecx * 4]
    Seems to push the argument at location @arg + (count*4) to the stack. The ecx increment by 4 Bytes(one pointer) basically simulates regular index behaviour of an array. As it would in pascal or simba.

    jnz @start
    This one actually got me puzled for a while. It is a conditional jump. Though the condition is ecx != 0. It seems to me jnz will always check ecx, you can't change it to another register(odd, but it is asm so it might make sense :P) This is what essentialy simulates an while loop.

    call [Address]
    mov @Result, eax
    So it calls the functions. It automatically passes the stack(I understand how and why). But doesn't the stack needs to be cleared or something? Or does the call do that. Also is the result always in eax or it just a pascal thingy? It stores eax in the pascal Result making the assembly code return something.

    I would apreciate if you can answer those two questions about call

    thanks in advance for reading this.
    Last edited by masterBB; 02-07-2015 at 07:50 PM.
    Working on: Tithe Farmer

  15. #15
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by masterBB View Post
    But doesn't the stack needs to be cleared or something? Or does the call do that. Also is the result always in eax or it just a pascal thingy? It stores eax in the pascal Result making the assembly code return something.

    I would apreciate if you can answer those two questions about call

    thanks in advance for reading this.

    Stdcall requires no cleanup. Cdecl requires manually cleanup. In assembly it's usually done by doing "ret (ArgCount * 4)". In the below, you'll notice I used a loop instead and popped each one off manually. That's because I'm not sure if Pascal's assembly will "ret" or not. Better to be safe than sorry.


    The result is ALWAYS in eax for 32-bit assembly and rax for 64-bit assembly. It is never stored anywhere else. If your 32-bit code wants to return a 64-bit value, then the result is stored in a pair of registers: eax and edx. To turn them into a 64-bit value in 32-bit code, you'd just do:

    mov lowBits, eax
    mov highBits, edx
    Result := (lowBits shl 32) or highBits.


    Code explanation:

    Simba Code:
    asm
        mov ecx, ArgC //Move the argument count into a register.
        mov edx, Arg  //Move the address of arguments[0] into a register.

       
        @@start:      //while (ArgCount > 0)

          dec ecx    //We decrease the arg count and use it as an index in the next line..
          push dword ptr[edx + ecx * 4]  //Push Argument[--ArgCount].

        jnz @@start  //If the argument count is 0, we break out of the loop and call the function. We've pushed enough arguments already.
        call [Address] //call the function with all the arguments pushed onto the stack in reverse order (CDECL)
       
        mov ecx, ArgC  //move the original argument count back into a register.
        @@end:         //while (ArgCount > 0)
          dec ecx      
          pop edx //Cleanup the stack by deleting:  Pop Argument[--ArgCount].
        jnz @@end  //Are we finished with ALL the arguments?
        mov @Result, eax  //If so, move the result of the function call into "Result"
      end


    This is essentially the same as:

    Simba Code:
    Function callFunc(Addr: PtrUInt; ArgCount: Integer; Arguments: Array of AnyArgs): Pointer;
    begin
      For I := ArgCount - 1 DownTo 0 Do
      begin
        push(Arguments[I]);
      end;
     
      Result := Call(Address);
    end;

    Where AnyArgs is array of PtrUInt.



    This is the most generic func I could come up with so far, to handle both stdcall and cdecl and functions with no args:

    Simba Code:
    Function CallFunc(Address: PtrUInt; ArgC: UInt32; Arg: Array of PtrUInt; isCDecl: Boolean = True): PtrUInt;
    {$ASMMODE INTEL}
    begin
      if (isCDecl and (ArgC > 0)) then
      asm
        mov ecx, ArgC
        mov edx, Arg
        @@start:
          dec ecx
          push dword ptr[edx + ecx * 4]
        jnz @@start
        call [Address]

        mov ecx, ArgC
        @@end:
          dec ecx
          pop edx
        jnz @@end
        mov @Result, eax
      end else
        if (ArgC > 0) then
        asm
          mov eax, 0
          mov ecx, ArgC
          mov edx, Arg
          @@start:
            push dword ptr[edx + eax * 4]
            cmp eax, ecx
            inc eax
          jl @@start
          call [Address]
          mov @Result, eax
        end else
          asm
            call [Address]
            mov @Result, eax
          end;
    end;

    begin
      CallFunc(PtrUInt(@Foo), 3, [PtrUInt(Arg1), PtrUInt(Arg2), PtrUInt(Arg3)]); //Calling CDECL function.
      CallFunc(PtrUInt(@Bar), 3, [PtrUInt(Arg1), PtrUInt(Arg2), PtrUInt(Arg3)], false); //Calling STDCALL function.
      CallFunc(PtrUInt(@Meh), 0, []); //calling either CDECL OR STDCALL with no args.
    end.

    This is what VariantInvoke should have really been.
    Last edited by Brandon; 02-07-2015 at 09:07 PM.
    I am Ggzz..
    Hackintosher

  16. #16
    Join Date
    Oct 2006
    Location
    Netherlands
    Posts
    3,285
    Mentioned
    105 Post(s)
    Quoted
    494 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Code explanation:
    Wow thanks! Never thought I would say this, but asm looks very readable in this situation(better than high level languages). I just tried some asm with lazarus(in pascal code), and register stuff starts making sense. We should really use something similar to your last piece of code in simba, it is so broad applyable!
    Working on: Tithe Farmer

  17. #17
    Join Date
    Sep 2010
    Posts
    5,762
    Mentioned
    136 Post(s)
    Quoted
    2739 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Sure why not?

    Just push 0 arguments onto the stack:

    Simba Code:
    Function CallProc(Address: PtrUInt): PtrUInt;
    {$ASMMODE INTEL}
    begin
      asm
        call [Address]
        mov @Result, eax
      end;
    end;

    You can actually make these functions very generic to call ANY function.

    A. Check ArgC. If it's 0, push nothing and just "call [Address]".
    B. If Cdecl, just call it with CallFunc(Address, ArgCount, [Arg1, Arg2, Arg3]); Pop nothing off the stack.
    C. If Stdcall, just call it with CallFunc(Address, ArgCount, [Arg3, Arg2, Arg1]); Pop all args off the stack.

    All in all, you'd have one function or two at most for calling anything you want.


    Again, for Lape, you'd need to get the address of the function specified by said string.
    So I could get execute a function based off parameters received from a getpage.. interesting

  18. #18
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by masterBB View Post
    Wow thanks! Never thought I would say this, but asm looks very readable in this situation(better than high level languages). I just tried some asm with lazarus(in pascal code), and register stuff starts making sense. We should really use something similar to your last piece of code in simba, it is so broad applyable!

    It's always readable. It just has a huge learning curve but if you use it all the time, you'd be able to read it as if reading any high level language.

    Pascal ASM takes the cake. It's the easiest of all as far as I can remember atm. And if loops or anything gets difficult, you can literally mix it (as little inline asm as possible):

    Simba Code:
    type AnyArg = PtrUInt;

    Function callFuncCdecl(Addr: PtrUInt; ArgCount: Integer; Arguments: Array of AnyArg): Pointer;
    var
      Arg: AnyArg;
    {$ASMMODE INTEL}
    begin
      For I := ArgCount - 1 DownTo 0 Do
      begin
        Arg := Arguments[I];
        asm
          mov edx, Arg; //Not really needed. Can just do push Arg.
          push edx;     //push the arguments onto the stack.
        end;
      end;

      //call the function and get the result.
      asm
        call [Addr];
        mov @Result, eax; //rax on 64-bit
      end;
     
      For I := ArgCount - 1 DownTo 0 Do
      asm
        pop edx //pop the arguments off of the stack. rdx on 64-bit.
      end;
    end;

    Pretty neat Of course I prefer to mix as little as possible but meh..
    Last edited by Brandon; 02-08-2015 at 01:38 AM.
    I am Ggzz..
    Hackintosher

  19. #19
    Join Date
    Sep 2006
    Posts
    6,089
    Mentioned
    77 Post(s)
    Quoted
    43 Post(s)

    Default

    This is what the ffi ("native") will do for you. VariantInvoke was introduced as a workaround to have a feature similar to PascalScript's CallProc.
    Hup Holland Hup!

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •