Jump to content

Remy Lebeau

Members
  • Content Count

    103
  • Joined

  • Last visited

  • Days Won

    11

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Using Indy for cross-platform TCP/IP

    I don't recommend that model, ESPECIALLY on Android, which DOES NOT ALLOW you to perform socket operations on the main UI thread if you are targeting API level 11 (Android 3.0 Honeycomb) or later. It will throw a NetworkOnMainThreadException exception if you try. Not only that, but Android will likely just kill your app process outright if the main thread becomes blocked for too long. Or, at the very least, it will prompt the user to kill the process. "safe" is relative to the platform you are running on. But I would not recommend it, since it has the POTENTIAL to block the main thread, which may be acceptable for desktop platforms like Windows, but is certainly not acceptable for mobile platforms like iOS and Android. You can call it in a worker thread and let it block normally. But, if you really want to check manually, the IOHandler does have InputBufferIsEmpty() and CheckForDataOnSource() methods, where the latter has a timeout parameter available that you can set as low as 0ms.
  2. Remy Lebeau

    Using Indy for cross-platform TCP/IP

    TIdTCPClient.Connect() is a blocking operation, so you should not be calling it in your UI thread. Call it in a worker thread as well. I would suggest changing your reading thread to call Connect() before entering its reading loop, and then call Disconnect() when the loop is finsihed. Also, do not call TIdTCPClient.Connected() in a different thread than TIdIOHandler.ReadLn(). Connected() performs a read operation, so it may read bytes that ReadLn() is expecting, or worse, having 2 threads reading from the same socket may cause the TIdIOHandler.InputBuffer to store the read bytes in the wrong order. Never perform reading operations across thread boundaries, unless you synchronous the threads. In any case, the demo you based your code on was written for Delphi 2007, which only supports Windows development. On non-Windows platforms (Android is Java running on top of Linux), closing a socket in one thread is not *guaranteed* to abort a blocking socket operation in another thread (though it *should* since Indy shuts down the socket before closing it, and a shut down should abort a socket operation in progress). However, on Nix-based systems at least, where sockets are just file descriptors, it is *possible* that the act of closing a socket descriptor allows that descriptor to be reused right away for another socket, or even a file, thus you *might* be performing data I/O on something that you are not expecting. In your case, a simple fix I would suggest for you is to call ReadLn() with a timeout (either via its own ATimeout input parameter, or via the TIdIOHandler.ReadTimeout property), and then you can check the TIdIOHandler.ReadLnTimedOut property whenever ReadLn() exits, if needed. Then you can simply signal your thread to terminate, and it will do so when the current timeout elapses and control returns to the 'while' loop. Try this: procedure TMainForm.CreateTCPIPConnection; begin if not Assigned(ZPReadThread) then begin IdTCPClient.Host := AddressEdit.Text; IdTCPClient.Port := StrToIntDef(PortEdit.Text, 4769); try ZPReadThread := TReadingThread.Create(IdTCPClient); try ZPReadThread.OnData := DataReceived; ZPReadThread.Start; except FreeAndNil(ZPReadThread); raise; end; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP exception creating read thread : '+E.Message);{$ENDIF} end; end; end; end; procedure TReadingThread.Execute; begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread created');{$ENDIF} try FClient.ConnectTimeout := 10000; // <-- use whatever you want... FClient.Connect; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP connect exception : '+E.Message);{$ENDIF} raise; end; end; try FClient.IOHandler.ReadTimeout := 5000; // <-- use whatever you want... while not Terminated do begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread ReadLn (before)');{$ENDIF} try FData := FClient.IOHandler.ReadLn; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP IOHandler.ReadLn exception : '+E.Message);{$ENDIF} raise; end; end; //FClient.IOHandler.ReadBytes(AData, sizeof(TWaveFormSample), False); {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread ReadLn (after)');{$ENDIF} if (FData <> '') and Assigned(FOnData) then Synchronize(DataReceived); //Sleep(1); end; finally FClient.Disconnect; end; end; procedure TReadingThread.DoTerminate; begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread terminating');{$ENDIF} inherited; end; procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); begin if Assigned(ZPReadThread) then begin {$IFDEF TRACEDEBUG}AddDebugEntry('Terminating Read thread');{$ENDIF} ZPReadThread.Terminate; {$IFDEF TRACEDEBUG}AddDebugEntry('Waiting for read thread termination');{$ENDIF} ZPReadThread.WaitFor; {$IFDEF TRACEDEBUG}AddDebugEntry('Finished waiting for read thread termination');{$ENDIF} FreeAndNil(ZPReadThread); end; end; Another option would be to have your UI thread shut down the socket directly, thus aborting the read. Let the reading thread close the socket when ready. procedure TMainForm.CreateTCPIPConnection; begin if not Assigned(ZPReadThread) then begin IdTCPClient.Host := AddressEdit.Text; IdTCPClient.Port := StrToIntDef(PortEdit.Text, 4769); try ZPReadThread := TReadingThread.Create(IdTCPClient); try ZPReadThread.OnData := DataReceived; ZPReadThread.Start; except FreeAndNil(ZPReadThread); raise; end; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP exception creating read thread : '+E.Message);{$ENDIF} end; end; end; end; procedure TReadingThread.Execute; begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread created');{$ENDIF} try FClient.ConnectTimeout := 10000; // <-- use whatever you want... FClient.Connect; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP connect exception : '+E.Message);{$ENDIF} raise; end; end; try while not Terminated do begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread ReadLn (before)');{$ENDIF} try FData := FClient.IOHandler.ReadLn; except on E: Exception do begin {$IFDEF TRACEDEBUG}AddDebugEntry('TCP/IP IOHandler.ReadLn exception : '+E.Message);{$ENDIF} raise; end; end; //FClient.IOHandler.ReadBytes(AData, sizeof(TWaveFormSample), False); {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread ReadLn (after)');{$ENDIF} if (FData <> '') and Assigned(FOnData) then Synchronize(DataReceived); //Sleep(1); end; finally FClient.Disconnect; end; end; procedure TReadingThread.DoTerminate; begin {$IFDEF TRACEDEBUG}AddDebugEntry('Read thread terminating');{$ENDIF} inherited; end; type TIdStackBSDBaseAccess = class(TIdStackBSDBase) end; procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); begin if Assigned(ZPReadThread) then begin {$IFDEF TRACEDEBUG}AddDebugEntry('Terminating Read thread');{$ENDIF} ZPReadThread.Terminate; try {$IFDEF TRACEDEBUG}AddDebugEntry('Shutting down socket');{$ENDIF} TIdStackBSDBaseAccess(GBSDStack).WSShutdown(IdTCPClient.Socket.Binding.Handle, Id_SD_Both); finally {$IFDEF TRACEDEBUG}AddDebugEntry('Waiting for read thread termination');{$ENDIF} ZPReadThread.WaitFor; {$IFDEF TRACEDEBUG}AddDebugEntry('Finished waiting for read thread termination');{$ENDIF} FreeAndNil(ZPReadThread); end; end; end;
  3. Remy Lebeau

    Exclude already read posts

    Is this what you are looking for?
  4. Remy Lebeau

    Any advice when to use FileExists?

    FileExists() has not relied on file ages for many years. The original version of FileExists() did, but it proved to be buggy and gave wrong results at times due to errors while calculating the age, so it was rewritten to rely on file attributes instead (see Why is GetFileAttributes the way old-timers test file existence?), and later to include some permission checks, too (can't access the attributes? If it is because the file is locked or access is denied, the file exists).
  5. Remy Lebeau

    Efficient list box items with Frames

    Using a TFrame is fine, but you can't combine that with a VCL TListBox. Putting the TFrame objects onto a TPanel or even a TScrollBox would make more sense.
  6. Remy Lebeau

    Any advice when to use FileExists?

    The FileExists() call becomes redundant if you make _IsFileValidForParsing() return False for a non-existing file. But really, I would suggest completely re-writing this loop to actually open the File, decide whether to call _ParseAsA() or _ParseAsB() based on the content of the opened file rather than just its filename, and wrap the loop code in a try/except to handle IO errors. For instance: procedure MainMethod; var File: string; begin for File in GetMainPath do begin if _IsFileValidForParsing(File) then // <-- OK to leave in only if it checks just the filename and not the content ... begin try FS := TFileStream.Create(File, fmOpenRead or fmShareDenyWrite); try if _FileIsA(FS) then _ParseAsA(FS) else if _FileIsB(FS) then _ParseAsB(FS); finally FS.Free; end; except // handle error as needed ... end; end; end; end;
  7. Can you lock this thread from further posts?
  8. Note that C++11 addes the 'override' keyword to the language, and its use is highly recommended to discover typos at compile-time. If a derived class method wants to override a base class virtual method, an explicit 'override' allows the compiler to validate that the overriding method actually matches the signature of an available base virtual method, if not the compile fails. Without the explicit 'override', a mistyped overriding method would be an overload instead, which can lead to logic errors not detectable until run-time.
  9. If you want to use the same name with different parameters, you have to declare both methods as 'overload', or else declare the TBar method as 'reintroduce', which will still hide the TFoo method. Otherwise, I would just rename the TBar method to something more meaningful: TFoo = class procedure Terminate(aNow: Boolean); virtual; end; TBar = class(TFoo) procedure TerminateNow(); end; procedure TBar.TerminateNow(); begin inherited Terminate(True); end;
  10. Remy Lebeau

    TOpenDialog.FileName encoding problems on Mac OS

    As they should be. Makes sense, since you are saving to an ANSI database field, so you are invoking a lossy Unicode->ANSI conversion. Because you are still invoking a conversion to ANSI. Have you tried switching the database column to nvarchar, and using TField.AsWideString?
  11. Borland tried that once before. The CLX framework in Delphi 6 was a cross-platform version of the VCL. Most of the interfaces remained the same, just the internal implementations changed. That didn't work out so well for them, CLX had to be scrapped.
  12. Multi-threading issues for TBitmap were addressed in 10.2 Tokyo: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/What's_New#Multi-Threading_Support_for_TBitmap.2C_TCanvas_and_TContext3D
  13. TBitmap works in a thread, so you are probably just not using it correctly. But, if threading is not an option, then I suggest to break up your calculations into small chunks that you can execute asynchronously without blocking the UI thread, such as with TThread.ForceQueue(). Execute a chunk and queue the next chunk, then return to the main message loop. When ready, the next chunk will execute, queue the next chunk, return to the main message loop, and so on, until the calculations are finished.
  14. Remy Lebeau

    [iOS ANDROID] MessageDialog Modal

    Android does not support modal dialogs, you MUST use asynchronous dialogs instead, such as via TDialogServiceAsync.MessageDialog().
  15. The answer you were given on StackOverflow suggested you use a background thread, which is what you should be using. Start a background thread on app startup to perform your time-consuming work. Have the thread use TThread.Synchronize() or TThread.Queue(), or simply assign a handler to the TThread.OnTerminate event, to communicate back to the main thread when the background thread is finished. Have your UI display the trackbar by default, and then hide the trackbar when the background thread is finished. You don't need to in this situation. The OnIdle event is triggered whenever the main message loop receives a new message and then finishes processing all pending messages. Don't rely on the timing of that event to drive your app's logic. Application.ProcessMessages() is broken in 10.3. You shouldn't be using it at all anyway. Do the pre-calculation in a background thread, and have the background thread post status updates to the main thread as needed. Let the main thread update the UI on its own schedule.
  16. Remy Lebeau

    My android app restarts on permission request

    Which circumstances exactly? ForceDirectories() SHOULD succeed without error in that case. Its job is to ensure a path exists, not to report an error if it does exist. The only time it should ever raise an exception is if the input path is blank. Anything else should cause it to simply return True if the path already exists or was successfully created, or False otherwise. If it is not working that way, that is a bug for Embarcadero to fix.
  17. Remy Lebeau

    Delphi Rio Android application.processMessages; dosent work

    Doubtful, since transitions are meant to be used asynchronously. TIntAnimation has an OnFinish event, you are supposed to use that instead of using a blocking wait loop on the Running property. Good thing SetActiveTabWithTransitionASync() has an option to skip the actual transition.
  18. Remy Lebeau

    My android app restarts on permission request

    and a look at it in the source code verifies it. Last time I checked, ForceDirectories() DOES NOT raise an exception if the specified directory already exists. Are you sure the documentation you quoted is referring to ForeceDirectories() and not TDirListBox? Which source code did you "verify" exactly? The ForceDirectories() documentation does warn against calling it with a blank string, which I accidentally did in my example above if TPath.GetSharedPicturePath() returns a blank string.
  19. Only if you use the syntax you showed, yes. An alternative would be to use the TList<T>.List property instead, which gives you somewhat more direct access to the internal array: procedure TForm1.FormCreate(Sender: TObject); var ... vList: TList<TRecLine>; vNode: PVirtualNode; ... begin ... vList.List[0].VirtualNode := vNode; ... end; Or, use the TList<T>.PList property instead, which is as bare-bones as you can get to the internal array: type TRecLine = record ... end; PRecLine = ^TRecLine; procedure TForm1.FormCreate(Sender: TObject); var ... vList: TList<TRecLine>; vNode: PVirtualNode; ... begin ... (PRecLine(vList.PList)+0{Index}).VirtualNode := vNode; ... end; Do note that System.TArray<T> (not to be confused with System.Generics.Collections.TArray<T>) has supported insert and concatenation operations since XE7. See String-Like Operations Supported on Dynamic Arrays and Dynamic Arrays in Delphi XE7. VST doesn't care how you store your data, only that you should store a user-defined value inside each VST node so you can get back to your data given a node when needed. The best way to do that would be to store an actual TRecLine pointer in the node, where that pointer points to an item in your TList/TArray. But, if you are going to be adding/removing items from your TList/TArray, then that gets dangerous since you will be invalidating stored pointers. The next best option would be to store a TList/TArray index in each node instead, but then you have to worry about invalidating stored indexes. The next best option might be to store a unique ID number in each TRecLine, and then store those IDs in each node, and look for them in your TList/TArray when needed, but then you get into performance issues having to re-iterate the TList/TArray all the time. Certainly other options may exist, but what they are really depends on the design of your data and how you intend to use/manipulate it.
  20. Even though TList<T> uses a dynamic array internally, accessing its elements is very different than accessing the elements of TArray<T>. In your case, accessing vArray[n] is direct access to your record items, but vList[n] uses the TList<T>.Items property, which returns a copy of your record items, and you can't modify a copy inline the way you are trying to. You would have to manually save that copy to a variable and reassign it back to the TList<T> after modifying it, eg: procedure TForm1.FormCreate(Sender: TObject); var vArray: TArray<TRecLine>; vList: TList<TRecLine>; vNode: PVirtualNode; vRec: TRecLine; begin ... vArray[0].VirtualNode := vNode; // OK //vList[0].VirtualNode := vNode; // NOT OK! vRec := vList[0]; vRec.VirtualNode := vNode; vList[0] := vRec; ... end;
  21. 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 );
  22. Remy Lebeau

    Delphi Rio Android application.processMessages; dosent work

    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.
  23. 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.
  24. Remy Lebeau

    Delphi Rio Android application.processMessages; dosent work

    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.
  25. 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.
×