Jump to content

Navid Madani

Members
  • Content Count

    36
  • Joined

  • Last visited

Posts posted by Navid Madani


  1. Is anyone else experiencing frequent (~ hourly), random IDE freezes, and unresponsiveness on Windows ARM, even with small and new projects? Task Manager shows 0% CPU activity. The only recourse has been to force-quit the  IDE using the Windows Task Manager and restart. A lot of productivity has been lost as a result.  I have installed Navigator, Bookmarks, TwineCompile, Bonus KSVC, Radiant Shapes, and MMX Code Explorer.  Oddly, GetIt claims that the "12.1 Patch 1 1.0" previously applied is not installed, and clicking "Install" does nothing.  I have already tried removing the IDE plugins without resolution. And just to add a rant, this is now my second uninstall/reinstall cycle of 12, which is enough for me.

     

    What is the best way to investigate (log files, etc.)?  

     

    Thanks in advance.


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

     


  3. 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.

     


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

  5. 35 minutes ago, David Heffernan said:

    So what would be the problem with that? That's what happens with vakue types. 

     

    Why would interfaces be needed to avoid memory leaks? 

    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.


  6. On 12/11/2023 at 3:35 AM, dormky said:

    I have a very, very large record that may cause memory issues for the stack if I use multiples instances of it in a procedure. I'd like to force delphi to allocate it on the heap so this doesn't happen.

    I need it to stay a record, it can't be a class. I have no problem with doing a bit of manual work in the procedure to get this, it doesn't have to be something baked in the framework and invisible to me as a dev.

     

    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.


  7. 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;

     


  8. 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.

     


  9. 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


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


  11. 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.

     

     

×