Jump to content

Navid Madani

Members
  • Content Count

    34
  • Joined

  • Last visited

Everything posted by Navid Madani

  1. RAD Studio 11.3 (2024 build) was posted on my.embarcadero.com on 2/20/2024. Does anyone have further details about this re-release? Thanks in advance.
  2. Navid Madani

    When will be binding to Swift libraries possible ?

    Looking at Swift 5.9 release notes, there is talk of bidirectional C++ interoperability. Has anyone tried it? https://www.swift.org/documentation/cxx-interop/
  3. TDirectory.GetFiles in System.IOUtils has a bug on macOS: In the returned dynamic string array, valid files with names that contain three or four consecutive periods are not included. I ended up having to use macOS system functions instead. If it would help others who could run into this problem, my solution is below. Please post if you find any errors. macOS programming is certainly not my forte. unit Nm.IOUtils; interface uses System.SysUtils , System.Types {$IFDEF MACOS} , Macapi.ObjectiveC , Macapi.CocoaTypes , Macapi.Foundation , Macapi.Helpers {$ENDIF} ; {$IFDEF MACOS} type EItemType = (itFiles, itDirectories); EItemTypes = set of EItemType; function macOS_GetDirectoryContents(const DirectoryPath: string; const ItemTypes: EItemTypes): TStringDynArray; {$ENDIF} implementation {$IFDEF MACOS} function macOS_GetDirectoryContents(const DirectoryPath: string; const ItemTypes: EItemTypes): TStringDynArray; const ARRAYSIZEINCREMENT = 32; var FileManager: NSFileManager; URL, FileURL: NSURL; FileEnum: NSDirectoryEnumerator; Options: NSDirectoryEnumerationOptions; Error: NSError; IsDirectoryVal: Pointer; IsDirectory: Boolean; Candidate: string; i: Integer; ArraySize: Integer; begin FileManager := TNSFileManager.Wrap(TNSFileManager.OCClass.defaultManager); URL := TNSURL.Wrap(TNSURL.OCClass.fileURLWithPath(StrToNSStr(DirectoryPath), True)); Options := NSDirectoryEnumerationSkipsSubdirectoryDescendants; FileEnum := FileManager.enumeratorAtURL(URL, nil, Options, nil); FileURL := TNSURL.Wrap(FileEnum.nextObject); ArraySize := ARRAYSIZEINCREMENT; SetLength(Result, ArraySize); i := 0; while Assigned(FileURL) and FileURL.isFileURL do begin IsDirectoryVal := nil; if FileURL.getResourceValue(@IsDirectoryVal, StrToNSStr('NSURLIsDirectoryKey'), @Error) and Assigned(IsDirectoryVal) then begin if i >= ArraySize then begin Inc(ArraySize, ARRAYSIZEINCREMENT); SetLength(Result, ArraySize); end; Candidate := UTF8ToString(FileURL.path.UTF8String); IsDirectory := TNSNumber.Wrap(IsDirectoryVal).boolValue; if IsDirectory then begin if (itDirectories in ItemTypes) then begin Result[i] := Candidate; Inc(i); end; end else begin if (itFiles in ItemTypes) then begin Result[i] := Candidate; Inc(i); end; end; end; FileURL := TNSURL.Wrap(FileEnum.nextObject); end; SetLength(Result, i); end; {$ENDIF} end.
  4. I have encountered a strange problem: Delphi Athens is running via Parallels on Apple Silicone. In the following demo, none of the destroyers are called when targeting macOS ARM 64, while everything works OK targeting Windows). Also, I can get the Windows target to report the memory leak when I comment out fDebug.Free in FormDestroy, but not with macOS ARM 64. Can anyone else reproduce this? Am I missing something? Thanks! unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TForm1 = class(TForm) procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); private fDebug: TObject; public destructor Destroy; override; end; var Form1: TForm1; implementation {$R *.fmx} // TForm1.FormDestroy is not called when Target is macOS ARM 64-bit procedure TForm1.FormDestroy(Sender: TObject); begin ShowMessage('FormDestroy Called'); fDebug.Free; end; // TForm1.Destroy is not called when Target is macOS ARM 64-bit destructor TForm1.Destroy; begin ShowMessage('Destroy Called'); inherited; end; procedure TForm1.FormCreate(Sender: TObject); begin fDebug := TObject.Create; end; end.
  5. https://quality.embarcadero.com/browse/RSP-26436 Such a fundamental pattern being broken, it could be a sign of more ominous problems.
  6. In the DUnitX tests below, the first 4 cases fail: the last character is omitted. It seems that for each hyphen added after the first, an extra character from the end is not written to the TClientDataSet field. Is this normal behavior? Thanks in advance. unit uCDSTest; interface uses DUnitX.TestFramework , Data.DB , Datasnap.DBClient ; type [TestFixture] TClientDataSetTest = class private fCDS: TClientDataSet; public [Setup] procedure Setup; [TearDown] procedure TearDown; [Test] [TestCase('Test0','S0002-9270(99)00035-0')] [TestCase('Test1','S0002-9270(99)00035-1')] [TestCase('Test2','S0002-9270(99)00035-2')] [TestCase('Test3','S0002-9270(99)00035-3')] [TestCase('Test4','S0002-9270(99)000353')] [TestCase('Test5','S00029270(99)00035-3')] [TestCase('Test6','S00-02-9270(99)00035-3')] [TestCase('Test7','S00-02-92-70(99)00035-3')] procedure TestCDS(const AValue : string); end; implementation const cFieldname = 'AFieldName'; procedure TClientDataSetTest.Setup; var fd: TFieldDef; begin fCDS := TClientDataSet.Create(nil); fd := fCDS.FieldDefs.AddFieldDef; fd.Name := cFieldname; fd.DataType := ftString; fCDS.CreateDataSet; end; procedure TClientDataSetTest.TearDown; begin fCDS.Free; end; procedure TClientDataSetTest.TestCDS(const AValue : string); begin fCDS.Insert; fCDS.Edit; fCDS.FieldByName(cFieldname).AsString := AValue; fCDS.Post; Assert.AreEqual(AValue, fCDS.FieldByName(cFieldname).AsString, 'Assignment failed.'); end; initialization TDUnitX.RegisterTestFixture(TClientDataSetTest); end.
  7. Navid Madani

    TClientDataSet's odd behavior with hyphens in string fields

    Duh! Thank you! I should have tested adding other characters on the tests. When the REST API added the hyphens, that's when everything broke down ...
  8. Thanks, David. No, I don't shun any value types, but we're talking about large record sets, too large for the stack. I was referring to bugs that creep in when the integrity of (mutable) data on the heap is not guaranteed, when variables are mutable. Certainly, records can emulate data integrity patterns with private fields, but that approach is not front and center with Delphi records. Interfaces too can cause memory leaks, and be complicated when weak references are needed. But again, it's an overall safer pattern in Delphi, at least in my experience. Interfaces also help write testable and more easily maintainable code by keeping things decoupled. And yes, try-finally blocks are written by the compiler, but compilers ultimately write machine code. That's the whole purpose of using a high-level language: so we don't have to write all that code. It helps keep things readable. I agree that it's the same basics with memory allocation and release patterns. But again, different languages implement them in different ways, and some patterns are more idiomatic for a specific language, as in this case. I think our discourse is more over language semantics than technical. While I understand and appreciate your points, I am not convinced to change my approach to large amounts of data on the heap.
  9. Navid Madani

    TClientDataSet's odd behavior with hyphens in string fields

    Thank Dmitry, but I did not grasp the implication of your terse comment. The size of string fields needs to be set?
  10. Value types are copied unless passed by reference. That can impact performance. Classes and interfaces would be more efficient. Interfaces can emulate data immutability and prevent bugs. Interfaces obviate the need for try-finally blocks, keep code cleaner and easier to maintain. Interfaces can prevent memory leaks. It is not always a simple case of memory allocation/de-allocation with records on the heap. There can be issues with access violations, and hard to detect bugs if record pointers are accessed but point to invalid data. Again, I said that is what I do when using Delphi as opposed to C.
  11. Depending on what you are trying to do, keeping records on the heap could become an anti-pattern and cause problems. For one, your records could be duplicated on assignments, on the stack, and if your application relies on their consistency, bugs can creep in. Unlike languages like Rust and Swift, all variables in Delphi are mutable. If I needed a large dataset on. the heap, I would either wrap the records in a class or define a class, and in order to maintain their integrity, I would use interface references to access them and avoid memory leaks. Just my two cents.
  12. Navid Madani

    How to test FDConnection with SQLite

    Check the Params TStrings property to check for the database name and other properties. procedure TForm1.Button1Click(Sender: TObject); begin if FDConnection1.Params.Values['Database'] <> '' then ShowMessage('Database name is ' + FDConnection1.Params.Values['Database']); end;
  13. Navid Madani

    TClientDataSet's odd behavior with hyphens in string fields

    Reported as bug: RSP-44009.
  14. When the project file has conditional compiler directives, the source indexer omits files that should be included. For example, in the uses section of the .dpr file quoted below: {$IFDEF MSWindows} uWinSpawn in '..\Units\Win\uWinSpawn.pas', uReadPipeThread in '..\Units\Win\uReadPipeThread.pas', {$ENDIF } {$IFDEF MACOS} uMacOSSpawn in '..\Units\macOS\uMacOSSpawn.pas' {$ENDIF } uProcessSpawn in '..\Units\uProcessSpawn.pas'; When MSWindows is defined and not MACOS, in my case, ..\Units\uMacOSSpawn.pas is indexed, but not ..\Units\uProcessSpawn.pas. Thanks to Uwe for his outstanding work maintaining and updating MMX Tools. Navid
  15. Navid Madani

    MMX 15.1.8 build 2580: Possible Source Indexer Bug?

    I'll take that back: not a bug - each time I add a unit to the project, the IDE messes up the conditional defines, sometimes inserting two uses clauses into the DPR. While editing, I also erroneously deleted the comma after the unit in the excluded MACOS clause. Restoring the missing comma allowed MMX to index everything.
  16. Is it just me, or is the entire refactoring functionality in 12/Athens broken? Sync Prototypes does not work, refactor rename just replaces the selected identifier instance, and ignores every other identifier occurrence including those in the visible file, ...
  17. Navid Madani

    Delphi 12 Athens Refactoring Broken

    Deprectated? https://docwiki.embarcadero.com/RADStudio/Athens/en/Refactor_Menu https://docwiki.embarcadero.com/RADStudio/Athens/en/Rename_symbol_name_(Delphi) ...
  18. Navid Madani

    Delphi 12 Athens Refactoring Broken

    Thank you, but mine is a different problem (P.S. modelling was selected during installation). Refactoring options are available, but they do not work.
  19. Navid Madani

    Delphi 12 Athens Refactoring Broken

    Agree. Unfortunately, version 12's installation made my previous versions unusable.
  20. Navid Madani

    Delphi 12 Athens Refactoring Broken

    So, what’s new in Athens boils down to multiline strings at the expense of: - No more refactoring. - Range-check errors with bitwise operations. Ranting aside, using GREP and RegEx would work, but would be error-prone. Years ago, before Uwe took over ModelMaker Tools, it wreaked havoc while trying to refactor one of my medium-sized projects. So I did not use its refactoring capabilities again. I am curious what options people are using.
  21. Navid Madani

    MacOS's dylib doesn't work fine.

    Since you are calling your function from C, try declaring ShowForm as cdecl instead of stdcall.
  22. unit uMacOSSpawn; interface uses System.SysUtils , System.Classes , Macapi.Foundation , Macapi.ObjectiveC , Macapi.ObjCRuntime , Macapi.Helpers ; type TProcessSpawn = class(TOCLocal) private fTask: NSTask; fInputPipe: NSPipe; fOutputPipe: NSPipe; fErrorPipe: NSPipe; fNotify: TNotifyEvent; fOutput: string; function GetNotify: TNotifyEvent; procedure SetNotify(const Value: TNotifyEvent); function GetOutput: string; public constructor Create(FileName: PWideChar); destructor Destroy; override; procedure NotificationReceived(notification: NSNotification); cdecl; procedure Input(const aText: string); property Output: string read GetOutput; property Notify: TNotifyEvent read GetNotify write SetNotify; end; implementation { TProcessSpawn } constructor TProcessSpawn.Create(FileName: PWideChar); var FileHandleLocObj: ILocalObject; InputLocObj: ILocalObject; OutputLocObj: ILocalObject; ErrorLocObj: ILocalObject; begin inherited Create; fInputPipe := TNSPipe.Create; fOutputPipe := TNSPipe.Create; fErrorPipe := TNSPipe.Create; fTask := TNSTask.Wrap(TNSTask.Wrap(TNSTask.OCClass.alloc).init); fTask.setLaunchPath(StrToNSStr(FileName)); if Supports(fInputPipe, ILocalObject, InputLocObj) and Supports(fOutputPipe, ILocalObject, OutputLocObj) and Supports(fErrorPipe, ILocalObject, ErrorLocObj) and Supports(fOutputPipe.fileHandleForReading, ILocalObject, FileHandleLocObj) then begin fTask.setStandardInput(InputLocObj.GetObjectID); fTask.setStandardOutput(OutputLocObj.GetObjectID); fTask.setStandardError(ErrorLocObj.GetObjectID); fOutputPipe.fileHandleForReading.acceptConnectionInBackgroundAndNotify; fOutputPipe.fileHandleForReading.readInBackgroundAndNotify; TNSNotificationCenter.Wrap(TNSNotificationCenter.OCClass.defaultCenter).addObserver( Self.GetObjectID, sel_getUid('NotificationReceived:'), NSFileHandleReadCompletionNotification, FileHandleLocObj.GetObjectID ); fTask.launch; // fNSTask.waitUntilExit; end else raise Exception.Create('ILocalObject interface not supported.'); end; destructor TProcessSpawn.Destroy; begin fTask.release; fInputPipe.release; fOutputPipe.release; fErrorPipe.release; inherited; end; function TProcessSpawn.GetNotify: TNotifyEvent; begin Result := fNotify; end; function TProcessSpawn.GetOutput: string; begin Result := fOutput; end; procedure TProcessSpawn.NotificationReceived(notification: NSNotification); var data: NSData; str: NSString; notifHandle: NSFileHandle; begin notifHandle := TNSFileHandle.Wrap(notification.&object); if notifHandle = fOutputPipe.fileHandleForReading then begin data := fOutputPipe.fileHandleForReading.availableData; str := TNSString.Wrap(TNSString.Alloc.initWithData(data, NSUTF8StringEncoding)); try fOutput := NSStrToStr(str); if Assigned(fNotify) then fNotify(Self); finally str.release; end; end; end; procedure TProcessSpawn.Input(const aText: string); var data: NSData; str: NSString; begin str := StrToNSStr(aText + #10); data := str.dataUsingEncoding(NSUTF8StringEncoding); fInputPipe.fileHandleForWriting.writeData(data); end; procedure TProcessSpawn.SetNotify(const Value: TNotifyEvent); begin fNotify := Value; end; end. I am trying to create a unit that spawns a console process on macOS that receives text input through stdin, and returns the result through stdout. I am not an iOS programmer, but I think I managed to get things done correctly based on documentation I consulted, and the unit below compiles without errors or warnings. However, the .addObserver call in the constructor (line 67) causes a runtime exception: First chance exception at $00000001823C976C. Exception class 6. Process Project1 (2540). Is this my fault, or could this be a bug in Delphi's Objective-C bridge? Thanks to everyone in advance. P.S.: I should also mention that debugging the RTL using Delphi 12 Athens on my Apple Silicon device running macOS Sonoma and Windows 11 Pro for ARM virtualized with Parallels, hangs the IDE 😞
  23. unit uMacOSSpawn; interface uses System.SysUtils , System.Classes , System.Threading , Macapi.Foundation , Macapi.ObjectiveC , Macapi.ObjCRuntime , Macapi.Helpers ; type TStringCallback = procedure(const aText: string) of object; IProcessSpawn = interface ['{606A5174-5921-431C-AD57-DD037472CEF5}'] function GetNotify: TStringCallback; procedure SetNotify(const Value: TStringCallback); function GetOutput: string; procedure Input(const aText: string); property Output: string read GetOutput; property OutputNotify: TStringCallback read GetNotify write SetNotify; end; TProcessSpawn = class(TInterfacedObject, IProcessSpawn) strict private fNSTask: NSTask; fInputPipe: NSPipe; fOutputPipe: NSPipe; fOutputNotify: TStringCallback; fOutput: string; fOutputTask: ITask; function GetNotify: TStringCallback; procedure SetNotify(const Value: TStringCallback); function GetOutput: string; procedure SendNotification(const aToNotify: TStringCallback; const aOutput: string); function WaitForOutput: ITask; procedure Input(const aText: string); public constructor Create(const FileName: string; const aCallback: TStringCallback); destructor Destroy; override; end; implementation { TProcessSpawn } constructor TProcessSpawn.Create(const FileName: string; const aCallback: TStringCallback); var InputLocObj: ILocalObject; OutputLocObj: ILocalObject; begin inherited Create; fOutputNotify := aCallback; fInputPipe := TNSPipe.Create; fOutputPipe := TNSPipe.Create; fNSTask := TNSTask.Wrap(TNSTask.Wrap(TNSTask.OCClass.alloc).init); fNSTask.setLaunchPath(StrToNSStr(FileName)); if Supports(fInputPipe, ILocalObject, InputLocObj) and Supports(fOutputPipe, ILocalObject, OutputLocObj) then begin fNSTask.setStandardInput(InputLocObj.GetObjectID); fNSTask.setStandardOutput(OutputLocObj.GetObjectID); fNSTask.setStandardError(OutputLocObj.GetObjectID); fNSTask.launch; end else raise Exception.Create('ILocalObject interface not supported.'); end; destructor TProcessSpawn.Destroy; begin if fNSTask.isRunning then fNSTask.terminate; fNSTask.release; fInputPipe.release; fOutputPipe.release; fOutputNotify := nil; fOutputTask.Cancel; fOutputTask := nil; inherited; end; function TProcessSpawn.GetNotify: TStringCallback; begin Result := fOutputNotify; end; function TProcessSpawn.GetOutput: string; begin Result := fOutput; end; procedure TProcessSpawn.Input(const aText: string); var data: NSData; str: NSString; begin if not fNSTask.isRunning then raise Exception.Create('No running process!'); str := StrToNSStr(aText + #10); data := str.dataUsingEncoding(NSUTF8StringEncoding); fInputPipe.fileHandleForWriting.writeData(data); if fOutputTask = nil then fOutputTask := WaitForOutput; end; procedure TProcessSpawn.SendNotification(const aToNotify: TStringCallback; const aOutput: string); begin fOutputTask := nil; if Assigned(aToNotify) then begin TThread.Synchronize(nil, procedure begin aToNotify(aOutput); end); end; fOutputTask := WaitForOutput; end; procedure TProcessSpawn.SetNotify(const Value: TStringCallback); begin fOutputNotify := Value; end; function TProcessSpawn.WaitForOutput: ITask; begin Result := TTask.Create( procedure var data: NSData; str: NSString; begin data := fOutputPipe.fileHandleForReading.availableData; str := TNSString.Wrap(TNSString.Alloc.initWithData(data, NSUTF8StringEncoding)); try fOutput := NSStrToStr(str); SendNotification(fOutputNotify, fOutput); finally str.release; end; end); Result.Start; end; end. In case anyone would run into this issue, I simplified things by calling .dataAvailable which is a blocking call in a separate thread. The unit below (at least for now) is working for my purposes.
  24. Thanks, David. That was indeed what caused the exception. I noticed that pattern at least once in the docs I read and forgot. The pattern above was based on Objective-C code samples I studied. However, mine does not work as intended because the notification handler is not called back. I did not know about Kastri until now. I'll pursue the Kastri solution if tinkering with my own implementation remains fruitless. In any case, I just became a Kastri sponsor on GitHub. Thanks again.
×