Recent

Author Topic: [Solved] Is it possible to const an argument passed by reference in FPC?  (Read 16496 times)

Zoran

  • Hero Member
  • *****
  • Posts: 1830
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #45 on: July 29, 2019, 10:17:15 am »
Thus if you have a void* x parameter in C/C++ it's perfectly valid to use const X in FPC/Delphi as long as it's not necessary for the parameter to be NULL/Nil.

Now I'm puzzled.

Did you actually mean it's valid to use var / constref (not const, as far as FPC is concerned) instead of pointer (if the C function expects pointer and you know you will never need to pass nil)?
But, unlike Delphi, if not declared with stdcall, FPC might decide to pass const by value, and the C function will get garbage and probably crash?
« Last Edit: July 29, 2019, 10:21:04 am by Zoran »

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #46 on: July 29, 2019, 12:20:49 pm »
Thus if you have a void* x parameter in C/C++ it's perfectly valid to use const X in FPC/Delphi as long as it's not necessary for the parameter to be NULL/Nil.

Now I'm puzzled.

Did you actually mean it's valid to use var / constref (not const, as far as FPC is concerned) instead of pointer (if the C function expects pointer and you know you will never need to pass nil)?
it is correct to specify "var" or "constref" for a parameter that is a pointer as long as the parameter cannot be nil.  "var" and "constref" pass the address of the parameter.

What he is failing to see, is that the definition of WriteFile taking an untyped parameter is incorrect and NOT equivalent to using "var" or "constref".  Specifically, if WriteFile was defined as follows:

Code: Pascal  [Select][+][-]
  1. type
  2.   TBUFFER = packed array[0..0] of byte;
  3.   PBUFFER = ^TBUFFER;
  4.  
  5. // now presume a "Buffer" variable of type PBUFFER (note the P, Buffer is a pointer to a TBUFFER)
  6. // has been allocated (using HeapAlloc or whatever other memory allocation function)
  7.  
  8. var
  9.   Buffer : PBUFFER;  // pointer to a dynamically allocated memory block
  10.  
  11. function WriteFile(const    FileHandle           : THANDLE;
  12.                      var    DataInBuffer         : TBUFFER;     // "var" or "constref", makes no difference in this case.
  13.                             NumberOfBytesToWrite : DWORD;
  14.                      out    NumberOfBytesWritten : DWORD;
  15.                             Overlapped           : POverlapped)
  16.          : BOOL; stdcall; external 'kernel32';
  17.  
That would work.  The function could be invoked by dereferencing Buffer, (Buffer^ for the parameter DataInBuffer).  Failing to dereference Buffer would cause a compile error because Buffer is a PBUFFER while the API, as defined above requires a TBUFFER, _not_ a PBUFFER (because the parameter is declared as "var".)

when the API is defined as it is currently, it's a dismally incorrect _mess_ (current definition):
Code: Pascal  [Select][+][-]
  1. function WriteFile(         FileHandle           : THANDLE;
  2.                    const    DataInBuffer;        // NOTE: untyped
  3.                             NumberOfBytesToWrite : DWORD;
  4.                      var    NumberOfBytesWritten : DWORD;
  5.                             Overlapped           : POverlapped)
  6.          : BOOL; stdcall; external 'kernel32';
  7.  
What happens is, when you pass "Buffer", the compiler takes the address of "Buffer" (because it is untyped) and passes the address of the pointer to the function instead of the pointer itself and, since the parameter is untyped, the compiler doesn't have a clue that what should be passed is a pointer to a TBUFFER.  No error, no warning, no nothing.

Added the following lines to the original post:
The problem is the use of an untyped parameter, if the parameter had been typed (as is absolutely required in C) then it would be possible to define the API function correctly, either with "var" or "constref".
end of additions



That definition is dismally incorrect.

« Last Edit: July 29, 2019, 12:42:44 pm by 440bx »
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5462
  • Compiler Developer
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #47 on: July 30, 2019, 09:38:19 am »
Thus if you have a void* x parameter in C/C++ it's perfectly valid to use const X in FPC/Delphi as long as it's not necessary for the parameter to be NULL/Nil.

Now I'm puzzled.

Did you actually mean it's valid to use var / constref (not const, as far as FPC is concerned) instead of pointer (if the C function expects pointer and you know you will never need to pass nil)?
But, unlike Delphi, if not declared with stdcall, FPC might decide to pass const by value, and the C function will get garbage and probably crash?
Please note that I was talking about untyped parameters here. But yes, for typed parameters var, out and constref can be used as well and it's often done for various WinAPI functions (e.g. for the "returned size" parameters).

when the API is defined as it is currently, it's a dismally incorrect _mess_ (current definition):
Code: Pascal  [Select][+][-]
  1. function WriteFile(         FileHandle           : THANDLE;
  2.                    const    DataInBuffer;        // NOTE: untyped
  3.                             NumberOfBytesToWrite : DWORD;
  4.                      var    NumberOfBytesWritten : DWORD;
  5.                             Overlapped           : POverlapped)
  6.          : BOOL; stdcall; external 'kernel32';
  7.  
What happens is, when you pass "Buffer", the compiler takes the address of "Buffer" (because it is untyped) and passes the address of the pointer to the function instead of the pointer itself and, since the parameter is untyped, the compiler doesn't have a clue that what should be passed is a pointer to a TBUFFER.  No error, no warning, no nothing.
What you don't get is that you pass Buffer incorrectly. You need to pass it as Buffer^. You don't pass Buffer either to FillChar if you want it's contents zeroed, right?

Look at this (this example is from Linux, but it would work similar on Windows):

Code: C  [Select][+][-]
  1. // save as abitest.c
  2. #include <stdio.h>
  3.  
  4. void test(void* arg)
  5. {
  6.         printf("%p\n", arg);
  7. }

Code: Pascal  [Select][+][-]
  1. // save as tabitest.pas
  2. program tabitest;
  3.  
  4. {$l abitest.o}
  5. {$linklib c}
  6.  
  7. procedure test(const arg); cdecl; external;
  8.  
  9. procedure Test2(const arg);
  10. begin
  11.   Writeln(HexStr(@arg));
  12. end;
  13.  
  14. var
  15.   i: LongInt;
  16.   p: PLongInt;
  17. begin
  18.   Writeln('@i: ', HexStr(@i));
  19.   Writeln('@p: ', HexStr(@p));
  20.  
  21.   p := @i;
  22.  
  23.   Writeln('Test:');
  24.   Test(i);
  25.   Test(p);
  26.   Test(p^);
  27.  
  28.   Writeln('Test2:');
  29.   Test2(i);
  30.   Test2(p);
  31.   Test2(p^);
  32. end.

Execute the following:
Code: [Select]
gcc -c abitest.o abitest.c
fpc tabitest.pas
./tabitest

The output will be something like this:
Code: [Select]
@i: 00000000006268C0
@p: 00000000006268C8
Test:
0x6268c0
0x6268c8
0x6268c0
Test2:
00000000006268C0
00000000006268C8
00000000006268C0

As you can see the C code receives the same pointer values as the Pascal code and can work with the parameter just as usual. The only thing to keep in mind is that you must dereference a pointer if you want it passed correctly. This is by design and intended to work this way.

The declaration of WriteFile is not any different here. Untyped parameters work there.

And further proof for you: WriteFile and ReadFile with their untyped parameters are the core functions used for the SysUtils File* functions and THandleStream. You really think something like the data parameter not working correctly wouldn't have been noticed by now? Nearly 15 years later? (The code in FileRead/FileWrite is this way since at least the first SVN revision which was in May '05)

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #48 on: July 30, 2019, 09:51:51 am »
What you don't get is that you pass Buffer incorrectly. You need to pass it as Buffer^. You don't pass Buffer either to FillChar if you want it's contents zeroed, right?
And what you obviously don't want to get is that the definition of WriteFile specifies that what must be passed is a pointer to the buffer. 

If the definition was correct, it would _not_ be necessary to dereference the Buffer variable (it isn't in C, is it ?.)  RIP.

And further proof for you:
I'm very pleased you mentioned "further proof" because further proof is in the definition of WriteFileEx which is defined correctly and, as I stated previously, if a programmer simply changed WriteFile to WriteFileEx and added a pointer to a completion routine, the call would fail.  That is, as you called it, "further proof".

You really think something like the data parameter not working correctly wouldn't have been noticed by now? Nearly 15 years later? (The code in FileRead/FileWrite is this way since at least the first SVN revision which was in May '05)
No, that is not what I think.  What I know is that the definition of WriteFile is incorrect.  I think it's likely used in quite a few places which is the reason it cannot be corrected, correcting it would likely break quite a bit of code.  That causes the parameters WriteFile has in common with WriteFileEx to be passed differently, when they shouldn't, since their definitions are identical.

Nice try though.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5462
  • Compiler Developer
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #49 on: July 31, 2019, 09:48:39 am »
I give up. I have better things to do than spent time arguing with you. The declaration of WriteFile and ReadFile will stay that way, because it works correctly. Only because you're not able to see this or use it correctly doesn't change that.

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #50 on: July 31, 2019, 11:31:03 am »
I give up.
good idea, particularly when you're wrong.

The declaration of WriteFile and ReadFile will stay that way, because it works correctly. Only because you're not able to see this or use it correctly doesn't change that.
Just because you can make a wrong definition work, it doesn't mean it's right and, you know that but, while you do see it, you don't want to acknowledge it.

I know the declaration will stay that way, we both know why too.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 14363
  • Sensorship about opinions does not belong here.
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #51 on: July 31, 2019, 12:54:20 pm »
Well, you are too cocky and can't back it up with knowledge in all cases. (granted, you are not ill-informed inmost cases)
PascalDragon even gave you verifiable proof. You did not. You make assumptions.
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #52 on: July 31, 2019, 01:15:01 pm »
Well, you are too cocky and can't back it up with knowledge in all cases. (granted, you are not ill-informed inmost cases)
PascalDragon even gave you verifiable proof. You did not. You make assumptions.
Really !?... is the fact that WriteFile cannot be changed into WriteFileEx without changing how the parameters are passed an "assumption" ?

Is it also an assumption that parameters in C are always typed, which means that all Windows, C defined, API function parameters are typed, therefore should be typed in any other language (including Pascal) ?  Just the fact that the parameter is untyped makes the definition wrong, it is not semantically equivalent to the original definition.  You'd know that, if you used WriteFile and WriteFileEx.

That's what you and PascalDragon are desperately trying to slide under the rug.  Just because you can make it work and, inappropriately accuse programmers that pass the parameter correctly of writing "wrong code" does not make that definition right.

As I suggested to you previously, follow your own advice.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5462
  • Compiler Developer
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #53 on: August 01, 2019, 10:08:59 am »
Well, you are too cocky and can't back it up with knowledge in all cases. (granted, you are not ill-informed inmost cases)
PascalDragon even gave you verifiable proof. You did not. You make assumptions.
Really !?... is the fact that WriteFile cannot be changed into WriteFileEx without changing how the parameters are passed an "assumption" ?
Oh shocker, I changed the function I call and now I need to check whether my parameters are still correct... One should check those anyway, I've been bitten often enough in the past when changing a function call to a different, but similar one, that the meaning of some argument changed and I'm not talking about untyped vs. Pointer here, but completely unrelated stuff.

Is it also an assumption that parameters in C are always typed, which means that all Windows, C defined, API function parameters are typed, therefore should be typed in any other language (including Pascal) ?  Just the fact that the parameter is untyped makes the definition wrong, it is not semantically equivalent to the original definition.  You'd know that, if you used WriteFile and WriteFileEx.
It might not be semantically the same, but regarding the functionality (and the ABI) it is.

A WriteFileEx with a untyped parameter would work just as well:
Code: Pascal  [Select][+][-]
  1. program twritetest;
  2.  
  3. {$mode objfpc}
  4.  
  5. uses
  6.   Windows, sysutils;
  7.  
  8. // explicitely import in both variants
  9. function WriteFileEx(hFile:HANDLE; lpBuffer:LPCVOID; nNumberOfBytesToWrite:DWORD; lpOverlapped:LPOVERLAPPED; lpCompletionRoutine:LPOVERLAPPED_COMPLETION_ROUTINE):WINBOOL; stdcall; external 'kernel32' name 'WriteFileEx';
  10. function WriteFileEx(hFile:HANDLE; const lpBuffer; nNumberOfBytesToWrite:DWORD; lpOverlapped:LPOVERLAPPED; lpCompletionRoutine:LPOVERLAPPED_COMPLETION_ROUTINE):WINBOOL; stdcall; external 'kernel32' name 'WriteFileEx';
  11.  
  12. var
  13.   count: Integer = 0;
  14.  
  15. procedure DoCompletion(_para1:DWORD; _para2:DWORD; _para3:LPOVERLAPPED);stdcall;
  16. begin
  17.   InterLockedIncrement(count);
  18.   if count = 4 then
  19.     SetEvent(_para3^.hEvent);
  20. end;
  21.  
  22. var
  23.   i, j: LongInt;
  24.   p: PLongInt;
  25.   e, h: THandle;
  26.   r: DWord;
  27.   ovl: array[0..3] of OVERLAPPED;
  28. begin
  29.   i := 42;
  30.   p := @i;
  31.  
  32.   FillChar(ovl, SizeOf(ovl), 0);
  33.  
  34.   e := CreateEvent(Nil, FALSE, FALSE, Nil);
  35.   try
  36.     h := CreateFile('test.txt', GENERIC_WRITE or GENERIC_READ, 0, Nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0);
  37.     try
  38.       ovl[0].Offset := 0;
  39.       ovl[0].hEvent := e;
  40.       ovl[1].Offset := SizeOf(i);
  41.       ovl[1].hEvent := e;
  42.       ovl[2].Offset := 2 * SizeOf(i);
  43.       ovl[2].hEvent := e;
  44.       ovl[3].Offset := 3 * SizeOf(i);
  45.       ovl[3].hEvent := e;
  46.  
  47.       if not WriteFileEx(h, @i, SizeOf(i), @ovl[0], @DoCompletion) then begin
  48.         Writeln('Error: ', GetLastError);
  49.         Exit;
  50.       end;
  51.       if not WriteFileEx(h, p, SizeOf(i), @ovl[1], @DoCompletion) then begin
  52.         Writeln('Error: ', GetLastError);
  53.         Exit;
  54.       end;
  55.  
  56.       if not WriteFileEx(h, i, SizeOf(i), @ovl[2], @DoCompletion) then begin
  57.         Writeln('Error: ', GetLastError);
  58.         Exit;
  59.       end;
  60.       if not WriteFileEx(h, p^, SizeOf(i), @ovl[3], @DoCompletion) then begin
  61.         Writeln('Error: ', GetLastError);
  62.         Exit;
  63.       end;
  64.  
  65.       WaitForSingleObjectEx(e, INFINITE, TRUE);
  66.  
  67.       SetFilePointer(h, 0, Nil, FILE_BEGIN);
  68.  
  69.       for j := 0 to 3 do begin
  70.         ReadFile(h, i, SizeOf(i), r, Nil);
  71.         Writeln(i);
  72.       end;
  73.     finally
  74.       CloseHandle(h);
  75.     end;
  76.   finally
  77.     CloseHandle(e);
  78.   end;
  79. end.

This will print:
Code: [Select]
42
42
42
42

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #54 on: August 01, 2019, 11:19:23 am »
Oh shocker, I changed the function I call and now I need to check whether my parameters are still correct... One should check those anyway, I've been bitten often enough in the past when changing a function call to a different, but similar one, that the meaning of some argument changed and I'm not talking about untyped vs. Pointer here, but completely unrelated stuff.
Yes, and the shocker is:
Code: C  [Select][+][-]
  1. BOOL WriteFile(
  2.   HANDLE       hFile,
  3.   LPCVOID      lpBuffer,
  4.   DWORD        nNumberOfBytesToWrite,
  5.   LPDWORD      lpNumberOfBytesWritten,
  6.   LPOVERLAPPED lpOverlapped
  7. );
and
Code: C  [Select][+][-]
  1. BOOL WriteFileEx(
  2.   HANDLE                          hFile,
  3.   LPCVOID                         lpBuffer,
  4.   DWORD                           nNumberOfBytesToWrite,
  5.   LPOVERLAPPED                    lpOverlapped,
  6.   LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  7. );
After checking, the documentation says that all the parameters in common are identical.  Yet, when a programmer changes his Pascal call from WriteFile to WriteFileEx, he/she gets a compiler error (actually, that part is nice) then, the programmer has to figure out why it works in one case and not in another (when they should, since the parameters in common are identical) and finds, not in the documentation but, in the Pascal definitions that WriteFile's parameters are NOT identical to WriteFileEx's parameters.

That's one of the reasons the definition of WriteFile is _incorrect_.  Another reason is, there are no untyped parameters in C.  Using untyped parameters is not semantically equivalent to "void*".

It's really amazing the extent some FPC developers, you among them, go to pretend an incorrect definitions or compiler behavior is "correct".  Very unfortunate.


(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5462
  • Compiler Developer
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #55 on: August 02, 2019, 09:06:36 am »
That's one of the reasons the definition of WriteFile is _incorrect_.  Another reason is, there are no untyped parameters in C.  Using untyped parameters is not semantically equivalent to "void*".

It's really amazing the extent some FPC developers, you among them, go to pretend an incorrect definitions or compiler behavior is "correct".  Very unfortunate.
The declarations are definitely correct (in the sense that they can be used for the same functionality as the C declaration), what they aren't is equivalent to the MSDN documentation.

What I do grant you however is that we don't have the equivalent declaration as an overload. Those should in my opinion definitely be there as those declarations with untyped parameters or typed var parameters are considered "convenience" declarations and thus an "add on".

ikel

  • New Member
  • *
  • Posts: 26
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #56 on: August 02, 2019, 11:55:25 pm »
Hi everyone,

Thanks for replies and for code examples... :D
I thought I got the best practices of using const is passing a reference (like c++'s string  *input). I was wrong, it is more complicated than what I had expected. Up to discussion on COM, const and stdcall, I pretty much got it.

Then discussion went into untyped... I got lost... :o
I will re-read again, making sure I got it, along with code examples, then I will write up a summary.

440bx

  • Hero Member
  • *****
  • Posts: 4014
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #57 on: August 03, 2019, 12:27:00 am »
Then discussion went into untyped... I got lost... :o
I suggest _never_ using untyped parameters. 

An untyped parameter implicitly causes the caller to pass any data type by reference since the parameter has no type.  It's one of those "great ideas" from a compiler writer who inhaled too much leaded red paint fumes before reaching the keyboard.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 14363
  • Sensorship about opinions does not belong here.
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #58 on: August 03, 2019, 08:17:09 am »
Tell that to C api writers too, please  :D But I mostly agree.
Btw I helped introduced OpaquePointer and a TOpaqueData - empty - record to avoid untyped. It is in 3.2.0. The ratio I had was for passing C structures around w/o changing them from Pascal code, although it is also used in the rtl and rtl-generics.

Some of the untyped stuff can be resolved with the above. It is in systemh.inc. Not Delphi compatible, though.
« Last Edit: August 03, 2019, 08:21:14 am by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5462
  • Compiler Developer
Re: Is it possible to const an argument passed by reference in FPC?
« Reply #59 on: August 03, 2019, 10:11:54 am »
Then discussion went into untyped... I got lost... :o
I suggest _never_ using untyped parameters. 

An untyped parameter implicitly causes the caller to pass any data type by reference since the parameter has no type.  It's one of those "great ideas" from a compiler writer who inhaled too much leaded red paint fumes before reaching the keyboard.
*rolls eyes*

 

TinyPortal © 2005-2018