Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2012-06

Header translation, argument list, array of const?

6 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Bernd

Hello, I need to call the following function:

/**
* Notifies Purple that a buddy's status has been activated. *
* This is meant to be called from protocol plugins. *
* @param account The account the user is on.
* @param name The name of the buddy.
* @param status_id The status ID.
* @param ... A NULL-terminated list of attribute IDs and values,
* beginning with the value for @a attr_id. */
void purple_prpl_got_user_status(PurpleAccount *account, const char *name,

							   const char *status_id, ...) G_GNUC_NULL_TERMINATED;

I have translated it as follows:

procedure purple_prpl_got_user_status(account: PPurpleAccount;
  name_, status_id: PChar; par3: array of const); cdecl; external LIBPURPLE;

and I call it with [] as the last parameter because I never need it anyways. This works in 50% of all cases, when it works it works always, when I crashes (on x64 for example) then it crashes always. When I slightly move around the function call, add some debug output before and after the call so that the stack is layed out differently (different garbage on the stack) then it will start working where it previously crashed or crash where it previously worked.

How is this such a list of arguments actually implemented? is it on the stack? What structure does it have? Can I somehow manually put stuff onto the stack to make it think it is an empty list? maybe pass another integer and pass 0 instead of this list?

Bernd

Bernd
to Bernd

2012/6/29 Bernd :

and I call it with [] as the last parameter

should I leave it as it is and simply pass [nil] to it to make a null-terminated list?

kyan
to Bernd: Hello, I need to call the following function: /** * Notifies Purple that a buddy's status…
Bernd wrote 22 lines
Hello, I need to call the following function: /**  * Notifies Purple that a buddy's status has been activated.  *  * This is meant to be called from protocol plugins.  *  * @param account   The account the user is on.  * @param name      The name of the buddy.  * @param status_id The status ID.  * @param ...       A NULL-terminated list of attribute IDs and values,  *                  beginning with the value for @a attr_id.  */ void purple_prpl_got_user_status(PurpleAccount *account, const char *name,                                                           const char *status_id, ...) G_GNUC_NULL_TERMINATED; I have translated it as follows: procedure purple_prpl_got_user_status(account: PPurpleAccount;  name_, status_id: PChar; par3: array of const); cdecl; external LIBPURPLE;

That would be:

procedure purple_prpl_got_user_status(account: PPurpleAccount;
 name_, status_id: PChar); varargs; cdecl; external LIBPURPLE;

"varargs" is a compiler directive that reflects the ellipsis "..." in C. It is only valid in external function declarations for static linking (as in your case) or function prototypes for dynamic linking using LoadLibrary()/GetProcAddress(). It is always combined with cdecl (read on) and you cannot implement a varargs cdecl function (you get a compiler error if you try), only declare a prototype and link to an external one written in C.

I am not 100% sure FPC supports the varargs directive but I've found at least one varargs cdecl function (gtk_tree_path_new_from_indices in gtk2extrah.inc), so it probably does.

How is this such a list of arguments actually implemented? is it on the stack? What structure does it have? Can I somehow manually put stuff onto the stack to make it think it is an empty list? maybe pass another integer and pass 0 instead of this list?

I am no C programmer but as I understand it the caller can pass as many variable parameters of any type (actually it has to pass what the function expects it to). The parameters are pushed into the stack after fixed parameters and the callee (function) will retrieve them out of the stack with pointer arithmetic using the address past the end of the last fixed parameter (in your case status_id) as a starting point and increment the running pointer according to every expected variable parameter's type size. So the number and type of the variable parameters must be known and respected by both caller and function and is usually deduced by the values of the fixed parameters. A well known such function with variable parameters is printf() where the type and number of variable parameters is deduced by the placeholders in the format string passed as the fixed parameter. In your case I think that the value of the status_id parameter dictates the number and type of the extra parameters. You'll have to read the documentation of the function.

Stack cleanup is possible although the parameter stack has an unknown size at compile time because of the cdecl calling convention where the caller must allocate AND cleanup the stack and the caller knows the number and types of the variable params at compile-time, the function doesn't. In other calling conventions where the caller allocates the stack but the function has to clean it up (e.g. pascal/stdcall) the ellipsis/varargs cannot work.

A pascal open array of const which is the closest concept to a C ellipsis may have similar semantics but its binary representation on the parameter stack is completely different. First of all, since it has to work in all calling conventions and not just cdecl the high bound of passed array elements is pushed in the stack after a pointer to an array of TVarRec structures that each contains a type attribute and either a value or a pointer to the value of the parameter. So the open array doesn't need to be the last parameter like the C ellipsis and there can be more than one in a routine signature. So your declaration of purple_prpl_got_user_status

procedure purple_prpl_got_user_status(account: PPurpleAccount;
  name_, status_id: PChar;
  par3: array of const);

is equivalent to:

procedure purple_prpl_got_user_status(account: PPurpleAccount;
  name_, status_id: PChar;
  par3: PVarRecArray; par3HighBound: Integer);

So the caller pushes only a pointer to a TVarRec array and an integer while the function expects a continuous "stream" of parameters. If you pass [] the compiler generates code to push a nil pointer (zero PtrInt) and a high bound of -1. If the C function can handle these values on the stack it *sort of* works by accident, but it will probably fail if it tries to read past the -1 high bound for a specific value of status_id that requires more variable parameters.

For details on open array parameters read this article: http://rvelthuis.de/articles/articles-openarr.html

(Disclaimer: the description of open arrays apply to Delphi but my guess is that the implementation in FPC is similar).

HTH

Constantine.

Sven Barth
"kyan" wrote 28 lines
A pascal open array of const which is the closest concept to a C ellipsis may have similar semantics but its binary representation on the parameter stack is completely different. First of all, since it has to work in all calling conventions and not just cdecl the high bound of passed array elements is pushed in the stack after a pointer to an array of TVarRec structures that each contains a type attribute and either a value or a pointer to the value of the parameter. So the open array doesn't need to be the last parameter like the C ellipsis and there can be more than one in a routine signature. So your declaration of purple_prpl_got_user_status procedure purple_prpl_got_user_status(account: PPurpleAccount; name_, status_id: PChar; par3: array of const); is equivalent to: procedure purple_prpl_got_user_status(account: PPurpleAccount; name_, status_id: PChar; par3: PVarRecArray; par3HighBound: Integer); So the caller pushes only a pointer to a TVarRec array and an integer while the function expects a continuous "stream" of parameters. If you pass [] the compiler generates code to push a nil pointer (zero PtrInt) and a high bound of -1. If the C function can handle these values on the stack it *sort of* works by accident, but it will probably fail if it tries to read past the -1 high bound for a specific value of status_id that requires more variable parameters.

Your explanation is very nice and mostly correct, but in case of external cdecl functions in FPC the "varargs" modifier and a "array of const" parameter imply the same C compatible parameter passing in the compiler. See also here:
http://freepascal.org/docs-html/ref/refsu77.html#x180-19000014.9.17

The only difference is when calling the functions. E.g.:

procedure a(args: array of const); cdecl; external;
procedure b; cdecl; varargs; external;
begin
  a([1, 'foo', somevar]);
  b(1, 'foo', somevar);
end.

Regards,
Sven

Bernd
to Sven Barth

2012/6/30 Sven Barth :

Show quoted text 15 lines
Your explanation is very nice and mostly correct, but in case of external cdecl functions in FPC the "varargs" modifier and a "array of const" parameter imply the same C compatible parameter passing in the compiler. See also here: http://freepascal.org/docs-html/ref/refsu77.html#x180-19000014.9.17 The only difference is when calling the functions. E.g.: procedure a(args: array of const); cdecl; external; procedure b; cdecl; varargs; external; begin   a([1, 'foo', somevar]);   b(1, 'foo', somevar); end.

This means in my case I declared the external function correctly and only need to pass
[nil]
to make it a null terminated list? Because I tried this last night and looked at the generated assembler and if I for example call it with

  purple_prpl_got_user_status(@self, Pointer(AName),
Pointer(AStatusID), [1,2,3,nil]);

then it will generate the following code:

0162EF17 6a00 push $0x0
0162EF19 6a03 push $0x3
0162EF1B 6a02 push $0x2
0162EF1D 6a01 push $0x1
0162EF1F ff75f8 pushl -0x8(%ebp) 0162EF22 ff75fc pushl -0x4(%ebp) 0162EF25 8b45f4 mov -0xc(%ebp),%eax 0162EF28 50 push %eax
0162EF29 e8b20afaff call 0x15cf9e0

so its just just pushing the contents of the list onto the stack as they are and If I pass [] there would be nothing pushed at all, so the right thing for me to do in this case would be to call it with

  purple_prpl_got_user_status(@self, Pointer(AName), Pointer(AStatusID), [nil]);

0162EF17 6a00 push $0x0
0162EF19 ff75f8 pushl -0x8(%ebp) 0162EF1C ff75fc pushl -0x4(%ebp) 0162EF1F 8b45f4 mov -0xc(%ebp),%eax 0162EF22 50 push %eax
0162EF23 e8b80afaff call 0x15cf9e0

so that it will push exactly one zero onto the stack, so it gets what it wants (an empty null-terminated list)? Then my only mistake was to assume that [] would automatically generate a null terminated list (for automagic[TM] C-friendlyness) just like strings do?

Sven Barth
to Bernd
"Bernd" wrote
2012/6/30 Sven Barth : > Your explanation is very nice and mostly correct, but in case of

external

> cdecl functions in FPC the "varargs" modifier and a "array of const" > parameter imply the same C compatible parameter passing in the

compiler. See

Show quoted text 40 lines
> also here: > http://freepascal.org/docs-html/ref/refsu77.html#x180-19000014.9.17 > > The only difference is when calling the functions. E.g.: > > procedure a(args: array of const); cdecl; external; > procedure b; cdecl; varargs; external; > > begin > a([1, 'foo', somevar]); > b(1, 'foo', somevar); > end. This means in my case I declared the external function correctly and only need to pass [nil] to make it a null terminated list? Because I tried this last night and looked at the generated assembler and if I for example call it with purple_prpl_got_user_status(@self, Pointer(AName), Pointer(AStatusID), [1,2,3,nil]); then it will generate the following code: 0162EF17 6a00 push $0x0 0162EF19 6a03 push $0x3 0162EF1B 6a02 push $0x2 0162EF1D 6a01 push $0x1 0162EF1F ff75f8 pushl -0x8(%ebp) 0162EF22 ff75fc pushl -0x4(%ebp) 0162EF25 8b45f4 mov -0xc(%ebp),%eax 0162EF28 50 push %eax 0162EF29 e8b20afaff call 0x15cf9e0 so its just just pushing the contents of the list onto the stack as they are and If I pass [] there would be nothing pushed at all, so the right thing for me to do in this case would be to call it with purple_prpl_got_user_status(@self, Pointer(AName), Pointer(AStatusID),
[nil]);
Show quoted text 13 lines
0162EF17 6a00 push $0x0 0162EF19 ff75f8 pushl -0x8(%ebp) 0162EF1C ff75fc pushl -0x4(%ebp) 0162EF1F 8b45f4 mov -0xc(%ebp),%eax 0162EF22 50 push %eax 0162EF23 e8b80afaff call 0x15cf9e0 so that it will push exactly one zero onto the stack, so it gets what it wants (an empty null-terminated list)? Then my only mistake was to assume that [] would automatically generate a null terminated list (for automagic[TM] C-friendlyness) just like strings do?

I have now looked at your mail again. Normally you don't need to explicitly pass a "Nil" (whatever of the earlier mentioned variants you chose) as e.g. for printf the following works:

printf("%d %d %d", 1, 2, 3);

or

printf("%d %d %d", [1, 2, 3]);

(depending on the declaration)

But the comment above the original C function you mentioned in your initial mail explicitly states that you must pass a Null-terminated list, which the compiler doesn't do by itself. Maybe that is told to GCC by the G_GNUC_NULL_TERMINATED define (whatever it resolves to...)

Nevertheless passing a Nil at the end should be the correct approach in your specific case.

Regards,
Sven