-
Content Count
2633 -
Joined
-
Last visited
-
Days Won
109
Everything posted by Remy Lebeau
-
Anon methods passed as event handlers?
Remy Lebeau replied to David Schwartz's topic in RTL and Delphi Object Pascal
That still won't work. No amount of syntax sugar will account for the fact that an anonymous procedure simply does not carry a Self parameter that the event caller can pass in, or the fact that an anonymous procedure is implemented using a compiler-generated reference-counted interface, which is radically different than a simple method pointer. Event handlers are currently implemented under the hood using the TMethod record. When a event owner wants to call an event handler, the compiler has to generate a particular set of code to call the method that is referenced by the record, inserting the Self parameter that is referenced by the record. In order to call an anonymous procedure, the compiler has to generate a completely different set of code to call the Invoke() method of the anonymous interface. There is currently no way for the compiler to decide at the call site at compile-time which set of code it needs to generate based on what kind of handler is assigned at runtime. Embarcadero would have to redesign the way TMethod and/or anonymous procedures are implemented in order to make anonymous procedures be compatible with TMethod, in a way that does not break existing user code. I suppose Embarcadero could make it so when an anonymous procedure is assigned to a TMethod, the record is flagged in a way that lets the compiler know the Data field points to an anonymous interface instead of an object pointer, and the Code field points to the interface's Invoke() method, so the compiler can then branch with the appropriate code to execute Invoke() on the interface. But that still leaves the issue that anonymous procedures are reference counted, and TMethod doesn't know anything about reference counting (outside of object ARC on mobile platforms - which is going away in upcoming Delphi versions). So that is even more boiler plate code that the compiler has to implement everywhere TMethod is used to account for the reference count. The Managed Records feature that was planned for 10.3 might have solved that, but that feature had to be deferred until 10.4 for technical reasons. -
Autocompletion for TEdits revisited
Remy Lebeau replied to dummzeuch's topic in Tips / Blogs / Tutorials / Videos
I also once wrote an article about IAutoComplete, for the C++Builder Journal. The archive of that article is on my website in the "Articles" section: Using Microsoft's Auto-Completion Framework -
On-demand ARC feature discussed
Remy Lebeau replied to AlekXL's topic in RTL and Delphi Object Pascal
Many C++ compilers do offer that as an extension. C++Builder has 'try/__finally'. VC++ and CLang have '__try/__finally', etc. But proper use of RAII trumps the need for try/finally at all. But if you want, try/finally can be emulated in pure C++ using lambdas and pre-compiler macros (see this, for example).- 52 replies
-
- arc
- memory management
-
(and 3 more)
Tagged with:
-
Not that I can see. But I did find some references to a "Best Answer" plugin that can be installed in this forum's software. Maybe this forum's admin will consider that in the future.
-
CreateBlobStream
Remy Lebeau replied to Ole Ekerhovd's topic in Algorithms, Data Structures and Class Design
Why are you freeing the TBlobField at all? You don't own it, the Query component does, so you should not be freeing it at all. Also, you are leaking the TMemoryStream that you create (which you should not be creating in the first place). The only thing you should be freeing manually is the TStream that CreateBlobStream() returns, eg: var aBlob : TField; aStream : TStream; begin aBlob := aQuery.FieldByName('Memo'); aStream := aQuery.CreateBlobStream(aBlob, bmRead); try aMemo.Lines.LoadFromStream(aStream); finally aStream.Free; end; end; -
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.
-
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;
-
Is this what you are looking for?
-
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).
-
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.
-
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;
-
Delphi compiler need to be opensourced
Remy Lebeau replied to AlekXL's topic in RTL and Delphi Object Pascal
Can you lock this thread from further posts? -
Class inheritance and hides method
Remy Lebeau replied to Jacek Laskowski's topic in Algorithms, Data Structures and Class Design
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. -
Class inheritance and hides method
Remy Lebeau replied to Jacek Laskowski's topic in Algorithms, Data Structures and Class Design
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; -
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?
-
Delphi compiler need to be opensourced
Remy Lebeau replied to AlekXL's topic in RTL and Delphi Object Pascal
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. -
When is a Delphi form actually visible to the user on Android?
Remy Lebeau replied to Yaron's topic in Cross-platform
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 -
When is a Delphi form actually visible to the user on Android?
Remy Lebeau replied to Yaron's topic in Cross-platform
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. -
Android does not support modal dialogs, you MUST use asynchronous dialogs instead, such as via TDialogServiceAsync.MessageDialog().
-
When is a Delphi form actually visible to the user on Android?
Remy Lebeau replied to Yaron's topic in Cross-platform
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. -
My android app restarts on permission request
Remy Lebeau replied to Yaron's topic in Cross-platform
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. -
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.
-
My android app restarts on permission request
Remy Lebeau replied to Yaron's topic in Cross-platform
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. -
TArray vs TList with VirtualStringTree
Remy Lebeau replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
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. -
TArray vs TList with VirtualStringTree
Remy Lebeau replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
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;