How to create functions that can accept variable number of parameters such as Format

http://www.chami.com/tips/delphi/112696D.html

Sometimes it's necessary to pass undefined number of [different type] variables to a function

-- look at Format() function in Delphi and *printf() functions in C/C++ for example.

Once you analyze the following code, you'll be on your way to creating mysterious variable parameter functions...

复制代码
//
// FunctionWithVarArgs()
//
// skeleton for a function that
// can accept vairable number of
// multi-type variables
//
// here are some examples on how
// to call this function:
//
// FunctionWithVarArgs(
//   [ 1, True, 3, '5', '0' ] );
//
// FunctionWithVarArgs(
//   [ 'one', 5 ] );
//
// FunctionWithVarArgs( [] );
//
procedure FunctionWithVarArgs(
  const ArgsList : array of const );
var
  ArgsListTyped :
    array[0..$FFF0 div SizeOf(TVarRec)]
      of TVarRec absolute ArgsList;
  n         : integer;
begin
  for n := Low( ArgsList ) to
           High( ArgsList ) do
  begin
    with ArgsListTyped[ n ] do
    begin
      case VType of
        vtInteger   : begin
          {handle VInteger here}      end;
        vtBoolean   : begin
          {handle VBoolean here}      end;
        vtChar      : begin
          {handle VChar here}         end;
        vtExtended  : begin
          {handle VExtended here}     end;
        vtString    : begin
          {handle VString here}       end;
        vtPointer   : begin
          {handle VPointer here}      end;
        vtPChar     : begin
          {handle VPChar here}        end;
        vtObject    : begin
          {handle VObject here}       end;
        vtClass     : begin
          {handle VClass here}        end;
        vtWideChar  : begin
          {handle VWideChar here}     end;
        vtPWideChar : begin
          {handle VPWideChar here}    end;
        vtAnsiString: begin
          {handle VAnsiString here}   end;
        vtCurrency  : begin
          {handle VCurrency here}     end;
        vtVariant   : begin
          {handle VVariant here}      end;
        else          begin
          {handle unknown type here} end;
      end;
    end;
  end;
end;

//
// example function created using
// the above skeleton
//
// AddNumbers() will return the
// sum of all the integers passed
// to it
//
// AddNumbers( [1, 2, 3] )
//   will return 6
//
//
function AddNumbers(
  const ArgsList : array of const )
    : integer;
var
  ArgsListTyped :
    array[0..$FFF0 div SizeOf(TVarRec)]
      of TVarRec absolute ArgsList;
  n         : integer;
begin
  Result := 0;
  for n := Low( ArgsList ) to
           High( ArgsList ) do
  begin
    with ArgsListTyped[ n ] do
    begin
      case VType of
        vtInteger   : Result := Result + VInteger;
      end;
    end;
  end;
end;
复制代码

http://stackoverflow.com/questions/8281436/how-can-i-create-a-function-with-an-arbitrary-number-of-parameters

I want to create a function that receive multiples strings as parameters. Like the function printf("Hello %s",name); of C. but I don't want to pass a ready array, it wouldn't be readable.

Edit1.text:=lang('Hello');

Edit2.text:=lang('Welcome to {1} guest',place);

Edit3.text:=lang('Hi {1}, is your {2} time in {3}','Victor','first','Disney');

output should be:

Hello
Welcome to Disney guest
Hi Victor is your first time in Disney

how I create the function TForm1.lang(parameters:String):String;, I did a research, but I can't get it work.

I need to access the parameters[] and the parameters.length also.

I'm needing this to turn my App to multilang.

Here's an example function of how you can do this:

复制代码
function TForm1.lang(s: String; params: array of String): String;
var
  i: Integer;
begin
  for i := 0 to High(params) do
  begin
    ShowMessage(params[i]);
  end;
end;
复制代码

 

Call it like this:

lang('My format string', ['this', 'that']);

or like this:

var
  b: String;
begin
  b := 'this';
  lang('My format string', [b, 'that']);
end;

 

 

复制代码
procedure DoSomething(args : Array of String);
Var
  Index : Integer;
Begin
  for index := Low(args) to High(args) Do
    ShowMessage(args[Index]);
End;

DoSomething(['Param1','Param2']);
复制代码

 

 

How can a function with 'varargs' retrieve the contents of the stack?

Normally, in Delphi one would declare a function with a variable number of arguments using the 'array of const' method.

However, for compatibility with code written in C, there's an much-unknown 'varargs' directive

that can be added to a function declaration (I learned this while reading Rudy's excellent 'Pitfalls of convering' document).

As an example, one could have a function in C, declared like this :

void printf(const char *fmt, ...)

In Delphi, this would become :

procedure printf(const fmt: PChar); varargs;

My question is :

How can I get to the contents of the stack

when implementing a method which is defined with the 'varargs' directive?

I would expect that some tooling for this exists,

like Dephi translations of the va_start(), va_arg() and va_end() functions,

but I can't find this anywhere. Please help!

PS: Please don't drift off in discussions about the 'why'

or the 'array of const' alternative -

I need this to write C-like patches for functions inside Xbox games

(see the Delphi Xbox emulator project 'Dxbx' on sourceforge for details).

 

OK, I see the clarification in your question to mean that you need to implement a C import in Delphi.

In that case, you need to implement varargs yourself.

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.

复制代码
{$apptype console}

type  
  TArgPtr = record
  private
    FArgPtr: PByte;
    class function Align(Ptr: Pointer; Align: Integer): Pointer; static;
  public
    constructor Create(LastArg: Pointer; Size: Integer);
    // Read bytes, signed words etc. using Int32
    // Make an unsigned version if necessary.
    function ReadInt32: Integer;
    // Exact floating-point semantics depend on C compiler.
    // Delphi compiler passes Extended as 10-byte float; most C
    // compilers pass all floating-point values as 8-byte floats.
    function ReadDouble: Double;
    function ReadExtended: Extended;
    function ReadPChar: PChar;
    procedure ReadArg(var Arg; Size: Integer);
  end;

constructor TArgPtr.Create(LastArg: Pointer; Size: Integer);
begin
  FArgPtr := LastArg;
  // 32-bit x86 stack is generally 4-byte aligned
  FArgPtr := Align(FArgPtr + Size, 4);
end;

class function TArgPtr.Align(Ptr: Pointer; Align: Integer): Pointer;
begin
  Integer(Result) := (Integer(Ptr) + Align - 1) and not (Align - 1);
end;

function TArgPtr.ReadInt32: Integer;
begin
  ReadArg(Result, SizeOf(Integer));
end;

function TArgPtr.ReadDouble: Double;
begin
  ReadArg(Result, SizeOf(Double));
end;

function TArgPtr.ReadExtended: Extended;
begin
  ReadArg(Result, SizeOf(Extended));
end;

function TArgPtr.ReadPChar: PChar;
begin
  ReadArg(Result, SizeOf(PChar));
end;

procedure TArgPtr.ReadArg(var Arg; Size: Integer);
begin
  Move(FArgPtr^, Arg, Size);
  FArgPtr := Align(FArgPtr + Size, 4);
end;

procedure Dump(const types: string); cdecl;
var
  ap: TArgPtr;
  cp: PChar;
begin
  cp := PChar(types);
  ap := TArgPtr.Create(@types, SizeOf(string));
  while True do
  begin
    case cp^ of
      #0: 
      begin
        Writeln;
        Exit;
      end;

      'i': Write(ap.ReadInt32, ' ');
      'd': Write(ap.ReadDouble, ' ');
      'e': Write(ap.ReadExtended, ' ');
      's': Write(ap.ReadPChar, ' ');
    else
      Writeln('Unknown format');
      Exit;
    end;
    Inc(cp);
  end;
end;

type
  PDump = procedure(const types: string) cdecl varargs;
var
  MyDump: PDump;

function AsDouble(e: Extended): Double;
begin
  Result := e;
end;

function AsSingle(e: Extended): Single;
begin
  Result := e;
end;

procedure Go;
begin
  MyDump := @Dump;

  MyDump('iii', 10, 20, 30);
  MyDump('sss', 'foo', 'bar', 'baz');

  // Looks like Delphi passes Extended in byte-aligned
  // stack offset, very strange; thus this doesn't work.
  MyDump('e', 2.0);
  // These two are more reliable.
  MyDump('d', AsDouble(2));
  // Singles passed as 8-byte floats.
  MyDump('d', AsSingle(2));
end;

begin
  Go;
end.
复制代码

 

 

pass unlimited number of paramters to procedure

in Delphi the procedure write can handle:

write(TF,st1)

and

write(TF,st1,st2,st3,st4);

I want to declare a procedure that can also do that, what is the syntax?

and the option of:

write(TF,[st1,st2,st3])

is less desirable, thow i know how to do that.

the main purpose was to pass ShortStrings into function, that would make a read call from file,

and would read at the length of the shortString as defined. however after passing it

as variant or in open array the shortstring loses its "size" and become 255,

which making this pass unusable, for me.

but the answer is still got if you want to pass open array.

First of all Inc and Write are bad examples because they both get special treatment from the compiler.

You can't write a function that behaves exactly like those two do yourself. There are alternatives you should investigate.

Take a look at overloads

You can create multiple versions of your method using varying number of parameters, and varying types. Something like this:

procedure MyInc(var i:Integer); overload;
procedyre MyInc(var i:Integer; const N:Integer); overload;
procedure MyInc(var i:Integer; const N1, N2: Integer); overload;
procedure MyInc(var i:Integer; const N1, N2, N3: Integer):overload;

This is feasible if the required number of overloads is not that large.

The compiler would probably handle lots of overloads easily,

but you'd probably not want to write them.

When the number of overloads becomes a problem you can switch to arrays:

Using Open Arrays as parameters

A function can take a parameter of type array of YourType,

and when you call that function you can pass as many parameters as you might need:

procedure MyInc(var i:Integer; Vals: array of Integer);

 

And then use it like this:

MyInc(i, []); // no parameters
MyInc(i, [1]);
MyInc(i, [1, 34, 43, 12]);

Just to complement Cosmin's answer: if the list of parameters are of different types,

you could use an variant open array parameter (also know as "array of const"). 

Example (from documentation):

 

复制代码
function MakeStr(const Args: array of const): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(Args) do
     with Args[I] do
        case VType of
            vtInteger:  Result := Result + IntToStr(VInteger);
            vtBoolean:  Result := Result + BoolToStr(VBoolean);
            vtChar:     Result := Result + VChar;
            vtExtended: Result := Result + FloatToStr(VExtended^);
            vtString:   Result := Result + VString^;
            vtPChar:    Result := Result + VPChar;
            vtObject:   Result := Result + VObject.ClassName;
            vtClass:    Result := Result + VClass.ClassName;
            vtAnsiString:  Result := Result + string(VAnsiString);
            vtCurrency:    Result := Result + CurrToStr(VCurrency^);
            vtVariant:     Result := Result + string(VVariant^);
            vtInt64:       Result := Result + IntToStr(VInt64^);
  end;
end;
复制代码

For ilustrative purposes only:

Delphi supports a way of writing "real" variable arguments functions,

but it is really cumbersome and intended for use mainly for

declaring external C functions with variable arguments like printf,

as it involves playing some low-level dirty tricks for accessing the arguments in the stack.

It involves using cdecl and varargs modifiers:

复制代码
procedure MyWrite_; cdecl;
begin
  ... some magic here ...
end;

var
  MyWrite: procedure; cdecl varargs = MyWrite_;

begin
  MyWrite(1);
  MyWrite(1, 2);
  MyWrite(1, 2, 3);
end;
复制代码

 

posted @   IAmAProgrammer  阅读(1146)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
历史上的今天:
2013-10-24 Read UNIQUE ID and flash size method for stm32
2013-10-24 这些英文千万别不懂装懂
2012-10-24 STM32 -- Multiple USB CDC (USB IAD)
2012-10-24 C/C++ Editor编辑器 Syntax Coloring 语法着色
点击右上角即可分享
微信分享提示