Using Delphi 10.4 and a FMX program.
I have a packed record with a "Single" type defined:
rRec = packed record
...
Inch: Single;
...
end;
Later in the logic, I wish to display the valve of Inch as a string. There are three ways to do this:
var
s: Single;
vStr: string;
vRec: rRec;
begin
vRec.Inch = 12.3;
//use FloatToStrF
vStr := FloatToStrF(vRec.Inch, ffFixed, 7, 1);
...
//Assign record value to variable and use TSingleHelper.ToString which simply calls FloatToStrF
s := vRec.Inch;
vStr := s.ToString(ffFixed, 7, 1);
...
//use TSingleHelper.ToString from the record value
//***** this fails on Android *****
vStr := vRec.Inch.ToString(ffFixed, 7, 1);
...
end;
All three of the above examples work on Win10, OSX and IOS. However, the last one (vRec.Inch.ToString) fails on Android with the IDE showing the error "Project xx raised exception class error (10)" before showing an access violation. The "Bus" error appears in the IDE when I try to step into the ...Inch.ToString call.
Is there a known reason TSingleHelper.ToString fails on Android when used with packed record fields? Note: I have not tested this with unpacked records, or tested the helper functions for other numeric types, or tested the TSingleHelper.ToString class function.
Edit:
turns out apparently Android does not support packed records very good or at all. I noticed in my code I had three "byte" fields before the "Single" field. By adding or removing a byte field, the vRec.Inch.ToString works. Guess I will have to use FloatToStrF.
Related
I am using Delphi Enterprise version 10.2.3 with Android SDK ver 24.3.3 32bit. I tried a very simple program with only one button. The Onclick is simply the following:
ShowMessage('1');
ShowMessage('2');
ShowMessage('3');
ShowMessage('4');
The result I got on my Samsung phone when clicking on the button is:
4
3
2
1
Of course I am expecting to get
1
2
3
4
This is not my first Android Program. The previous ones runs smoothly. But when I got strange errors on my latest program, I found that programming steps are carried out in reverse. I am also scared now to recompile the previous apps, just in case I am getting this strange behavior. So I just make a new program (above) to test, but got the same results. I also disabled the Antivirus Avast program, and even try it on another Samsung device.
Help will be very much appreciated. At this moment I am really confused and not sure what next steps to take to solve the problem. Please help me!
On mobile platforms, ShowMessage behaves asynchronously. The call finishes instantaneously, it does not wait for the user to close the dialog box.
Try this code:
function TForm1.MyShowMessage(const Msg: String): TModalResult;
var
MR: TModalResult;
begin
MR := mrNone;
TDialogService.MessageDialog(Msg, TMsgDlgType.mtConfirmation,const [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbYes, 0,
procedure(const AResult: TModalResult)
begin
MR := AResult;
end);
while MR = mrNone do begin
Application.ProcessMessages;
CheckSynchronize;
end;
Result := MR;
end;
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
This code is working in a Firemonkey Windows app, but doesn't work in Android app, i get Goodbye instead of Welcome, what is wrong?
Edit8 Text : 162496 //Computer unique code
Edit9 Text : 1564224593 //serial #
procedure TForm2.Button5Click(Sender: TObject);
var
f2,f1:textfile;
i,j:byte;
s1,s2,s3,c:string;
F: TextFile;
begin
j:=0;
s2 := Edit8.Text;
for i:=1 to Length(s2) do
if (s2[i]>='0') and (s2[i]<='9') then
s3:=s3+s2[i];
for i:=1 to Length(s3)-1 do
if (edit9.Text[i*2-1]<>s3[i]) or (abs(strtoint(s3[i+1])-strtoint(s3[i]))<> strtoint(edit9.Text[i*2])) then
inc(j);
if j=0 then
ShowMessage('Welcome')
else
ShowMessage('Goodbye');
end;
Delphi mobile compilers use zero-based strings.
You have three choices:
As #Günter_the_Beautiful points out, your best choice is to rewrite your code to use string helpers (which are always 0-based)
Rewrite your code to use 0-based indexing: for I := 0 to ...
If you need a quick fix, turn it off locally for your code snippet using the {$ZEROBASEDSTRINGS OFF} directive (and revert it back with {$ZEROBASEDSTRINGS ON} again).
For options 2. and 3., if you need your code to be cross-platform, consider using appropriate platform conditional defines. This is what makes option 1. compelling: no need to clutter your code with conditional defines.
I am using these two helper routines:
FUNCTION GetChar(CONST S : STRING ; OneBasedIndex : LongWord) : CHAR;
BEGIN
{$IF CompilerVersion>=24 }
Result:=S[SUCC(OneBasedIndex-LOW(S))]
{$ELSE }
Result:=S[OneBasedIndex]
{$IFEND }
END;
PROCEDURE SetChar(VAR S : STRING ; OneBasedIndex : LongWord ; NewChar : CHAR);
BEGIN
{$IF CompilerVersion>=24 }
S[SUCC(OneBasedIndex-LOW(S))]:=NewChar
{$ELSE }
S[OneBasedIndex]:=NewChar
{$IFEND }
END;
This way, you can continue working with strings as 1-based (which is the logical choice :-)) as long as you always access the strings as characters using these two functions.
I have problem with Delphi XE5 Firedac application. I use ZTE Blade 3 phone to run application. I used deployment manager to add database file to assets\internal directory. But when I call FDQuery1.FieldByName('Nimi').AsString it raises exception Segmentation fault (11).Thanks.
FDQuery1.SQL.Clear;
FDQuery1.SQL.Add('SELECT * FROM Laskuttaja');
FDQuery1.Open();
FDQuery1.First;
while(not FDQuery1.Eof) do begin
FormTiedot.EditNimi.Text := FDQuery1.FieldByName('Nimi').AsString;
FormTiedot.EditOsoite.Text := FDQuery1.FieldByName('Osoite').AsString;
FormTiedot.EditY.Text := FDQuery1.FieldByName('Ytunnus').AsString;
FDQuery1.Next;
end;
if FormTiedot.ShowModal = mrOk then begin
FDQuery1.SQL.Clear;
FDQuery1.SQL.Add('UPDATE Laskuttaja SET Nimi = '+QuotedStr(FormTiedot.EditNimi.Text)+', Osoite = ' + QuotedStr(FormTiedot.EditOsoite.Text) + ', Ytunnus=' + QuotedStr(FormTiedot.EditY.Text));
FDQuery1.SQL.Add('WHERE ID=1');
The error occurs on this line:
FormTiedot.EditNimi.Text := FDQuery1.FieldByName('Nimi').AsString;
A segmentation fault means that you are referring to invalid memory. So, this could arise for at least one of the following reasons:
FormTiedot is invalid.
FormTiedot.EditNimi is invalid.
FDQuery1 is invalid.
FDQuery1.FieldByName('Nimi') returns nil.
Now, as far as I know, FieldByName() raises an exception to indicate failure, rather than returning nil. And FDQuery1 is surely valid, otherwise the earlier code would have failed.
So, the most likely conclusion is that either FormTiedot or FormTiedot.EditNimi are invalid. Perhaps you failed to instantiate FormTiedot?
I was able to solve (I compiled and the error gives in function function "TClientModule1.GetServerMethods1Client: TServerMethods1Client;" when accessing the class FServerMethods1Client...
Go to menu:
Project -> Options -> Forms;
Verify that TClientModule1 is first in the Auto-Create forms.
I'm trying to use Indy's TIdHTTP component to send data to a website.
The code works perfectly on Windows platform but unfortunately it behaves strangely on Android platform. The problem occurs when I use TIdMultipartFormDataStream to send POST parameters.
On Android platform TIdMultipartFormDataStream behaves strangely and this what happens:
Suppose your POST data is myparam=myvalue where "myparam" is parameter name and "myvalue" is the parametervalue.
The parameter values gets changed to {FIRST CHARACTER REMOVED}yvalue{NULL CHARACTER} so the final output will look like this yvalue\x00 where \x00 is a null character.
I can replace TIdMultipartFormDataStream with TStringList and I won't face such issue but I prefer to use TIdMultipartFormDataStream because it enables me to upload files + send POST data at the same time.
Sample code:
procedure HTTPPOST;
var
HTTP: TIdHTTP;
POSTData: TIdMultipartFormDataStream;
begin
HTTP := TIdHTTP.Create(nil);
POSTData := TIdMultipartFormDataStream.Create;
try
POSTData.AddFile('myfile','file.txt'); // works
POSTData.AddFormField('username', 'user1'); // On Android the value gets changed to ser1\x00 where \x00 = Null character
HTTP.Post('http://www.example.com', POSTData)
finally
POSTData.Free;
end;
end;
Note: the code was tested using Delphi XE5 and Delphi XE5 Update 1
What you describe sounds like a ZEROBASEDSTRINGS bug, which should not exist in the current SVN version because Indy disables ZEROBASEDSTRINGS globally in all of its units after it kept suffering from lots of ZBS bugs in XE4. So I do suggest you upgrade to the lastest SVN verson. If you are having problems doing so, please update your question with details explaining why
Works..
procedure HTTPPOST;
var
HTTP: TIdHTTP;
POSTData: TIdMultipartFormDataStream;
begin
HTTP := TIdHTTP.Create(nil);
POSTData := TIdMultipartFormDataStream.Create;
try
POSTData.AddFile('myfile','file.txt'); // works
POSTData.AddFormField('username', UTF8Encode('user1'), 'utf-8').ContentTransfer:= '8bit';
HTTP.Post('http://www.example.com', POSTData)
finally
POSTData.Free;
end;
end;