okay I have a TStringList variable called CaLlist and use it as follows in my code.
CalList.strings[i] := "Something";
CalList.Objects[i] := TObject(22.3);
rawval := PtrInt(CalList.Objects[i]); <<------- Error raised here.
I am using FPC 2.6.2 Lazarus 1.0.16. This code compiles and runs okay but when I step through the code rawval is set to be some astronomical negative number not 22.3 as I expect it to be.
So, what am I doing wrong? Is there is a solution for this issue?
You must cast the .Object[i] to double (double size = 8 bytes = register size on x86_64) or single (single size = 4 bytes = register size on x86).
Example:
program Project1;
uses classes;
{$IFDEF CPU32}
type PtrFloat = Single;
{$ELSE}
type PtrFloat = Double;
{$ENDIF}
var
CalList: TStringList;
rawval: PtrFloat;
begin
CalList := TStringList.Create;
CalList.Add('');
rawval := 22.3;
CalList.strings[0] := 'Something';
CalList.Objects[0] := TObject(rawval);
rawval := PtrFloat(CalList.Objects[0]);
writeln(rawval);
readln;
end.
Related
how do i get the number of visible charcters in a UTF8String regardless of how many bytes it takes to hold the string?
If you under Windows, try CharNextW:
uses
Winapi.Windows,
System.SysUtils;
function GetCharsCount(const AStr: UTF8String): Integer;
var
P: PWideChar;
begin
Result := 0;
P := PWideChar(UnicodeString(AStr));
while P <> '' do begin
Inc(Result);
P := CharNextW(P);
end;
end;
I am running this code:
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
begin
if CompareText(Copy(ParamStr(I), 1, Length(Value)), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
end;
function GetAppName(Value: string): string;
begin
if CmdLineParamExists('/COMPONENTS=prog2') then
begin
Result := 'Программа 2'; //<----This shows up as ????????? 2
end
else
begin
Result := '{#SetupSetting("AppName")}';
end;
end;
procedure CurPageChanged(CurPageID: Integer);
var
S: string;
Begin
if CurPageID = wpSelectDir then
begin
S := SetupMessage(msgSelectDirLabel3);
StringChange(S, '[name]', GetAppName(''));
WizardForm.SelectDirLabel.Caption := S;
end;
end;
Now, I'm not sure what I'm doing wrong here. Every other string shows up correctly, except when I use the result of GetAppName. Should I convert anything to AnsiString at some point?
I'm assuming that you are using Ansi version of Inno Setup.
In the Ansi version, the culprit is probably the StringChange as it does not play nicely with non-Ansi character sets. Try using StringChangeEx.
Though you should be using Unicode version of Inno Setup anyway.
Only the most recent version of Inno Setup, 5.6, does support Unicode string literals. So make sure you have the latest version.
If you are stuck with an older version:
Encode the string like
#$041F#$0440#$043E#$0433#$0440#$0430#$043C#$043C#$0430 + ' 2'
Or, actually the most correct way is to add a new custom message to the language files (like Russian.isl):
[CustomMessages]
Program2=Программа 2
And load it like:
CustomMessage('Program2')
Something strange happens when I try to pass strings from the Lines of a TMemo control to an array of PChar. At the end of the routine, the last string in the array is duplicated. I was able to replicate this in this simple code:
procedure Test;
var
i: smallint;
arr: array of PAnsiChar;
strarr: array[0..1] of string;
begin
SetLength(arr, 2);
strarr[0] := 'abbb';
strarr[1] := 'baaa';
for i := 0 to Length(strarr) do
arr[i] := PAnsiChar(AnsiString(strarr[i]));
end;
If I run this procedure step by step, I can see arr[0] = 'abbb' however, at the end of the rutine, both values, arr[0] and arr[1] equal to baaa. I guess it has something to do with the typecast.
Can anyone see what is wrong ?
There are two problems with your code:
Your loop is exceeding the upper bound of the array. It needs to use for i := 0 to Length(strarr)-1 do or for i := 0 to High(strarr) do instead.
More importantly, when you type-cast an AnsiString to a PAnsiChar, it returns a pointer to the AnsiString's internal data if the AnsiString is not empty. You are type-casting a UnicodeString to an AnsiString and grabbing a pointer into it, so the compiler has to use a compiler-generated local variable for the AnsiString data. In other words, your code is effectively doing the same thing as the following:
procedure Test;
var
i: smallint;
arr: array of PAnsiChar;
strarr: array[0..1] of string;
compiler_temp: AnsiString;
begin
SetLength(arr, 2);
strarr[0] := 'abbb';
strarr[1] := 'baaa';
for i := 0 to Length(strarr) do
begin
compiler_temp := AnsiString(strarr[i]);
arr[i] := PAnsiChar(compiler_temp);
end;
end;
Depending on how the memory for compiler_temp gets managed by the RTL memory manager at run-time, it is certainly possible for arr[0] and arr[1] to end up pointing at the same physical memory block in this situation.
If you want an array of PAnsiChar values then you need to start with an array of Ansi data for them to point at:
procedure Test;
var
i: Integer;
arr: array of PAnsiChar;
strarr: array[0..1] of AnsiString;
begin
SetLength(arr, 2);
strarr[0] := 'abbb';
strarr[1] := 'baaa';
for i := 0 to Length(strarr)-1 do
arr[i] := PAnsiChar(strarr[i]);
end;
I have a spanish user who is getting an invalid floating point error when doing this
var
S : String;
R : Real;
begin
S := '3.12345';
R := StrToFloat(S); //- Exception here.
The reason for this is that his location uses , for the decimal place!
How can I safely convert the string above to a float for the user without it bombing out.
Roll your own version of StrToFloat
function StrToFloat_UK(const AStr: string): Float;
var
FS: TFormatSettings;
begin
FS.Create('en-UK');
Result:= StrToFloat(AStr, FS):
end;
And use this in place of StrToFloat.
Use the second overload of StrToFloat with a TFormatSettings that has DecimalSeparator set to ..
You could use the procedure val, it disregards local system settings.
var
S : String;
R : Real;
Test: Integer;
begin
S := '3.12345';
Val(S, R, Test);
end;
If you know that the strings use . as the decimal separator, then you should do something like
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.UpdateFormatSettings := false;
DecimalSeparator := '.';
end;
The line
Application.UpdateFormatSettings := false;
is very important. The default value of this property is true, and in such case, the DecimalSeparator variable may be reverted to its default value (e.g. ,) anytime, for instance when you switch user.
Does there exist any RTL Delphi function to determine the position of the last occurrence of a char in a string?
try the LastDelimiter function which is part of the SysUtils unit.
RRUZ answered the actual question (he gave you a RTL function).
Still, I cannot quite resist giving a simple code snippet that does what you want:
function LastCharPos(const S: string; const Chr: char): integer;
var
i: Integer;
begin
result := 0;
for i := length(S) downto 1 do
if S[i] = Chr then
Exit(i);
end;
Since this does exactly what you want and offer no other features, it is far more compact (especially when we use the Exit(Result) syntax of Delphi 2009 and later) and probably slightly faster. In Delphi 2007, however, you have to do
function LastCharPos(const S: string; const Chr: char): integer;
var
i: Integer;
begin
result := 0;
for i := length(S) downto 1 do
if S[i] = Chr then
begin
result := i;
break; // or Exit; if you prefer that
end;
end;
Use StrRScan or AnsiStrRScan, both in the SysUtils unit. The latter, despite its name, works on Unicode characters in the Delphi versions where string is UnicodeString. (If you still need the "real" Ansi version, use the AnsiStrings unit.)
These functions search for exactly one character, whereas LastDelimiter searches for any of several characters from the given list of possibilities — think of StrRScan as LastDelimiter optimized for a one-character Delimiters argument.
The best cross-platform solution is TStringHelper.LastIndexOf, it exists since Delphi XE4.
Note, that this function is 0-based.
And here's my contribution for finding the position of the nth occurrence of a substring within a string.
function GetPositionOfNthOccurence(sSubStr, sStr: string; iNth: integer): integer;
var
sTempStr: string;
iIteration: integer;
iTempPos: integer;
iTempResult: integer;
begin
result := 0;
// validate input parameters
if ((iNth < 1) or (sSubStr = '') or (sStr = '')) then exit;
// evaluate
iIteration := 0;
iTempResult := 0;
sTempStr := sStr;
while (iIteration < iNth) do
begin
iTempPos := Pos(sSubStr, sTempStr);
if (iTempPos = 0) then exit;
iTempResult := iTempResult + iTempPos;
sTempStr := Copy(sStr, iTempResult + 1, Length(sStr) - iTempResult);
inc(iIteration);
end;
result := iTempResult;
end;