Why does this code:
w: word;
s: String;
begin
str(w, s);
generate this warning in XE7:
[dcc32 Warning] Unit1.pas(76): W1057 Implicit string cast from 'ShortString' to 'string'
Tom
System.Str is an intrinsic function that dates from a byegone era. The documentation says this:
procedure Str(const X [: Width [:Decimals]]; var S: String);
....
Notes: However, on using this procedure, the compiler may issue a warning: W1057 Implicit string cast from '%s' to '%s' (Delphi).
If a string with a predefined minimum length is not needed, try using the IntToStr function instead.
Since this is an intrinsic, there is likely something extra going on. Behind the scenes, the intrinsic function is implemented by a call to an RTL support function that yields a ShortString. Compiler magic then turns that into a string. And warns you of the implicit conversion. The compiler magic transforms
Str(w, s);
into
s := _Str0Long(w);
Where _Str0Long is:
function _Str0Long(val: Longint): _ShortStr;
begin
Result := _StrLong(val, 0);
end;
Since _Str0Long returns a ShortString then the compiler has to generate code to perform the implicit converstion from ShortString to string when it assigns to your variable s. And of course it's then natural that you see W1057.
The bottom line is that Str only exists to retain compatibility with legacy Pascal ShortString code. New code should not be calling Str. You should do what the documentation says and call IntToStr:
s := IntToStr(w);
Or perhaps:
s := w.ToString;
Related
We have some structure S. Is it possible to make a converter so that the expression
s := S(a_string)
began to compile, where a_string is actually a string.
The allowed conversions are listed in Spec: Conversions. There's a section for "Conversions to and from a string type". Only those are allowed, you can't "extend" or change the behavior of conversions.
You may however always write a function that takes a string and returns a value of type S.
func Parse(s string) S {
var r S
// Parsing logic
return r
}
Using it is / looks like the same as a conversion:
s := Parse(a_string)
At the moment I'm converting a project written in VBA to Delphi and have stumbled upon a problem with converting some Subs with Optional arguments.
Say, there is a Sub declaration (just an example, actual Subs have up to 10 optional parameters):
Sub SetMark
(x0 As Double, y0 As Double,
Optional TextOffset As Integer =5,
Optional TextBefore As String = "",
Optional Text As String = "",
Optional TextAfter As String = "mm",
Optional Color As String = "FFFFFF",
Optional ArrowPresent As Boolean = True)
That Sub subsequently can be called like this:
Call SetMark (15, 100,,,"135")
Call SetMark (100, 100, 8,, "My text here..", "")
'a lot of calls here
The Optional arguments are very flexible here, you can omit any of them, and you can assign a value to any of them as well. Unlike in Delphi.
Procedure SetMark
(x0: real; y0: real,
TextOffset: Integer =5;
TextBefore: ShortString = '';
Text: ShortString = '';
TextAfter: ShortString = 'mm';
Color: ShortString = 'FFFFFF';
ArrowPresent: Boolean = True);
It seems you cannot just make a copy of VBA call:
SetMark (15, 100,,,'135');// error here
So, the question is: is there any way to convert that Subs to Delphi procedures keeping the same flexibility in parameters?
My first idea was to use default parameters, but it doesn't work.
As for now it seems in Delphi I will have to pass all the parameters in the list with their values directly but that means a lot of work for reviewing and proper porting of VBA calls.
Any ideas?
Is there any way to convert the VBA subroutines to Delphi procedures, still keeping the same flexibility in parameters?
There is no way to achieve that – that flexibility to omit parameters, other than at the end of the list, simply does not exist.
For methods of automation objects, you can use named parameters, as described here: Named/optional parameters in Delphi? However, I very much recommend that you don't implement your classes as automation objects just to get that functionality.
Whenever you switch between languages, you will find differences that are inconvenient. That is inevitable. The best approach is to try to find the best way to solve the problem in the new language, rather than trying to force idioms from the old language into the new language.
In this case you might want to use overloaded functions or parameter objects as ways to alleviate this inconvenience.
Just to expand on the idea of refactoring to use a parameter object, you could declare a record like:
TSetMarkParams = record
x0 : double;
y0 : double;
TextOffset : integer;
TextBefore : string;
Text : string;
TextAfter : string;
Color : string;
ArrowPresent : boolean;
constructor Create(Ax0, Ay0 : double);
end;
And implement the constructor to populate default values as :
constructor TSetMarkParams.Create(Ax0, Ay0 : double);
begin
x0 := Ax0;
y0 := Ay0;
TextOffset := 5;
TextBefore := '';
Text := '';
TextAfter := 'mm';
Color := 'FFFFFF';
AllowPresent := true;
end;
Your procedure would then have signature :
procedure SetMark(ASetMarkParams : TSetMarkParams);
Which you could then, using your example of SetMark (15, 100,,,'135'); call as :
var
LSetMarkParams : TSetMarkParams
begin
LSetMarkParams := TSetMarkParams.Create(15, 100);
LSetMarkParams.Text := '135';
SetMark(LSetMarkParams);
end;
As collateral benefit, the above is much more readable as it saves you from going blind trying to count commas when returning to debug a troublesome method call.
I got a great surprise when I noticed the following snippet not compiling:
aTime := time.Time{}
if defaultTime := time.Time{} ; aTime != defaultTime {}
The compiler returns:
type time.Time is not an expression
defaultTime := time.Time used as
value undefined: defaultTime
The intent here is to test the aTime variable if it's set to it's default value.
It also does not compile if I get the pointer of the struct (defaultTime := &time.Time{}).
However, it does compile if I init defaultTime outside of the if construct, or do the init using the new() builtin:
aTime := time.Time{}
if defaultTime := new(time.Time) ; aTime != *defaultTime {}
From what I've read everywhere, new(myStruct) it supposed to be completely equivalent to &myStruct{}.
As I interprate it, defaultValue := time.Time{} qualifies as a SimpleStmt (specifically an Assignment), as detailed in the If statement spec.
I've come up with no explanation for this behavior, despite my best googling efforts. Would be grateful if someone could make my head stop spinning.
The { is recognized as the beginning of a Block, terminating the parsing of the SimpleStmt. After committing to that decision, the compiler decides that, as a SimpleStmt, aTime := time.Time isn't valid because time.Time isn't a value that can be assigned. However, it's presumably too late for the parser to try another interpretation of the {.
The version with new works because it doesn't contain a { character, and so avoids confusing the parser in this way.
You can also use the literal format by wrapping it in parentheses, because a block can't legally begin in the middle of an expression, so this also works:
if defaultTime := (time.Time{}); aTime != defaultTime {
// ...
}
gofmt gives the helpful message "expected boolean expression, found simple statement (missing parentheses around composite literal?)", but oddly, the go compiler itself does not.
I don't think what I am going to suggest is necessarily a better solution. But for your use case, you can try this to be concise
if !aTime.IsZero() {
// ...
}
Ymmv
How can I execute in Delphi a conditional statements in a String?
In PHP there is something like this:
<?php
echo "Hello (isset($name) ? $name : 'Guest'));
?>
I'm assuming you actually want to evaluate code that is not known until runtime. That's the only reason why you would have code in a string. If my assumption is correct, then you cannot do that readily in Delphi. Delphi is compiled. So in order to execute Delphi code you need to compile it.
You could consider using a scripting language for this part of your program. There are many available.
Of course, if all you want is a conditional operator in Delphi then there is none built in but the RTL provides IfThen:
function IfThen(AValue: Boolean; const ATrue: string;
AFalse: string = ''): string;
Description
Conditionally returns one of two specified values.
IfThen checks the expression passed as AValue and returns ATrue if it evaluates to true, or AFalse if it evaluates to false. In Delphi, if the AFalse parameter is omitted, IfThen returns 0 or an empty string when AValue evaluates to False.
the closest thing you can get in Delphi is this :
Writeln('Hello ' + IIf(Name='', 'Guest', Name));
where IIf is defined as:
function iif(Test: boolean; TrueRes, FalseRes: string): string;
begin
if Test then
Result := TrueRes
else
Result := FalseRes;
end;
Please mind that this example only works with strings...
EDIT
As David suggested you can also use the IfThen function from the StrUtils unit
For a type independent IIF, use this:
function IIF(pResult: Boolean; pIfTrue: Variant; pIfFalse: Variant): Variant;
begin
if pResult then
Result := pIfTrue
else
Result := pIfFalse;
end;
I'm trying to create string using StringOf function in code below. Why after ZeroMemory on array that was used to create string Showmessage displays nothing. Why? In commented ZeroMemory case ===== is displayed.
TIdBytes = array of Byte;
procedure fill(var b: TIDBytes);
begin
setlength(b,5);
b[0]:=61;
b[1]:=61;
b[2]:=61;
b[3]:=61;
b[4]:=61;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
POSSaleTransaction: TPOSSaleTransaction;
s: ansistring ;
b:TIDBytes;
begin
fill(b);
s := StringOf( TArray<byte>(b) );
ZeroMemory(#b, Length(b));
Showmessage(s);
end;
I'm using Delphi XE4
The reason I'm trying to ZeroMemory is that I wont to be 100% shure that newly created string is not using reference to byte[], but copyes b data. With help of ZeroMemory I'm deleting contents of b while expecting that it will not have influence on string.
ZeroMemory does not free memory. It writes zero bytes into the block of memory that you provide.
Even then, your code gets that wrong. In your code, b is a pointer to the dynamic array. You pass #b to ZeroMemory so you are zeroising the pointer rather than the array that it points to. And since the value byte count that you pass is greater than SizeOf(b) then you are zeroising other parts of the stack too. That's why your call to ZeroMemory is destroying your string.
To zeroise the memory you would write:
ZeroMemory(Pointer(b), Length(b));
If you want to delete a dynamic array then you can write
b := nil;
or
Finalize(b);
or
SetLength(b, 0);
The reason I'm trying to use ZeroMemory is that I want to be 100% sure that newly created string is not using reference to the byte array, but is a copy of it.
You don't need to write any code to prove that. You can be sure because a Delphi string is UTF-16 encoded, and your byte array uses an 8 bit encoding. So even if the RTL designers wanted to take a reference to the byte array, it would not have been possible.
You just blew the stack variables there. Both the B pointer (in its entirety) and partially the pointer S ( (Length(b) - SizeOf(b)) bytes of it ).
What is b ? it is a some complex structure, a handle, a pointer. Usually You do not want to destroy memory structure, you want to put the data into the cells. But in your example you just wiped out the whole memory structures allocated on stack. Including, probably, the string itself.
The following program works as expected in Delphi XE2 - see what is there instead of Zero Memory. Read what are dynamic arrays in Delphi and how they are allocated from CPU Assembler point of view when you want to use low-level tricks as raw pointers ( or untyped variables like in ZeroMemory)
program Project11;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
procedure fill(var b: TBytes);
begin
SetLength(b,5);
// b[0]:=61; b[1]:=61; b[2]:=61; b[3]:=61; b[4]:=61;
FillChar(b[Low(b)], Length(b), 61); // Less copy-paste, more program structure
// Notice, above i take pointer to the cell inside the array,
// not to the array the container itself.
// That is both safer and does document the intention of the code
end;
Procedure SOTest();
var
s: ansistring ;
b: TBytes;
begin
fill(b);
s := StringOf( b );
// ZeroMemory(#b, Length(b)); -- destroying the pointer instead of freeing memory - is a memory leak
// FillChar(b, Length(b), 0); -- same as above, written in Pascal style, rather than C style.
b := nil; // this really does free the DYNAMIC ARRAYS. Laconic but prone to errors if mistyped.
// SetLength(b, 0); -- more conventional and safe method to do the same: free string or dyn-array.
// Anyway that is unnecessary - both b and s would anyway be auto-freed before the function exit.
Writeln(Length(s):4, ' ', s);
end;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
SOTest;
Write('Press Enter to exit;'); ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
See manuals.
http://docwiki.embarcadero.com/Libraries/XE4/en/System.FillChar
http://docwiki.embarcadero.com/RADStudio/XE4/en/Parameters_(Delphi)#Untyped_Parameters
http://docwiki.embarcadero.com/RADStudio/XE4/en/Structured_Types#Dynamic_Arrays
http://docwiki.embarcadero.com/Libraries/XE4/en/System.SysUtils.TBytes
http://docwiki.embarcadero.com/Libraries/XE4/en/System.SysUtils.StringOf
So the next question is WHY were you trying to call ZeroMemory, what is the point there ? IS there soem attempt to destroy a cypher key or other sensitive data ? http://www.catb.org/~esr/faqs/smart-questions.html#goal
If you only want to assure that "s" variable does not have any external references - there is a special function for it, UniqueString.
http://docwiki.embarcadero.com/Libraries/XE4/en/System.UniqueString
However in this particular workflow and this particular Delphi version that could not happen anyway. Read again manual for StringOf - it returns a UnicodeString temporary hidden variable. That variable is encoded in UTF-16 in XE4, which means having 2 bytes per letter, which means the original byte-chain would not suit anyway and would be transformed into new buffer.
After that you convert the UnicodeString temporary hidden variable into AnsiString variable s having one byte per letter, so it also can not have references to the temp-var, but would allocate yet another independent buffer to hold the transformed data.
As you can see there is two necessary copy-with-transformation operations, both of which make keeping data references just impossible.
You probably want to do this:
ZeroMemory(#b[0], Length(b));
instead of
ZeroMemory(#b, Length(b));
Remember the b variable is 4 bytes size pointer only and point to array of bytes.