Why does Format reject procedure address arguments starting with XE4 - delphi

Consider this program:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
procedure Foo;
begin
end;
type
TProcedure = procedure;
const
FooConst: TProcedure = Foo;
var
FooVar: TProcedure = Foo;
P: Pointer;
{$TYPEDADDRESS ON}
begin
P := #Foo;
Writeln(Format('%p', [P]));
Writeln(Format('%p', [#FooConst]));
Writeln(Format('%p', [#FooVar]));
Writeln(Format('%p', [#Foo]));
Readln;
end.
This program compiles and runs on XE3 and produces the following output:
00419FB8
00419FB8
00419FB8
00419FB8
On XE4 and later the program fails to compile, with error messages on both of these lines:
Writeln(Format('%p', [#FooConst]));
Writeln(Format('%p', [#FooVar]));
[dcc32 Error] E2250 There is no overloaded version of 'Format' that can be called
with these arguments
On XE4, XE5 and XE6, the program compiles when $TYPEDADDRESS is switched off. On XE7, the program fails to compile irrespective of the setting of $TYPEDADDRESS.
Is this a compiler bug? Or am I using incorrect syntax to obtain the address of a procedure?

I believe that this is a compiler bug and have submitted a QC report: QC#127814.
As a work around you can use either of the following:
Use addr() rather than the # operator.
Cast #FooVar or #FooConst to Pointer, e.g. Pointer(#FooVar).

I think that the behaviour of the new compiler XE7 is more consistent with the specification and the error need to be shown in this case, since the {$TYPEDADDRESS ON} enforce the # operator to return a typed pointer and the Format function instead gets as input an untyped generic pointer.
Since the the purpose of the {$TYPEDADDRESS ON} is encouraging careful use of pointers, catching at compile time unsafe pointer assignments, it is right that if the function expects a generic untyped pointer(and in this case make sense because the function purpose is to print the address of it - so no need to have typed pointer to retrieve its address), the compiler will catch an error in the case of a typed pointer is passed, the behaviour is consistent with the specification.
I think that in this case the right way to go(based on the documentation) would be :
Writeln(Format('%p', [Addr(FooConst)]));
Writeln(Format('%p', [Addr(FooVar)]));
since the Addr function always returns an untyped Pointer that is what the Format with %p expects and needs.
What I assume is that in previous versions the compiler, in a case like this one, used to perform an automatic cast : Pointer(#FooConst) , but it doesn't make too much sense because of the {$TYPEDADDRESS ON} directive .

Related

Delphi 2007->10.1 Berlin Port: Solving E2251 Ambiguious overloaded call to StrLen

Background: Porting my code to Delphi 10.1 Berlin and working through the third party libraries. Some are no longer available so I will try to fix the code...
The following code (passing params from one instance of a program to another) raises E2251 Ambiguious overloaded call to StrLen. I understand why, I just don't know the best way to resolve it.
type
PInstInfo = ^TInstInfo;
TInstInfo = packed record
FirstInstanceWnd:HWND;
ParamCount:Integer;
Params:Array[0..MAX_PARAMS-1, 0..MAX_PARAM_SIZE] of Char;
end;
// Memory is filled with:
lpInfo^.ParamCount:=ParamCount;
if lpInfo^.ParamCount>MAX_PARAMS then
lpInfo^.ParamCount:=MAX_PARAMS;
for i:=0 to lpInfo^.ParamCount-1 do
begin
tempStr:=ParamStr(i+1);
if length(tempStr)>MAX_PARAM_SIZE then
setLength(tempStr,MAX_PARAM_SIZE);
StrCopy(#(lpInfo^.Params[i,0]),PChar(tempStr));
end;
// and notify the first instance
PostMessage(lpInfo^.FirstInstanceWnd, MSG_2ND_INSTANCE, 0, 0);
// And read using:
if lpInfo <> nil then
try
// get Parameters
params:=TStringList.Create;
try
for i:=0 to lpInfo^.ParamCount-1 do
begin
SetString(tempStr,
PChar(#(lpInfo^.Params[i,0])),
StrLen(#(lpInfo^.Params[i,0]))); <--- E2251 Ambiguious overloaded call to StrLen
params.Add(tempStr);
end;
InstanceStarted(params);
finally
params.Free;
end;
Thanks
By default, the # address operator produces an untyped pointer. There are two overloaded versions of StrLen(), one that takes a PAnsiChar and one that takes a PWideChar. An untyped pointer can be passed to both overloads, thus the ambiguity. The PWideChar overload did not exist in Delphi 2007, which is why the code compiled before.
To fix the code, you will have to either:
use the {$TYPEDADDRESS ON} or {$T+} compiler directive to enable Type-checked pointers so taking the address of a Char variable using the # operator will produce a typed PChar pointer instead of an untyped pointer.
{$TYPEDADDRESS ON}
SetString(tempStr,
#(lpInfo^.Params[i,0]),
StrLen(#(lpInfo^.Params[i,0])));
use the same type-cast you use in the 2nd parameter of SetString():
SetString(tempStr,
PChar(#(lpInfo^.Params[i,0])),
StrLen(PChar(#(lpInfo^.Params[i,0]))));
Get rid of the calls to SetString() and StrLen(), since a null-terminated character pointer can be assigned directly to a String variable:
tempStr := PChar(#(lpInfo^.Params[i,0]));
{$TYPEDADDRESS ON}
tempStr := #(lpInfo^.Params[i,0]);
With that said, do be aware that the Char data type changed from Ansi to Unicode in D2009, so this code will only work when sending the parameters to Unicode versions of your app, not to Ansi versions. If you need to continue supporting older versions of your app, you should define a different window message for passing Unicode parameters, and then have your Unicode app support both messages for receiving, and analyze the target HWND to decide which message to use for sending.

Casting literals to PChar / PAnsiChar

I've got really stupid question...
Why this code:
PChar('x');
causes "Access violation" error?
Compiler optimalisation?
Example:
var s: String;
...
s := StrPas(PAnsiChar('x'));
This causes AV in Delphi 5 / Delphi XE
Or this one:
Windows.MessageBox(0, PChar('x'), PChar('y'), 0);
This causes AV in Delphi 5, but not in Delphi XE
In XE there is an empty MessageBox
Console example:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
var s: String;
begin
s := StrPas(PChar('xxx')); // EAccessViolation here
end.
StrPas(PAnsiChar('x'));
I posit that 'x' is treated as a character literal rather than a string literal. And so the cast is not valid. If so then this will work as you would expect
StrPas('x');
due to an implicit conversion. Or
StrPas(PAnsiChar(AnsiString('x')));
thanks to the explicit conversion.
I think the former is probably to be preferred. Literals don't need casting to null terminated pointer types. The compiler can emit the correct code without the cast. And casts always run the risk of suppressing an error.

Will Delphi compiler always fail when compiling a module with conditional-based type mismatch?

For practical example, suppose ModuleA have some type varying on conditional compilation:
unit ModuleA;
interface
type
{ explicit character width }
PASTR = type PAnsiChar;
PWSTR = type PWideChar;
{ imlicit character width }
PTSTR = {$IFDEF UNICODE}PWSTR{$ELSE}PASTR{$ENDIF};
{ ... }
And now the set of functions in ModuleB is depending on the type declared in ModuleA:
unit ModuleB;
{ ... }
implementation
uses ModuleA;
{ explicit }
function FuncA(Arg: PASTR): Integer;
begin
{ do something with 1 byte char }
end;
function FuncW(Arg: PWSTR): Integer;
begin
{ do something with 2 byte char }
end;
{ implicit - will compiler safeguard against mismatching UNICODE define? }
function Func(Arg: PTSTR): Integer;
begin
{ map to explicit one }
Result := {$IFDEF UNICODE}FuncW(Arg){$ELSE}FuncA(Arg){$ENDIF};
end;
Now, suppose ModuleA has been compiled with UNICODE symbol defined and DCU file has been generated. And then ModuleB has been compiled using DCU information only without UNICODE symbol (or vice versa case - ModuleA.dcu generated as ANSI, and then there is an attempt to use it in Unicode ModuleB).
Will Delphi compiler always fail when compiling a module with such conditional-based type mismatch? With type keyword and without it? Is there any version specific behaviour?
As a bonus: I'm interested in the same for Free Pascal.
The compiler will fail to compile the program in that scenario. Both in Delphi and FPC.
One way to think about it is to expand the conditional code. Once written that way it becomes much clearer what the compiler will do. And this is the right mental model because that is how the compiler processes conditional code. It simply does not see the code that is in conditional branches that are not active.
Consider these two units:
unit Unit1;
// expanded with UNICODE defined
interface
type
PASTR = type PAnsiChar;
PWSTR = type PWideChar;
PTSTR = PWSTR;
implementation
end.
unit Unit2;
// expanded with UNICODE undefined
interface
implementation
uses
Unit1;
function FuncA(Arg: PASTR): Integer;
begin
end;
function FuncW(Arg: PWSTR): Integer;
begin
end;
function Func(Arg: PTSTR): Integer;
begin
Result := FuncA(Arg);
end;
end.
The definition of PTSTR is found in Unit1 and it is an alias to PWSTR. Hence the call to FuncA will not compile.
The use of type to make distinct types changes nothing either, because PAnsiChar and PWideChar are already incompatible.
Yes and no, you could break it if you introduced a DEF or UNDEF of the UNICODE symbol, effectively overriding the directive you pass in when compiling.
Not sure why you would do something that daft, though.
Can't think of any version based difference, not breaking would be a catastrophic bug.

"reference to function" as result of a function

i have a function that returns a function TFunc<Integer> which is reference to function:Integer.
and i have a procedure which takes a function TFunc<Integer> as argument, calls it and prints its result.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
function GetFunction:TFunc<Integer>;
begin
result := function:Integer begin result := 42 end;
end;
procedure FunctionCall(AFunc:TFunc<Integer>);
var i:Integer;
begin
i := AFunc;
WriteLn(Format('Function Result = %d',[i]));
end;
begin
// FunctionCall(GetFunction); // error
FunctionCall(GetFunction()); // works as excpected
end.
this call (FunctionCall(GetFunction);) results in an error. and the call with () works as excpected.
my question is:
when in delphi do i need brakets to call a function and when not (i thought that i never need them)
or
shouldn't i need them and is it a bug?
i work with delphi xe5 on windows 7 dcc32.
If what you report is correct (and see below for more on that), then you would have found a bug, I believe.
This code:
FunctionCall(GetFunction);
should not compile. Indeed it does not compile when I try to compile it in XE3, XE4, XE5, XE6 and XE7. It does not compile because, in this particular context, the compiler interprets GetFunction as being of type
function: TFunc<Integer>
All above mentioned compilers object with this error message:
[dcc32 Error] E2010 Incompatible types: 'System.SysUtils.TFunc' and 'Procedure'
So, if you have somehow (perhaps with some compiler options), managed to make that code compile then I can only believe that is due to a bug.
You should deal with this by applying the parentheses so that the compiler can understand that you wish to call GetFunction, not refer to it.
FunctionCall(GetFunction());

E2010 Incompatible Types, why?

I'm getting this error:
[DCC Error] JwaStrSafe.pas(2277): E2010 Incompatible types: 'PSTRSAFE_LPWSTR' and 'PSTRSAFE_LPTSTR'
The following is the relevant portion of code from JwaStrSafe.pas (from Jedi Api), I'm compiling with the symbol UNICODE defined:
type
STRSAFE_LPWSTR = PWIDECHAR;
PSTRSAFE_LPWSTR = ^STRSAFE_LPWSTR;
{$IFDEF UNICODE}
STRSAFE_LPTSTR = STRSAFE_LPWSTR;
PSTRSAFE_LPTSTR = ^STRSAFE_LPTSTR;
{$ELSE}
...
{$ENDIF}
...
//function declaration
function StringCchCopyExW(
{__out_ecount(cchDest)}pszDest : STRSAFE_LPWSTR;
{__in}cchDest : size_t;
{__in}const pszSrc : STRSAFE_LPCWSTR;
{__deref_opt_out_ecount(pcchRemaining^)}ppszDestEnd : PSTRSAFE_LPWSTR;
{__out_opt}pcchRemaining : PSize_t;
{__in}dwFlags : Cardinal) : HRESULT; stdcall; forward; external;
...
//var passed to function
ppszDestEnd : PSTRSAFE_LPTSTR;
...
{$IFDEF UNICODE}
result := StringCchCopyExW(pszDest, cchDest, pszSrc, ppszDestEnd, pcchRemaining, dwFlags);
{$ELSE}
result := StringCchCopyExA(pszDest, cchDest, pszSrc, ppszDestEnd, pcchRemaining, dwFlags);
{$ENDIF}
I get the error on the call of StringCchCopyExW, on parameter ppszDestEnd.
Looking at the type definition I understand that PSTRSAFE_LPTSTR is a pointer type to STRSAFE_LPTSTR which is just an alias of STRSAFE_LPWSTR, why are PSTRSAFE_LPTSTR and PSTRSAFE_LPWSTR incompatible?
Solution
Thanks to David's explanation I replaced
PSTRSAFE_LPTSTR = ^STRSAFE_LPTSTR;
with
PSTRSAFE_LPTSTR = PSTRSAFE_LPWSTR;
now the code compiles without errors.
Thanks
I can reproduce this easily enough in XE2, and I imagine it will behave the same in all other versions. To make it simpler I've cut it down to this:
program PointerTypeCompatibility;
{$APPTYPE CONSOLE}
type
A = Integer;
B = Integer;
var
ptA: ^A;
ptB: ^B;
begin
ptA := ptB;
end.
This also produces E2010. However, if you enable the type-checked pointers option, then the code compiles successfully. In fact the documentation of that compiler options states:
In the {$T-} state, distinct pointer types other than Pointer are incompatible (even if they are pointers to the same type). In the {$T+} state, pointers to the same type are compatible.
Thanks to Ken White for pointing me at the useful help topic Type Compatibility and Identity. The pertinent extracts are that types T1 and T2 are assignment compatible if:
T1 and T2 are compatible pointer types.
The documentation also states that types are type compatibile if:
Both types are (typed) pointers to the same type and the {$T+} compiler directive is in effect.
So this documents the observed behaviour and leads me to this example:
program PointerTypeCompatibilityTake2;
{$APPTYPE CONSOLE}
{$TYPEDADDRESS OFF}
var
P1,P2: ^Integer;
P3: ^Integer;
begin
P1 := P2;//compiles
P1 := P3;//E2008 Incompatible types
end.
So, to summarise:
When type-checked pointers is disabled, pointers are assignment compatible if the pointers are of the same type.
When type-checked pointers is enabled, pointers are assignment compatible if the pointers point to the the same type.
I have to confess to being ignorant of the history and reasoning behind the type-checked pointer setting, so I can't offer any explanation for why the compiler is the way it is.

Resources