Jump to content

Kryvich

Members
  • Content Count

    402
  • Joined

  • Last visited

  • Days Won

    8

Posts posted by Kryvich


  1. Have you measured how users work with your program? It's possible that an average user use 5-10 reports (tabs) in one session. Then all controls on other tabs just sit in memory, grab GDI resources without need.

     

    It's OK to create the form layouts manually, Delphi has all instruments to make this work easier. But after rising HTML and CSS it has become fashionable to entrust the program with the placement and adjustment of the size of controls on a form. For ex.

     


  2. 2 hours ago, Mike Torrettinni said:

    No... it's actually very simple (looks wise) reporting tool, but organized into 50+ separate tabs (features) on page control(s). So, not cluttered, but all on Main form...

    I presume you didn't put all that controls to the form manually. It's a lot of work. I would use some sort of automation, and create  the appropriate controls on the fly when the tab was selected. Then you'll get 3000/50 = 60 controls at a moment.


  3. 2 hours ago, ConstantGardener said:

    If you have plenty of forms, all created at the begin, your Startup-Time is looooong!

    You're right. A better solution would be to create and initialize a form by request, and then Show(ShowModal)/Close it as needed. The reference to the created form can be saved as a class variable and freed when an application to finish. It makes sense for forms that need to maintain their state. Or for modal dialogs. And you need to ensure that two or more identical forms are not opened at the same time.


  4. 3 hours ago, Mike Torrettinni said:

    I commented out all CreateForm in Project source, except for Main form.

    There is option in the project options (Application | Forms) to select what forms to create when an application starts.


  5. OK I have a workaround for this issue. Try to specify a type of the inline variable.

    procedure TestDictErr_WorkAround;
    var
      Dict: TDictionary<string,TObject>;
    begin
      Dict := TDictionary<string,TObject>.Create;
      for var item: TPair<string,TObject> in Dict do
        Writeln('Key = ', item.Key, ' Name = ', item.Value.ClassName);
    end;

    I cannot guarantee that this code will be correctly compiled. But at least there is no the runtime error anymore.


  6. Another observation. The exception occurs only if the dictionary has the key and/or value of the string type. If I change it to Integer - the exception disappears.

    procedure TestDictErr_IntegerKey_OK;
    var
      Dict: TDictionary<Integer,TObject>;
    begin
      Dict := TDictionary<Integer,TObject>.Create;
      for var item in Dict do
        Writeln('Key = ', item.Key, 'Name = ', item.Value.ClassName);
    end;

    Well, I just found it. For procedure TestDictOK the compiler generates a pair of calls:

    • call @InitializeRecord
    • call @FinalizeRecord

    But for procedure TestDictErr it generates only

    • call @FinalizeRecord

    Program tries to finalize not initialized record and falls.

    P.S. Is there a bug bounty program for Delphi? :) 

    https://quality.embarcadero.com/browse/RSP-23417

    • Like 1

  7. Hi, I try to adopt outstanding and free Pas2js transpiler to Delphi language. My main development IDE is Delphi CE Rio, so I decided to try a new Delphi syntax: generic collections and inline variables. And stumbled upon a runtime error. The code (simplified):

    program TestInlineVarForDictionary;
    {$APPTYPE CONSOLE}
    {$R *.res}
    
    uses SysUtils, Generics.Collections;
    
    procedure TestDictErr;
    var
      Dict: TDictionary<string,TObject>;
    begin
      Dict := TDictionary<string,TObject>.Create;
      for var item in Dict do
        Writeln('Key = ', item.Key, 'Name = ', item.Value.ClassName);
    end;
    
    procedure TestDictOK;
    var
      Dict: TDictionary<string,TObject>;
      item: TPair<string,TObject>;
    begin
      Dict := TDictionary<string,TObject>.Create;
      for item in Dict do
        Writeln('Key = ', item.Key, 'Name = ', item.Value.ClassName);
    end;
    
    begin
      try
        //!!TestDictOK;
        TestDictErr;
      except
        on E: Exception do begin
          Writeln(E.ClassName, ': ', E.Message);
          Write('Press Enter to continue...');
          Readln;
        end;
      end;
    end.

    This program causes Exception class $C0000005 with message 'access violation at 0x0040a86e: write of address 0x0040a29e'. Can you confirm it? Is it a bug in the compiler or/and RTL, or am I misusing the new syntax?

    It's interesting: if you uncomment TestDictOK that does enumeration in old-style, the exception will disappear!


  8. @haentschman It's always better to have named constants instead of numbers. Say you want to swap 2nd and 6th bits in some structure's field. Then you need to scan all your program and check all places where these bits are used. But if you used an enumeration from the start, you just swap these bits in the declaration:

    type
      TMyEnum = (mb0, mb5, mb2, mb3, mb4, mb1, mb6, mb7);

    Of course instead of mb0, mb1 etc. should be really meaningful names, without numbers.

    • Haha 1

  9. You can write it as

    If ((b and $01) > 0) or ((b and $08) > 0) or ((b and $80) > 0) then ...

    Or you can create an enumeration and use meaningful names for each bit.

    type
      TMyEnum = (mb0, mb1, mb2, mb3, mb4, mb5, mb6, mb7);
      TMyBits = set of TMyEnum; // = Byte in size
    
    function Test: Byte;
    var
      mbs: TMyBits;
    begin
      mbs := [mb0, mb3, mb7];
      Byte(mbs) := $89; // It's equivalent of mbs := [mb0, mb3, mb7];
      if mbs * [mb0, mb3, mb7] <> [] then // If one of bit is set
        ;//...
      if mbs * [mb0, mb3, mb7] = [mb0, mb3, mb7] then // If all 3 bits are set
        ;//...
      if mbs - [mb0, mb3, mb7] = [] then // If no other bits are set
        ;//...
      Include(mbs, mb1); // Set 2nd bit
      mbs := mbs - [mb3, mb7]; // Unset 4th and 8th bit
      //etc...
      Result := Byte(mbs);
    end;

    It's always better to deal with clearly named typed variables and constants.

    • Like 5

  10. I would write it as 

    localVar := ParameterValue;
    if localVar = None then
      localVar := DefaultValue;

    Presume that ParameterValue is assigned in most cases, then DefaultValue will not be accessed in most cases.


  11. @Lars Fosdal Sorry I cannot help you with it. I tried the attribute [JsonReflectAttribute(ctObject???,rtObject???,TArrayElementInterceptor)] but there is no a converter/reverter type that accepts values of any type: strings, numbers, and objects. Perhaps you may need to create a descendant of TJSONUnMarshal to handle all the options.

     

    It would be great if Embarcadero improves JSON support and allow to convert other types to a custom record if this record can take values of other types:

    type
      TSwitch = class
      private
        FSwitch: Boolean;
      public
        property Switch: Boolean read FSwitch write FSwitch;
      end;
    
      TArrayElement = record
      private
        FValue: Variant;
      public
        class operator Implicit(const Value: Integer): TArrayElement;
        class operator Implicit(const Value: string): TArrayElement;
        class operator Implicit(const Value: TSwitch): TArrayElement;
      end;

     

    • Like 1

  12. @Schokohase In fact RTTI is generated for types declared in a DPR file. But it is not findable by TRttiContext.FindType(AQualifiedName: string). In the Delphi help for the function FindType it is written:

    Quote

    FindType searches for the type in all packages and works only for public types that have qualified names.

    But there is no information what types have qualified names. And only in System.Rtti.pas I found this comment:

    Quote

    Types not declared in interface section of unit have no qualified name and can't be looked up by name.

    @Lars Fosdal Unfortunately, this will not work for the Variant type without additional processing. You can try to declare a custom reverter for an element of the inner array.


  13. May be TMessageArray = array of array of Variant; ?

    program TestJson;
    {$APPTYPE CONSOLE}
    {$R *.res}
    uses
      System.SysUtils, REST.Json;
    const
      JsonData = '{'
        + '  "message": ['
        + '    [ 0, "a text" ],'
        + '    [ 1  ],'
        + '    [ 1, { "switch": true } ],'
        + '    [ 2,  "text one",  "text line two" ]'
        + '  ]'
        + '}';
    type
      TMessageArray = array of array of Variant;
      TJsonMessage = class
      private
        Fmessage: TMessageArray;
      public
        property message: TMessageArray read FMessage write FMessage;
      end;
    var
      Message: TJsonMessage;
    begin
      try
        if TypeInfo(TJsonMessage) = nil then
          Writeln('No RTTI for ', TJsonMessage.ClassName);
        Message := TJson.JsonToObject<TJsonMessage>(JsonData);
        Writeln('Length = ', Length(Message.message));
        Message.Free;
      except
        on E: Exception do begin
          Writeln(E.ClassName, ': ', E.Message);
          Readln;
        end;
      end;
    end.

    I tried this option, but my test application gives the exception: "Exception class EConversionError with message 'Internal: Cannot instantiate type TestJson.TJsonMessage'.".
    I still do not understand why.

    • Like 1
×