Jump to content

Remy Lebeau

Members
  • Content Count

    2343
  • Joined

  • Last visited

  • Days Won

    95

Everything posted by Remy Lebeau

  1. Remy Lebeau

    New to Json

    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.
  2. Remy Lebeau

    The Android 64bit deadline warnings have started

    You can't. The apps have to actually be build with Unity.
  3. Remy Lebeau

    Get UserID from LogIn form at startup.

    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.
  4. Remy Lebeau

    New to Json

    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.
  5. Remy Lebeau

    The Android 64bit deadline warnings have started

    The reason was clearly stated in Google's deadline anouncement:
  6. Is there any other interface?
  7. Remy Lebeau

    screen shot - reliable method

    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.
  8. 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.
  9. Remy Lebeau

    The Android 64bit deadline warnings have started

    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.
  10. Remy Lebeau

    The Android 64bit deadline warnings have started

    Except for 32bit games that use Unity 5.6.6+. 32bit-only updates for those apps will continue to be accepted until August 2021.
  11. Remy Lebeau

    The Android 64bit deadline warnings have started

    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.
  12. Remy Lebeau

    The Android 64bit deadline warnings have started

    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.
  13. Remy Lebeau

    DynArraySetLength exception

    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.
  14. Remy Lebeau

    Anon methods passed as event handlers?

    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.
  15. Remy Lebeau

    Anon methods passed as event handlers?

    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.
  16. Remy Lebeau

    Anon methods passed as event handlers?

    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.
  17. Remy Lebeau

    Anon methods passed as event handlers?

    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.
  18. Remy Lebeau

    Autocompletion for TEdits revisited

    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
  19. Remy Lebeau

    On-demand ARC feature discussed

    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).
  20. Remy Lebeau

    Mark post as answered

    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.
  21. Remy Lebeau

    CreateBlobStream

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

    Exclude already read posts

    Is this what you are looking for?
  25. 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).
×