Recent

Author Topic: varargs in function/procedure definitions  (Read 642 times)

440bx

  • Hero Member
  • *****
  • Posts: 2362
varargs in function/procedure definitions
« on: April 16, 2021, 11:35:46 am »
Hello,

I wanted to define a function with a variable number of arguments "the C way", that is without using "array of const".  I tried this:
Code: Pascal  [Select][+][-]
  1. procedure OutputError(InAsciizFormatString : pchar); cdecl; varargs;
  2. begin
  3. end;
  4.  
but the compiler was not happy with that.

I tried this too :
Code: Pascal  [Select][+][-]
  1. procedure A(parameters : array of const); cdecl;
  2. begin
  3. end;
  4.  
but the compiler didn't like that either.

My question is: is there a way to make the first function definition work without using "array of const" and, is it possible to define a function/procedure that is fully equivalent to a C function i.e, no variants being passed, just raw data (as C does) ?

Related to the above question: is there a way of defining a function/procedure using an ellipsis (...) as is done is C ? I believe I saw somewhere in the documentation a way of using the ... for parameters but I cannot find it again and, I'm starting to think I probably imagined that.

Thank you for your help.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 10784
Re: varargs in function/procedure definitions
« Reply #1 on: April 16, 2021, 12:53:59 pm »
I have this, made by Barry Kelly which I adapted to compile in FreePascal.
It still contains a small bug, but you'll get the idea:
He wrote
"The basic knowledge needed is the C calling convention on the x86: the stack grows downwards, and C pushes arguments from right to left. Thus, a pointer to the last declared argument, after it is incremented by the size of the last declared argument, will point to the tail argument list. From then, it's simply a matter of reading the argument out and incrementing the pointer by an appropriate size to move deeper into the stack. The x86 stack in 32-bit mode is 4-byte aligned generally, and this also means that bytes and words are passed as 32-bit integers.

Anyhow, here's a helper record in a demo program that shows how to read out data. Note that Delphi seems to be passing Extended types in a very odd way; however, you likely won't have to worry about that, as 10-byte floats aren't generally widely used in C, and aren't even implemented in the latest MS C, IIRC."

Code: Pascal  [Select][+][-]
  1. {$MODE DELPHI}
  2. {$apptype console}
  3. {$ALIGN 4}
  4. type  
  5.   TArgPtr = record
  6.   private
  7.     FArgPtr: PByte;
  8.     class function Align(Ptr: Pointer; Align: Integer): Pointer; static;
  9.   public
  10.     constructor Create(LastArg: Pointer; Size: Integer);
  11.     // Read bytes, signed words etc. using Int32
  12.     // Make an unsigned version if necessary.
  13.     function ReadInt32: Integer;
  14.     // Exact floating-point semantics depend on C compiler.
  15.     // Delphi compiler passes Extended as 10-byte float; most C
  16.     // compilers pass all floating-point values as 8-byte floats.
  17.     function ReadDouble: Double;
  18.     function ReadExtended: Extended;
  19.     function ReadPChar: PChar;
  20.     procedure ReadArg(var Arg; Size: Integer);
  21.   end;
  22.  
  23. constructor TArgPtr.Create(LastArg: Pointer; Size: Integer);
  24. begin
  25.   FArgPtr := LastArg;
  26.   // 32-bit x86 stack is generally 4-byte aligned
  27.   FArgPtr := Align(FArgPtr + Size, 4);
  28. end;
  29.  
  30. class function TArgPtr.Align(Ptr: Pointer; Align: Integer): Pointer;
  31. begin
  32.   Result := Pointer((PtrUint(Ptr) + Align - 1) and not (Align - 1));
  33. end;
  34.  
  35. function TArgPtr.ReadInt32: Integer;
  36. begin
  37.   ReadArg(Result, SizeOf(Integer));
  38. end;
  39.  
  40. function TArgPtr.ReadDouble: Double;
  41. begin
  42.   ReadArg(Result, SizeOf(Double));
  43. end;
  44.  
  45. function TArgPtr.ReadExtended: Extended;
  46. begin
  47.   ReadArg(Result, SizeOf(Extended));
  48. end;
  49.  
  50. function TArgPtr.ReadPChar: PChar;
  51. begin
  52.   ReadArg(Result, SizeOf(PChar));
  53. end;
  54.  
  55. procedure TArgPtr.ReadArg(var Arg; Size: Integer);
  56. begin
  57.   Move(FArgPtr^, Arg, Size);
  58.   FArgPtr := Align(FArgPtr + Size, 4);
  59. end;
  60.  
  61. procedure Dump(const types: string); cdecl;
  62. var
  63.   ap: TArgPtr;
  64.   cp: PChar;
  65. begin
  66.   cp := PChar(types);
  67.   ap := TArgPtr.Create(@types, SizeOf(string));
  68.   while True do
  69.   begin
  70.     case cp^ of
  71.       #0:
  72.       begin
  73.         Writeln;
  74.         Exit;
  75.       end;
  76.  
  77.       'i': Write(ap.ReadInt32, ' ');
  78.       'd': Write(ap.ReadDouble, ' ');
  79.       'e': Write(ap.ReadExtended, ' ');
  80.       's': Write(ap.ReadPChar, ' ');
  81.     else
  82.       Writeln('Unknown format');
  83.       Exit;
  84.     end;
  85.     Inc(cp);
  86.   end;
  87. end;
  88.  
  89. type
  90.   PDump = procedure(const types: string) cdecl varargs;
  91. var
  92.   MyDump: PDump;
  93.  
  94. function AsDouble(e: Extended): Double;
  95. begin
  96.   Result := e;
  97. end;
  98.  
  99. function AsSingle(e: Extended): Single;
  100. begin
  101.   Result := e;
  102. end;
  103.  
  104. procedure Go;
  105. begin
  106.   MyDump := @Dump;
  107.  
  108.   MyDump('iii', 10, 20, 30);
  109.   MyDump('sss', 'foo', 'bar', 'baz');
  110.  
  111.   // Looks like Delphi passes Extended in byte-aligned
  112.   // stack offset, very strange; thus this doesn't work.
  113.   MyDump('e', Double(2.0));
  114.   // These two are more reliable.
  115.   MyDump('d', AsDouble(2));
  116.   // Singles passed as 8-byte floats.
  117.   MyDump('d', AsSingle(2));
  118. end;
  119.  
  120. begin
  121.   Go;
  122. end.
« Last Edit: April 16, 2021, 12:57:08 pm by Thaddy »

Thaddy

  • Hero Member
  • *****
  • Posts: 10784
Re: varargs in function/procedure definitions
« Reply #2 on: April 16, 2021, 01:04:20 pm »
Ah, does not work correct in 64 bit...Compiles, but does not work.
Will have a look.
« Last Edit: April 16, 2021, 01:24:10 pm by Thaddy »

440bx

  • Hero Member
  • *****
  • Posts: 2362
Re: varargs in function/procedure definitions
« Reply #3 on: April 16, 2021, 01:08:50 pm »
Thank you Thaddy.  That's a very interesting little bit of code.

As you probably surmised from my question, I was hoping for some "compiler support" to simply use either "..." or "varargs".

Ah, does not work correct in 64 bit...
It doesn't work in 64 bit because it assumes the arguments are 4 bytes in size (except extended types) and, obviously in 64bit that's not true.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 10784
Re: varargs in function/procedure definitions
« Reply #4 on: April 16, 2021, 01:26:39 pm »
Yes, I know what caused it. But it should be able to be massaged into not using Variants.
( It should also be able to be much simpler using {$pointermath on} )
« Last Edit: April 16, 2021, 01:28:30 pm by Thaddy »

MarkMLl

  • Hero Member
  • *****
  • Posts: 2699
Re: varargs in function/procedure definitions
« Reply #5 on: April 16, 2021, 01:34:57 pm »
There might be something useful in https://forum.lazarus.freepascal.org/index.php/topic,53348.0.html noting in particular PascalDragon's comments.

In the end I decided that what I was trying to do was more trouble than it was worth, and made vararg functions a case which had to be handled separately.

MarkMLl

Turbo Pascal v1 on CCP/M-86, multitasking with LAN and graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

440bx

  • Hero Member
  • *****
  • Posts: 2362
Re: varargs in function/procedure definitions
« Reply #6 on: April 16, 2021, 02:10:50 pm »
There might be something useful in https://forum.lazarus.freepascal.org/index.php/topic,53348.0.html noting in particular PascalDragon's comments.
I see now that what you asked in that post is basically the same question I'm asking in this post.  I have to admit that because Python was mentioned, I only glanced at the question.

PascalDragon's comments make it clear that there is no compiler support for creating a function in Pascal that works like a C function.

At least, now the situation is clear in my mind.

Thank you.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

MarkMLl

  • Hero Member
  • *****
  • Posts: 2699
Re: varargs in function/procedure definitions
« Reply #7 on: April 16, 2021, 03:19:33 pm »
In my case I'd have been happy enough with  array of const  or whatever, but since FPC can't handle the same stackframe format it turned into more trouble than it was worth. I worked round it by insisting that vararg library routines had to be initialised explicitly, if it were guaranteed that a solution would be portable it might have merited more work.

Note that I'm not saying "this must be fixed..." or anything like that: broadly speaking things work fine and it would be unreasonable to expect a niche requirement to mandate some fairly major code generation surgery.

MarkMLl
Turbo Pascal v1 on CCP/M-86, multitasking with LAN and graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

440bx

  • Hero Member
  • *****
  • Posts: 2362
Re: varargs in function/procedure definitions
« Reply #8 on: April 16, 2021, 03:32:57 pm »
Note that I'm not saying "this must be fixed..." or anything like that: broadly speaking things work fine and it would be unreasonable to expect a niche requirement to mandate some fairly major code generation surgery.
I understand and I won't ask for any changes or additions in the compiler to make it possible.

Thank you for making the issues clear.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

nanobit

  • Full Member
  • ***
  • Posts: 109
Re: varargs in function/procedure definitions
« Reply #9 on: April 16, 2021, 04:46:13 pm »
The missing piece is the conversion from array-of-constant (of TvarRec) to varargs.
Varargs is not really part of Pascal (typed), but only for interfacing (calling) external routines.

One could also call a foreign function interface (FFI, like www.dyncall.org) which wraps the varargs call.
Pro: The Pascal side is more high level (typed and no asm).
Con: Requires much work (refactor) and adds a dependency.

MarkMLl

  • Hero Member
  • *****
  • Posts: 2699
Re: varargs in function/procedure definitions
« Reply #10 on: April 16, 2021, 04:50:06 pm »
@nanobit I know, and as I said in the other thread I was working on something to automate the job.

MarkMLl
Turbo Pascal v1 on CCP/M-86, multitasking with LAN and graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018