Jump to content

Remy Lebeau

Members
  • Content Count

    2633
  • Joined

  • Last visited

  • Days Won

    109

Everything posted by Remy Lebeau

  1. Remy Lebeau

    My android app restarts on permission request

    It shouldn't. It has a Boolean return value, it should just return False on failure. @Yaron the code you have showed can be simplified a little bit: // Request permission PermissionsService.RequestPermissions( [FPermission_WRITE_EXTERNAL_STORAGE, FPermission_READ_EXTERNAL_STORAGE], procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>) var fmt: TFormatSettings; begin if (Length(AGrantResults) = 2) and (AGrantResults[0] = TPermissionStatus.Granted) and (AGrantResults[1] = TPermissionStatus.Granted) then begin { Save file } PIKAFolder := System.IOUtils.TPath.GetSharedPicturesPath; if PIKAFolder <> '' then PIKAFolder := TPath.Combine(PIKAFolder, clientGameName); if ForceDirectories(PIKAFolder) then begin fmt := TFormatSettings.Create; fmt.DateSeparator := '.'; fmt.TimeSeparator := '.'; PIKAFile := TPath.Combine(PIKAFolder, clientGameName + '_' + DateTimeToStr(Now, fmt) + saveImageFileExt); try MyImage.SaveToFile(PIKAFile); clientFadingMessage := strPictureSaved; except clientFadingMessage := strErrorSavingPicture; end; end else clientFadingMessage := strPictureFolderNotFound; end else clientFadingMessage := strErrorSavingPicture; end );
  2. Since you are developing for mobile. DO NOT run blocking tasks in the main UI thread, it must service only the UI. Long-running tasks must be done in the background instead. In your case, you have a LONG-running task (5.5 seconds) that blocks the UI until the task is finished. Tokyo let you get away with it using a hack. Rio doesn't. So just don't do it anymore. You need to change the code (even for Tokyo) to work with the main UI thread correctly, stay away from ProcessMessages() at all costs (especially now that Embarcadero has broken it and doesn't want to fix it). Do things asynchronously so flow returns to the main UI message loop in a timely manner (otherwise Android is likely to kill your app!). For example: procedure TForm2.Button1Click(Sender: TObject); var StartTimer: TProc; begin ProgressBar1.Max := 100; ProgressBar1.Value := 0; StartTimer := procedure begin Timer1.Interval := 500; // <-- can be set at design-time Timer1.Enabled := True; end; {$IF CompilerVersion < 33} // 10.2 Tokyo or earlier TabControl1.TabIndex := 1; StartTimer; {$ELSE} // 10.3 Rio or later TabControl1.SetActiveTabWithTransitionAsync(TabControl1.Tabs[1], TTabTransition.None, TTabTransitionDirection.Normal, StartTimer); {$IFEND} end; procedure TForm2.Timer1Timer(Sender: TObject); begin ProgressBar1.Value := ProgressBar1.Value + 10; if ProgressBar1.Value >= ProgressBar1.Max then Timer1.Enabled := False; end; Yes, sorry about that. Fixed above. What is there not to understand? The TTabControl is transitioned to the desired Tab and a timer is started, then flow is returned to the main UI message loop. When the timer fires after 500ms, the ProgressBar is incremented and flow is returned to the main UI message loop. The timer fires again after another 500ms, and again, each time returning to the main UI message loop. Eventually, the ProgressBar reaches its Max and the timer is stopped.
  3. Remy Lebeau

    Initialize local variables at declaration

    There is no technical reason that I can think of, Borland/CodeGear/Embarcadero could have expanded on the Pascal syntax, like FreePascal did, but they simply never got around to implementing it that way. Remember that FreePascal is its own Pascal compiler, it is not based on Delphi, so it is free to do things however it wants to. It has quite a few Pascal language niceties that Delphi doesn't. Even Generics and Unicode support are other areas where FreePascal differs greatly from Delphi. But, FreePascal does have special compatibility modes meant for migrating Delphi code to FreePascal. In those modes, it tries to closely emulate Delphi 7 or 2009, depending on whether Unicode is being emulated or not.
  4. Well, then you went down the wrong rabbit hole, and you need to start climbing your way out of it. Such as? You really need to stop using ProcessMessages(), and start using proper code designs. Even if you don't want to use timers/threads, there is always TThread.ForceQueue(), for instance. Anything that allows code flow to return to the main UI message loop naturally will be better than manually pumping the message queue artificially. Then file a bug report with Embarcadero. But don't litter your code with ProcessMessages() calls just to satisfy the framework. Find other alternatives. Stay away from ProcessMessages() whenever possible. That was true even in the old days of the VCL. More so now in the days of FMX on mobile.
  5. Remy Lebeau

    Initialize local variables at declaration

    Note that FreePascal supports initializing variables in "old style" declarations. A shame that Delphi does not also support that, seems like something that might have been easy for Embarcadero to add while they were messing around with how variable declarations work.
  6. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Sorry, I missed the part where you had changed the function declaration to make the UIDI parameter be a single Byte, which is of course wrong. I thought you were still passing that parameter by PChar instead. So, you need to EITHER do this: Function GetSystemInformation(...; UIDI : PByte; ...): LongInt; stdcall;external 'RR3036.dll'; ... Get_Designated_tag_info_status := GetSystemInformation(..., PByte(@(DSFIDAndUID[0].UIDasBytes)), ...); alternatively Get_Designated_tag_info_status := GetSystemInformation(..., @(DSFIDAndUID[0].UIDasBytes[0]), ...); Or this: Function GetSystemInformation(...; var UIDI : Byte; ...): LongInt; stdcall;external 'RR3036.dll'; ... Get_Designated_tag_info_status := GetSystemInformation(..., DSFIDAndUID[0].UIDasBytes[0], ...);
  7. This is definitely not the right way to write code. Avoid ProcessMessages() whenever possible. The example given can be replaced with a simple UI Timer instead, eg: ProgressBar1.Value : =0; ProgressBar1.Max := 100; Timer1.Enabled := True; ... procedure TForm1.Timer1Timer(Sender: TObject); begin ProgressBar1.Value := ProgressBar1.Value + 1; if ProgressBar1.Value >= ProgressBar1.Max then begin Timer1.Enabled := True; Exit; end; //... end; But, if your real project is using the ProgressBar to tracking status of actual tasks, those tasks should be done in the background, and updates to the UI synced with the main UI thread accordingly.
  8. The caller that Bill refers to is whatever code is calling your TProductResource.GetProduct() method. That code is responsible for freeing the returned array when done using it, eg: Arr := SomeProductResource.GetProduct(ClientId); ... Arr.Free; UNLESS - TBllProduct.Singlenton.RetrieveJSONArray() returns an array that is owned by the Singleton, in which case the code that calls your TProductResource.GetProduct() must not free the array at all.
  9. Remy Lebeau

    Is another process running as admin?

    The easiest way to detect this is to simply send the message and handle the case where SendMessage() fails with an ERROR_ACCESS_DENIED error: "When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied)." It is not, actually.
  10. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Get_Designated_tag_info_status := GetSystemInformation(fComAdr, State, PChar(@DSFIDAndUID[0].UIDasBytes), InformationFlag, UIDO, DSFID, AFI, MemorySize, ICReference, errorCode, frmcomportindex); You need to type cast the array when passing it as a PChar.
  11. Remy Lebeau

    Function embedded in a function?

    Yes, it is doable, but the syntax is slightly different than you showed. The inner function has to be defined before the 'begin' of the outer function, eg: function foobar(param1, param2: string): string; var somevar: string; function inside(Param3: string): string; var anothervar: string; Begin Do something with Param3; ... Result := anothervar; end; begin Somevar := inside(somestring); Result := Somevar; end; This is covered in Embarcadero's documentation: Nested Routines
  12. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Little Endian vs Big Endian. Simply swap the bytes around. You don't need to convert anything. TTagResult has a UIDasBytes field to access the raw 8 bytes of the UID. Simply pass that field as-is to the function that wants the bytes.
  13. I would suggest having the wrapper expose access to the actual TStrings object for the SQL, instead of getting/setting the SQL as just a String. It is usually beneficial to be able to build up SQL queries, especially long/complex queries, in multiple pieces, which can be tricky/inefficient as a single string. type TxQuery<T: TDataSet> = class abstract private FiQuery: T; protected function GetSQL: TStrings; virtual; abstract; function GetDataSet: TDataSet; virtual; abstract; property iQuery: T read FiQuery write FiQuery; public procedure Execute; virtual; abstract; property SQL: TStrings read GetSQL; property DataSet: TDataSet read GetDataSet; end TxQueryClass = class of TxQuery; TxOraQuery = class TxQuery<TOracleDataset> protected function GetSQL: TStrings; override; function GetDataSet: TDataSet; override; end; TxPgQuery = class TxQuery<TPgQuery> protected function GetSQL: string; override; function GetDataSet: TDataSet; override; end; function TxOraQuery.GetSQL: TStrings; begin Result := iQuery.SQL; end; function TxOraQuery.GetDataSet: TDataSet; begin Result := iQuery.DataSet; end; function TxPgQuery.GetSQL: TStrings; begin Result := iQuery.SQL; end; function TxPgQuery.GetDataSet: TDataSet; begin Result := iQuery.DataSet; end; type TForm1 = class(TForm) var QryClass: TxQueryClass; procedure FormCreate; procedure TestQuery(aQuery: TxQuery; aSQL: string); end; procedure TForm1.FormCreate; begin if Config = Pg then QryClass := TxPgQuery else QryClass := TxOraQuery; end; procedure TForm1.TestQuery; var Qry: TxQuery; SQL: string; begin Qry := QryClass.Create; Proc1(Qry, aSQL); end; procedure TForm1.Proc1(aQuery: TxQuery; aSQL: string); begin aQuery.DataSet.Close; aQuery.SQL.Text := aSQL; aQuery.DataSet.Open; ... end;
  14. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Like this: var DSFIDAndUID : TTagBuffer; CardNum: Byte; ... DFSID: Byte; UID: Uint64; i: Integer; begin ... // populate DSFIDAndUID and CardNum as needed, then ... for i := 0 to CardNum-1 do begin DFSID := DSFIDAndUID[i].DFSID; UID := DSFIDAndUID[i].UID; // use DFSID and UID as needed... end; ... end;
  15. Remy Lebeau

    Remote Desktop with ICS

    Just curious, why are you converting from Indy to ICS? Are you having a problem with Indy?
  16. Remy Lebeau

    What to do with unsupported components?

    You could always embed the DLL in the EXE's resources and then extract it at runtime before using its functions. That way, the DLL does not have to be deployed separately.
  17. If checking for a common interface, like Dmitry showed, and creating wrappers, like Lars mentioned, do not work for you and/or are not options in your case, you can use RTTI instead: uses ..., System.Rtti; procedure Form1.Proc1( aQry : TDataset ); var Ctx: TRttiContext; SQL: TStrings; begin ... aQry.Close; SQL := TStrings(Ctx.GetType(aQry.ClassType).GetProperty('SQL').GetValue(aQry).AsObject); SQL.Text := 'select * from xyz'; aQry.Open; ... end;
  18. Essentially, yes. On Android, Google's mapping API is queried. On iOS, Apple's mapping API is queried. In both cases, the address database is stored remotely on Google's/Apple's systems, so you need an Internet connection to perform reverse geocoding.
  19. Remy Lebeau

    A directory translate

    I've already answered this for you. You need to use the IShellLibrary interface to get the filesystem path(s) that a given Library points to. Why do you keep ignoring me on this? You need to obtain the IShellLibrary interface for "Libraries\Pictures" first, Then you can call the IShellLibrary::GetFolders() method asking it for an IShellItemArray interface, Then you can the IShellItemArray::EnumItems() method to get an IEnumShellItems interface, Then you can enumerate it to access the individual IShellItem interfaces, And finally call IShellItem::GetDisplayName() asking for SIGDN_FILESYSPATH. This is all documented on MSDN.
  20. Remy Lebeau

    A directory translate

    EVERYTHING in the Shell has a PIDL, relative to the Shell root. In this case, Libraries is not part of the file system, so you can't get its PIDL from parsing a file system path. The correct way to get the Libraries PIDL is to use SHGetKnownFolderIDList(FOLDERID_Libraries) instead.
  21. Remy Lebeau

    Check for override

    But then we would have been stuck with Seek64() moving forward, and much more code would have had to be re-written to migrate to it. It is funny though, they have actually added a separate Seek32() now in 10.3 Rio, which takes a 32bit Integer for the Offset and a TSeekOrigin enum instead of a Word for the Origin parameter. Wonder why they decided to do that after all these years, instead of just leaving the 32bit Seek() alone or remove it completely. That is exactly what they actually did originally when the 64bit Seek() was first added in Delphi 6. The Word-taking version of the 64bit Seek() was added much later in XE4. Why, I have no idea, especially since it was marked as 'deprecated' in the same version it was added in. The 'deprecated' directive was added in Delphi 6, the same version that added the first version of the 64bit Seek().
  22. Remy Lebeau

    A directory translate

    That last step does not require the IShellFolder as the returned ItemIDList will be an absolute PIDL that can be passed to SHGetPathFromIDList(). But, this solution really isn't much different than using SHCreateItemFromParsingName() and ShellItemFileSystemPath() with an IShellItem instead of an ItemIDList.
  23. Remy Lebeau

    Get path and GPS from a gallery picture for Android

    When you capture an image to the library, FireMonkey does not give you access to the final image that is stored in the library, as you have noticed. And there is no guarantee that the image will have GPS information in it anyway (the user can disable that in the phone settings). So, generally, what you are asking for is not possible with TTakePhotoFromLibraryAction. You will likely have to either monitor the phone's library for new files, or else just access the phone's Camera and GPS APIs directly,
  24. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Remember that PChar is PWideChar in Delphi 2009+, but the DSFIDAndUID data is returning an array of bytes, not characters, so the function should be declared as using PByte, not PChar, for that parameter: Function Inventory(var ComAdr : Byte; var State : Byte; var AFI : Byte; DSFIDAndUID : PByte; var CardNum : Byte; frmComPortindex : Integer): LongInt; stdcall; external 'RR3036.dll'; I would recommend this approach as well. However, do note that the function declaration would have to be changed to accommodate this, eg: Function Inventory(var ComAdr : Byte; var State : Byte; var AFI : Byte; var DSFIDAndUID : TTagBuffer; var CardNum : Byte; frmComPortindex : Integer): LongInt; stdcall; external 'RR3036.dll'; Otherwise, you would have to type-cast the DSFIDAndUID variable when passing it: var DSFIDAndUID : TTagBuffer; Get_UID_RFID_Tag_Data_Status := Inventory(fComAdr, State, AFI, PChar(@DSFIDAndUID), CardNum, frmcomportindex);
  25. Remy Lebeau

    Read of address DEADBEE7. - Source of this raise?

    In some piece of code somewhere, a pointer was set to $DEADBEEF, most likely after the object it was pointing to had been freed, and then that same pointer was used afterwards by code running at address $0040CEEC trying to access a data member at offset -8 ($DEADBEE7) via that pointer. Magic addresses like $DEADBEEF are commonly used for debugging such mistakes. In this case, the code at address $0040CEEC is the System._UStrAsg() function, which is used to assign a UnicodeString to another UnicodeString. Offset -8 is the StrRec.refCount field in the UnicodeString data header, which makes sense as UStrAsg() does increment that field. In other words, some piece of code tried to assign a UnicodeString value to a dead UnicodeString variable. UStrAsg() was called by TFieldValue.SetValue(), so SetValue() was likely called on an invalid TFieldValue object inside of TGridSet.CopyRow(), which was called by TDeliverySet.UpdateFromDeliverySet() inside of TDeliveryGrid.RefreshByRouteList(). So, start in RefreshByRouteList() and work your way into UpdateFromDeliverySet() and try to figure out how it may be accessing an invalid TFieldValue object. The Operation System, when the invalid memory address $DEADBEE7 is accessed. Delphi's RTL catches that error from the OS, creates an EAccessViolation object for it, and then raises it.
×