Jump to content

vfbb

Members
  • Content Count

    266
  • Joined

  • Last visited

  • Days Won

    30

Posts posted by vfbb


  1. 1 hour ago, vfbb said:

    The best thing to do at this point is to port the C# code.

    Wrong! Actually porting the C# code will be laborious and will require maintenance as the unicode is changed.

     

    The best solution at this point is to use apis:

     

    Windows - DWriteTextAnalyze
    Android - GraphemeCharsLength := JString.codePointAt(Index);
    iOS - CFStringGetRangeOfComposedCharactersAtIndex


  2. 14 hours ago, David Heffernan said:

    Why would you need to know this, unless you are rendering the text 

    All manipulations of strings received by user input should consider each element and not each individual character. This is basically why delphi TEdit does not work properly with these kind of emojis (just try adding one and pressing backspace that you will see part of problem). In short, when you don't consider this, you risk breaking a string into a position in the middle of a Grapheme, creating a malformed string.

     

    As I said, this iteration is essential, Embarcadero should implement this natively to work cross-platform just like C # did.

     

    The best thing to do at this point is to port the C # code.


  3. The delphi string is an implementation of UTF-16 in which the "normal" graphic characters corresponding to 1 Char (2 bytes), but having others graphic characters being represented by 2 Chars (4 bytes), the so-called surrogate pair, (like the emoji 🙏). And in this case we need to check if the char IsSurrogate, to know that it is a graphic character of 2 Chars (4 bytes). But the problem is that utf-16 is not limited to a maximum of 4 bytes for a graphic character, in fact it already predicts that a graphic character can have up to 14 bytes for future implementations, and today most of the emojis already occupy at least 8 bytes (like the emoji 🙏🏻 <- light version). This is a problem, because delphi only handles the graphic character with the possibility of being 2 bytes or 4 bytes, and is often converting the graphic character or string to UCS4Char or UCS4String respectively, to treat everyone as being 4 bytes.


    So, how do you know the actual size of each graphic character in a string?


  4. 1 hour ago, John Kouraklis said:

    What's the strategy to shut down a multi-thread app?

     

    This problem is very common because without proper care the threads end in a different order than the units.

    The rule to avoid this is: each unit that creates each thread has to guarantee that the thread it created will be canceled and completely terminated until the finalization of the unit.

    How to do this? There are several possibilities, I will quote one:

     

    1) Create a way to cancel each thread (it is almost always possible through a simple Boolean + TEvent.SetEvent, but each case must be evaluated)
    2) Create a unique TEvent to signal the end of the thread; Just run the thread code in a try finaly FFinishEvent.SetEvent;
    3) At the end of the unit, cancel the thread, and give FFinishEvent.WaitFor;

    • Like 1

  5. 2 hours ago, Davide Angeli said:

    It's hard... the project is quite complex with several runtime packages

    Have you tried to see if there are any more exception or information debugging the IDE? Open some bpl then Run> Parameters... > Host Application "C:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bds.exe" > Save > press F9

     

    Then new instance of delphi will be created in debug mode, then you can reproduce the error in that delphi instance and you may have more information about the problem.

    • Like 1
    • Thanks 1

  6. I'm not going to talk about delphi 10.4.2 because I still have 10.4.1. But on sdk, you don't need to use this Embarcadero tool, it is almost always a version behind. Ideally, you should install Android Studio on your own, open it and Configure> SDK Manager and install (download) the latest sdk, get the directory and configure it in your Delphi Options > Deployment > SDK Manager> Android

    • Like 3
    • Thanks 1

  7. Sometimes I come across some delphi language enhancements that are undocumented or are poorly documented. In the last few years docwiki has improved a lot, but there are still undocumented cases, such as accessing an item in an array just by declaring a property, without any method:

      TipGeolocation = record
      private
        FCoordinates: array[0..1] of Double;
      public
        property Latitude: Double read FCoordinates[0] write FCoordinates[0];
        property Longitude: Double read FCoordinates[1] write FCoordinates[1];
      end;

    Share here any enhancements to the delphi language that you have discovered but are not documented.

    • Like 4

  8. @Attila Kovacs You are right about compatibility, thanks for checking this out. I will put this observation. In the future I can do some backwards compatibility, but not now.

     

    About the null values serialization, this library is just to consumes one rest api, then the serialization can occur just when you call a Post or Put and have the parameter ABody that is a record or a class or an array. In this specific case, the library will serialize the type as json, and in this case, the values that is null will be written to the json body without skip. The current implementation of System.JSON.Serializers don’t permit to ignore the fields with null values.

     

    1 hour ago, mvanrijnen said:

    Ah ok, its for creating an API consumer, 

    (my first thought was something for declaring API's,, e.g. like in radserver (which we are using right now)).

     

    Sorry i'll make it clearer

    • Thanks 1

  9. @Attila Kovacs Here one example without TValue.Make<T>, and with the nullables stating with IsNull True:

    uses
      System.Rtti, System.TypInfo, System.JSON.Serializers, System.JSON.Readers,
      System.JSON.Writers, System.JSON.Types, iPub.Rtl.Refit;
      
    type
      TNullable<T> = record
      strict private
        FIsNotNull: Boolean;
        function GetIsNull: Boolean;
        procedure SetIsNull(AValue: Boolean);
      public
        Value: T;
        property IsNull: Boolean read GetIsNull write SetIsNull;
      end;
    
      TNullableConverter<T> = class(TJsonConverter)
      public
        procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;
        function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;
          const ASerializer: TJsonSerializer): TValue; override;
        function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
      end;
      
    { TNullable<T> }
    
    function TNullable<T>.GetIsNull: Boolean;
    begin
      Result := not FIsNotNull;
    end;
    
    procedure TNullable<T>.SetIsNull(AValue: Boolean);
    begin
      FIsNotNull := not AValue;
    end;
    
    { TNullableConverter<T> }
    
    function TNullableConverter<T>.CanConvert(ATypeInf: PTypeInfo): Boolean;
    begin
      Result := ATypeInf = TypeInfo(TNullable<T>);
    end;
    
    function TNullableConverter<T>.ReadJson(const AReader: TJsonReader;
      ATypeInf: PTypeInfo; const AExistingValue: TValue;
      const ASerializer: TJsonSerializer): TValue;
    var
      LNullable: TNullable<T>;
    begin
      if AReader.TokenType = TJsonToken.Null then
      begin
        LNullable.IsNull := True;
        LNullable.Value := Default(T);
      end
      else
      begin
        LNullable.IsNull := False;
        LNullable.Value := AReader.Value.AsType<T>;
      end;
      TValue.Make(@LNullable, TypeInfo(TNullable<T>), Result);
    end;
    
    procedure TNullableConverter<T>.WriteJson(const AWriter: TJsonWriter;
      const AValue: TValue; const ASerializer: TJsonSerializer);
    var
      LNullable: TNullable<T>;
      LValue: TValue;
    begin
      LNullable := AValue.AsType<TNullable<T>>;
      if LNullable.IsNull then
        AWriter.WriteNull
      else
      begin
        TValue.Make(@LNullable.Value, TypeInfo(T), LValue);
        AWriter.WriteValue(LValue);
      end;
    end;
    
    initialization
      GRestService.RegisterConverters([TNullableConverter<string>,
        TNullableConverter<Byte>, TNullableConverter<Word>,
        TNullableConverter<Integer>, TNullableConverter<Cardinal>,
        TNullableConverter<Single>, TNullableConverter<Double>,
        TNullableConverter<Int64>, TNullableConverter<UInt64>,
        TNullableConverter<TDateTime>, TNullableConverter<Boolean>,
        TNullableConverter<Char>]);
    end.

    To test:

      TUser = record
        Name: TNullable<string>;
        Location: string;
        Id: Integer;
        Email: TNullable<string>;
        NonexistentField: TNullable<Integer>;
      end;
      
      [BaseUrl('https://api.github.com')]
      IGithubApi = interface(IipRestApi)
        ['{4C3B546F-216D-46D9-8E7D-0009C0771064}']
        [Get('/users/{user}')]
        function GetUser(const AUser: string): TUser;
      end;
      
    procedure TForm1.FormCreate(Sender: TObject);
    var
      LGithubApi: IGithubApi;
      LUser: TUser;
    begin
      LGithubApi := GRestService.&For<IGithubApi>;
      LUser := LGithubApi.GetUser('viniciusfbb');
    end;

     


  10. 4 minutes ago, Attila Kovacs said:

    -To be able to send and receive pure arrays "[]" , some php api's are working that strange way

    Works perfectly in both cases, with the [] or with {..},{..},{..}. In both cases your function result should be a TArray<> of a record or a class.

    8 minutes ago, Attila Kovacs said:

    -To be able to suppress the null values from the nullables (or a shouldmarshal callback/wallpaper)

    Do you mean what happens with the nullable fields that were not received? Well, all fields are set to the default value before reading the received json, so there are several possible workarounds to ensure that the field not received is considered null. In sydney just add the class operator Initialize to set IsNull true, but in older versions, you can simply change the name of IsNull to IsValid, since the boolean fields started False, then the initial value of IsValid of nullable will be False. But there are other possibilities.


  11. @Attila Kovacs I added support to register custom json converters.

     

    About the nullables, is very easy to implement the nullable and the converters. See one simple solution:

     

    uses
      System.Rtti, System.TypInfo, System.JSON.Serializers, System.JSON.Readers,
      System.JSON.Writers, System.JSON.Types, iPub.Rtl.Refit;
      
    type
      TNullable<T> = record
        IsNull: Boolean;
        Value: T;
      end;
    
      TNullableConverter<T> = class(TJsonConverter)
      public
        procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;
        function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;
          const ASerializer: TJsonSerializer): TValue; override;
        function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
      end;
      
     { TNullableConverter<T> }
    
    function TNullableConverter<T>.CanConvert(ATypeInf: PTypeInfo): Boolean;
    begin
      Result := ATypeInf = TypeInfo(TNullable<T>);
    end;
    
    function TNullableConverter<T>.ReadJson(const AReader: TJsonReader;
      ATypeInf: PTypeInfo; const AExistingValue: TValue;
      const ASerializer: TJsonSerializer): TValue;
    var
      LNullable: TNullable<T>;
    begin
      if AReader.TokenType = TJsonToken.Null then
      begin
        LNullable.IsNull := True;
        LNullable.Value := Default(T);
      end
      else
      begin
        LNullable.IsNull := False;
        LNullable.Value := AReader.Value.AsType<T>;
      end;
      TValue.Make<TNullable<T>>(LNullable, Result);
    end;
    
    procedure TNullableConverter<T>.WriteJson(const AWriter: TJsonWriter;
      const AValue: TValue; const ASerializer: TJsonSerializer);
    var
      LNullable: TNullable<T>;
      LValue: TValue;
    begin
      LNullable := AValue.AsType<TNullable<T>>;
      if LNullable.IsNull then
        AWriter.WriteNull
      else
      begin
        TValue.Make<T>(LNullable.Value, LValue);
        AWriter.WriteValue(LValue);
      end;
    end;
    
    initialization
      GRestService.RegisterConverters([TNullableConverter<string>,
        TNullableConverter<Byte>, TNullableConverter<Word>,
        TNullableConverter<Integer>, TNullableConverter<Cardinal>,
        TNullableConverter<Single>, TNullableConverter<Double>,
        TNullableConverter<Int64>, TNullableConverter<UInt64>,
        TNullableConverter<TDateTime>, TNullableConverter<Boolean>,
        TNullableConverter<Char>]);
    end.

    To use just replace the simple types to the nullable types:

      TUser = record
        Name: TNullable<string>;
        Location: string;
        Id: Integer;
        Email: TNullable<string>;
      end;
      
      [BaseUrl('https://api.github.com')]
      IGithubApi = interface(IipRestApi)
        ['{4C3B546F-216D-46D9-8E7D-0009C0771064}']
        [Get('/users/{user}')]
        function GetUser(const AUser: string): TUser;
      end;
      
    procedure TForm1.FormCreate(Sender: TObject);
    var
      LGithubApi: IGithubApi;
      LUser: TUser;
    begin
      LGithubApi := GRestService.&For<IGithubApi>;
      LUser := LGithubApi.GetUser('viniciusfbb');
    end;

    You can test the code above and see the user name is not null but the user email is null.

    The nullable type and the nullable converter code in this example need to be implemented by the developer, I can't put it on the library because each developer has it own implementation os nullable.

    • Like 1
    • Thanks 1

  12. 21 minutes ago, Attila Kovacs said:

    I don't think so as I can't find System.JSON.Serializers under Berlin but under Tokyo.

    I don't have older versions installed to be able to test, or do backwards compatibility, but it looks like you're right, delphi 10.2 tokyo or newer.

     

    22 minutes ago, Attila Kovacs said:

    Is it possible to decorate the custom nullable fields that this internal serializer/reverter can translate them?

    A one-time-registered type converter/reverter would be even better.

    What exactly did you want? The json serialization of delphi is very malleable, you do not need to declare all the necessary fields and you can also use all existing attributes in System.Json.Serializers.pas.
     

    29 minutes ago, Attila Kovacs said:

    A one-time-registered type converter/reverter would be even better.

    It's a great option, I will implement it today.


  13. @Andrea Raimondi

    The full explanation is on the github page. See this part:

     

    "The problem

    Delphi has its own messaging system (System.Messaging.pas) that works well but is totally synchronous and thread unsafe. In multithreaded systems we always need to communicate with other classes, sometimes synchronously, sometimes asynchronously, sometimes synchronizing with the mainthread (in the case of the UI), and doing this without a message system (communicating directly) makes the code large and complex, prone to many bugs."

     

×