SRL Archive - RS3 Help

[LAPE] variantInvoke with object.function


bg5

2015-02-06 16:16:02

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:

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?

rj

2015-02-06 16:23:48

I got:

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

Olly

2015-02-06 18:27:13

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

All it is internally is a giant case statement :p

bg5

2015-02-07 09:04:23

@Robert

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:p




I'm one step further, I see that VariantInvoke is not needed at all.
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.

rj

2015-02-07 10:42:36

From: bg5
…


The second version was recovering a result for me :garfield:

bg5

2015-02-07 11:00:31

From: Robert
The second version was recovering a result for me :garfield:

When I compile your code it prints a result, but from this line:
func := @Bim.exec(300);
not from invoke() function.

masterBB

2015-02-07 11:29:54

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

Olly

2015-02-07 11:45:25

From: masterBB
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".

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! :D

bg5

2015-02-07 11:56:59

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:whistling:


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/Simba/search?utf8=%E2%9C%93&q=VariantInvoke
?

Olly

2015-02-07 12:07:40

From: bg5
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:whistling:


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/Simba/search?utf8=%E2%9C%93&q=VariantInvoke
?


It's lape. https://github.com/SRL/Lape/blob/master/lputils.pas#L151

Brandon

2015-02-07 13:44:09

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.



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:


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.

rj

2015-02-07 14:29:32

From: Brandon
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.



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:


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, []))

Brandon

2015-02-07 14:34:23

From: Robert
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:


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.

masterBB

2015-02-07 14:48:29

From: Brandon

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 :p. Could you confirm if I am on the right track understanding that code, I have never done assembly but just googled those commands:

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

[INDENT]dec ecx[/INDENT]
Lowers the count. Making the stack fill backwards.

[INDENT]push dword ptr[edx + ecx * 4][/INDENT]
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.

[INDENT]jnz @start[/INDENT]
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.

[INDENT]call [Address]
mov @Result, eax
[/INDENT]
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.

Brandon

2015-02-07 15:09:06

From: masterBB
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:


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:


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:


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.

masterBB

2015-02-07 18:13:44

From: Brandon
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!

rj

2015-02-07 19:35:15

From: Brandon
Sure why not?

Just push 0 arguments onto the stack:


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

Brandon

2015-02-07 20:21:22

From: masterBB
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):


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 :D Of course I prefer to mix as little as possible but meh..

nielsie95

2015-02-08 04:55:01

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.