-
Content Count
2343 -
Joined
-
Last visited
-
Days Won
95
Everything posted by Remy Lebeau
-
The 2nd JSON object in the "conditions" array DOES NOT have a "bar_sea_level" value in it, so calling JsonObject.GetValue('bar_sea_level') returns nil. The "bar_sea_level" value is actually in the 3rd JSON object in the array, so you need to use Items[2] instead of Items[1]: var JsonValue: TJSONValue; JsonObject, JsonData: TJSONObject; JsonConditions: TJSONArray; Branch: string; ... begin ... JsonValue := TJSONObject.ParseJSONValue(st); if JsonValue <> nil then try JsonObject := JsonValue as TJSONObject; JsonData := JsonObject.GetValue('data') as TJSONObject; JsonConditions := JsonData.GetValue('conditions') as TJSONArray; JsonObject := JsonConditions.Items[0] as TJSONObject; Branch := JsonObject.GetValue('temp').Value; memo1.Lines.add('Parsed temperature '+branch); JsonObject := JsonConditions.Items[2] as TJSONObject; Branch := JsonObject.GetValue('bar_sea_level').Value; memo1.Lines.add('Parsed barometer '+branch); finally JsonValue.Free; end; ... end; It helps to view the JSON in an indented format so you can more clearly see the actual hierarchy of values, objects, and arrays, eg: { "data":{ "did":"001D0A710197", "ts":1557136813, "conditions":[ { "lsid":223656, "data_structure_type":1, "txid":1, "temp": 52.7, "hum":66.3, "dew_point": 41.8, "wet_bulb": 46.2, "heat_index": 51.7, "wind_chill": 52.7, "thw_index": 51.7, "thsw_index": 49.7, "wind_speed_last":0.00, "wind_dir_last":0, "wind_speed_avg_last_1_min":0.00, "wind_dir_scalar_avg_last_1_min":null, "wind_speed_avg_last_2_min":0.00, "wind_dir_scalar_avg_last_2_min":null, "wind_speed_hi_last_2_min":0.00, "wind_dir_at_hi_speed_last_2_min":0, "wind_speed_avg_last_10_min":0.00, "wind_dir_scalar_avg_last_10_min":null, "wind_speed_hi_last_10_min":0.00, "wind_dir_at_hi_speed_last_10_min":0, "rain_size":2, "rain_rate_last":0, "rain_rate_hi":0, "rainfall_last_15_min":0, "rain_rate_hi_last_15_min":0, "rainfall_last_60_min":0, "rainfall_last_24_hr":0, "rain_storm":null, "rain_storm_start_at":null, "solar_rad":0, "uv_index":0.0, "rx_state":0, "trans_battery_flag":0, "rainfall_daily":0, "rainfall_monthly":0, "rainfall_year":0, "rain_storm_last":null, "rain_storm_last_start_at":null, "rain_storm_last_end_at":null }, { "lsid":223554, "data_structure_type":4, "temp_in": 69.1, "hum_in":38.2, "dew_point_in": 42.6, "heat_index_in": 66.8 }, { "lsid":223553, "data_structure_type":3, "bar_sea_level":29.932, "bar_trend": 0.028, "bar_absolute":29.404 } ] }, "error":null } Now you can clearly see that there are 3 objects in the "conditions" array.
-
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
You can't. The apps have to actually be build with Unity. -
Of course there is. The easiest way would be to update your TLogInForm.Execute() method to return the UserID when it exits, and then add a UserID variable to your TMainForm class that you can assign to after the MainForm has been created, eg: unit LogInFrm; interface ... type TLogInForm = class public ... class function Execute: string; ... end; ... implementation class function TLogInForm.Execute: string; begin ... if LoggedIn then Result := UserID else Result := ''; end; end. program Passwordapp; uses Vcl.Forms, System.UITypes, MainFrm in 'MainFrm.pas' {MainForm} , LogInFrm in 'LogInFrm.pas' {LogInForm}; {$R *.res} var UserID: string; begin // UserID := TLogInForm.Execute; if UserID <> '' then begin Application.Initialize; Application.CreateForm(TMainForm, MainForm); MainForm.UserID := UserID; Application.Run; end ... end. Otherwise, just have the Login Form save the UserID to a global variable if successfully logged in, and then your MainForm can use that global variable when needed. PS: {code} blocks do not work on this forum. You need to use the "Code" (</>) button on the post editor's toolbar instead.
-
ParseJSONValue() is a 'class' method, so you don't need to create a TJSONObject instance in order to call it: JsonValue := TJSONObject.ParseJSONValue(st); This is creating a memory leak, because you are losing your pointer to the original TJSONValue object that ParseJSONValue() returns. You need to Free() that original object when you are done using it. You are not doing that. You should use additional variables for your typecasts, eg: var ... JsonValue: TJSONValue; JSonObject: TJOSNObject; JsonData: TJSONObject; JsonConditions: TJSONValue; begin ... JsonValue := TJSONObject.ParseJSONValue(st); if JsonValue <> nil then try JsonObject := JsonValue as TJSONObject; JsonData := JsonObject.GetValue('data') as TJSONObject; JsonConditions := JsonData.GetValue('conditions'); if (JsonConditions is TJSONArray) then begin JsonObject := (JsonConditions as TJSONArray).Items[0] as TJSONObject; Branch := JsonObject.GetValue('temp').Value; end; finally JsonValue.Free; end; ... end; A "second bracketed data" of what exactly? Which element of the JSON document has the pairs you are looking for? You need to be more specific. Are you referring to the "conditions" array? If so, then simply use Items[1] to access the second array element, Items[2] for the third element, etc. If that is not what you need, then what is stopping you from accessing the desired second array? What are you actually stuck on? Where is that array located exactly? It is really hard to help you with that when you have not shown the actual JSON you are working with. Please show the actual JSON.
-
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
The reason was clearly stated in Google's deadline anouncement: -
Hide/Ignore comments from certain individuals?
Remy Lebeau replied to DJSox's topic in Community Management
Is there any other interface? -
screen shot - reliable method
Remy Lebeau replied to johnnydp's topic in RTL and Delphi Object Pascal
The Desktop Duplication API was actually introduced in Windows 8. In Windows Vista and Windows 7, you can use the Magnification API instead (but with some restrictions). Windows 10 build 1803 introduces a new Screen Capture API. -
Tools Api: how to detect a key has been pressed in the editor window?
Remy Lebeau replied to santiago's topic in Delphi IDE and APIs
TApplicationEvents works by assigning its own handlers to the TApplication events. So if the IDE, or another plugin, assigns its own handlers to the TApplication events directly then TApplicationEvents will not work correctly. As it should, since TApplicationEvents is specifically designed to be used that way. When everyone uses TApplicationEvents handlers instead of TApplication handlers, then everyone gets notified of events. -
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
Not sure what you are trying to say. Delphi itself only runs on Windows, but it compiles Android apps as native code, using a small Java stub to load and run the native code. So Android apps compiled with Delphi are affected by the August 1 2019 deadline. -
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
Except for 32bit games that use Unity 5.6.6+. 32bit-only updates for those apps will continue to be accepted until August 2021. -
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
No, but as a TeamB member, I'm usually invited to betas for Delphi and C++Builder. I haven't received any invitation for a new version yet. Scratch that. I just received notification that invitations are now starting to be sent out to prospective testers. -
The Android 64bit deadline warnings have started
Remy Lebeau replied to Yaron's topic in Cross-platform
SHOULD being the figurative work here. There has been NO announcement from Embarcadero since August 2018 about how they plan to address the Android 64bit issue. There has been NO announcement of a new Delphi version in the works to include a 64bit Android compiler. AFAIK, there is no beta at this time. -
Yes, exactly. It needs a write lock, not a read lock. Yes, you are wrong. A read lock allows multiple threads to read shared data at the same time. A write lock allows only 1 thread at a time to modify shared data. Since you are modifying data (adding a new string to the TStringList, thus modifying its internal memory array and its Count), you need a write lock. You are getting an AV because your read lock is allowing multiple threads to modify the TStringList at the same time, trampling on each other's toes, so to speak.
-
Anon methods passed as event handlers?
Remy Lebeau replied to David Schwartz's topic in RTL and Delphi Object Pascal
You don't have to create instances of the class if you use a non-static 'class' method for the event handler. It still has an implicit Self parameter, but it points to the class type instead of an object instance. Works fine with events, as long as the method doesn't need to access any members of a particular object instance. -
Anon methods passed as event handlers?
Remy Lebeau replied to David Schwartz's topic in RTL and Delphi Object Pascal
Sometimes I do that, too. But writing a class to wrap a standalone function is not always as convenient as just using the function by itself. -
Anon methods passed as event handlers?
Remy Lebeau replied to David Schwartz's topic in RTL and Delphi Object Pascal
And yet, it CAN be done with plain/static functions. I do it all the time when writing test apps that have no UIs. It is just a matter of adding an explicit Self parameter to the function, populating a TMethod record with the address of the function in the Code field and an arbitrary value in the Data field, and then assigning that TMethod to the target event via a type-cast. -
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).