Jump to content

aehimself

Members
  • Content Count

    1030
  • Joined

  • Last visited

  • Days Won

    22

Posts posted by aehimself


  1. 11 minutes ago, programmerdelphi2k said:

    sorry, on copy I did wrong ... but the result is the same = WORKS!

    No, it does not 🙂 First, CurrentKey has a zero value after .OpenKeyReadOnly. Second, you are assigning a wrong registry path, not what the API expects:

     

    https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa

     

    hkeyClass

    Type: HKEY

    A handle to the registry key for the file type. The access rights for this registry key should be set to KEY_READ. This member is ignored if fMask does not include SEE_MASK_CLASSKEY.

     

    Edit: wrong parameter copied


  2. 24 minutes ago, programmerdelphi2k said:

    but this works with IDE on memory or not!

    That is really surprising as that's not how you open a registry key with TRegistry AND even if you correct that, reg.CurrentKey won't be changed after a single .OpenReadOnly.

     

    Running the code on a PC with Delphi 10.4 and 11 installed also confirms failure: it opens up the file in Delphi 10.4.


  3. 15 minutes ago, Attila Kovacs said:

    what about bdslauncher.exe bds.exe /np d:\myfile.pas

    ?

     

    with full paths ofc

     

    Tried. Bds.exe never starts, bdslauncher.exe never quits, just hangs. No windows, no errors, nothing.

    Also tried adding the necessary (?) -pDelphi switch, same result. Tried giving these parameters only to Bdslauncher and only the source file. No effect.


  4. I'm building a tool which searches for and opens Delphi source files. Until now execution was a simple ShellExecute call to the .pas file and everything was working just file.

    Now, as a second Delphi version is introduced I had to change the code to support opening said file in a specific Delphi version. My issue is that a simple call to bds.exe will always launch a new Delphi instance, and I need to re-use the one currently running. Basically I need to mimic the IDEs behavior when double-clicking a .pas file in Explorer.

     

    Unfortunately I could not find any documentation about the parameters of BDSLauncher, nor any relevant parameters of Bds.exe which could achieve this. I also built a small program which only lists the parameters it was started with and temporarily replaced BdsLauncher.exe and then bds.exe to see what it received when something is double-clicked, but all I got was the full path of bds.exe.

     

    Can anyone point me to the right direction?

     

    Thanks!


  5. Var
      TokenHandle: THandle;
    Begin
      If LogonUser(PChar(UserEdit.Text), PChar(DomainEdit.Text), PChar(PasswordEdit.Text), LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, TokenHandle) Then
      Try
        // Credentials are valid...
    
        // In case needed:
        If ImpersonateLoggedOnUser(TokenHandle) Then
        Try
          // Do stuff in the context of user
        Finally
          RevertToSelf;
        End;
      Finally
        CloseHandle(TokenHandle);
      End;
    End;

    User must have network login right to the PC, though.


  6. 1 minute ago, Attila Kovacs said:

    As the component itself stores only a reference in both cases, I can't see any benefit.

    The benefit is, that you are not actually storing and handling data in a visual component, e.g.: TTreeNode.Data := TMyClass.Create;

    While it does work it's considered bad practice as you are not separating UI and data / business logic.

     

    Anyway, let's not hijack the topic, shall we. OP might not be interested in this discussion.


  7. 6 minutes ago, direktor05 said:

    DAMN, I just discovered there is also TJsonTreeView component. Delphi 10.4. A nono, its a component I installed. Made by some Polish guy at Embarcadero.

    If you mean https://github.com/pglowack/DelphiJSONComponents/blob/master/JSONTreeView.pas this only visualizes the JSON as far as I can see from the code. You still have to manually append your extra data to each node, or search in each node's children to find your OnClik, URL and Description nodes.

     

    3 minutes ago, Attila Kovacs said:

    You both never worked with treeviews, do you?

    I did, and first I used .Data to actually store data too, it was just too convenient.  Lately I'm keeping my data in a separate store and .Data only points to the data in this store, if needed. This way I don't even need to free up memory when a node is deleted.


  8. We had a custom component which was only a wrapper for a TStringList, you can do something like this. On the other hand I feel like we are wasting memory; to store textual data you simply can use string constants or if you don't want to convert your TXT files into Delphi strings, build a .res file from them and embed it to the compiled application.

     

    In case you wish to convert, I have a method which seemed to work in simple cases and properly splits the input into 255-length chunks while preserving line breaks and escapes apostrophes:

    Function ToDelphiString(Const inString: String): String;
    Const
     ADDITION = #39' + '#39;
     BREAKAT = 80;
    Var
     line, a, max: Integer;
     sb: TStringBuilder;
     strarr: TArray<String>;
    Begin
     sb := TStringBuilder.Create;
     Try
      sb.Append(#39);
    
      strarr := AdjustLineBreaks(inString).Split([sLineBreak]);
    
      For line := Low(strarr) To High(strarr) Do
       Begin
        max := strarr[line].Length Div BREAKAT;
    
        For a := 0 To max Do
         Begin
          sb.Append(strarr[line].Substring(a * BREAKAT, BREAKAT).Replace(#39, #39#39));
    
          sb.Append(#39);
    
          If a <> max Then sb.Append(' +' + sLineBreak + #39);
         End;
    
        If line <> High(strarr) Then sb.Append(' + sLineBreak +' + sLineBreak + #39);
       End;
    
      Result := sb.ToString;
     Finally
      FreeAndNil(sb);
     End;
    End;

     


  9. 8 minutes ago, direktor05 said:

    No I want to make a tool to create HTML menus. So I want a treeview that will be just like a website menu then export to json and read with javascript. The menu must not only have name, but also link url, onclick event description and parent/child id, stored in tTreeview1.items.data pointer.

    TTreeView is basically a wrapper for the old Windows TreeView component, which was created way before JSON. You MIGHT have some success finding JSON export in TVirtualTreeView but I wouldn't be so sure; most probably you'll have to extract that by yourself.

    As @haentschman mentioned storing data in a visual component is a bad practice. What I'd do is...

     

    Have a TJSONObject which stores your node information. By adding childnodes, you will already have the parent / children relationship and since each JSON object has a name, they only now have to store URL, OnClick javascript handler and description.

    For quick access to this information when building your TreeList based on the TJSONObject, you can assign the related object to the TTreeNode's .Data pointer. This way you didn't just separate UI / data, but simply can call TJSonObject(TreeView.Selected.Data).ToString to immediately get the selected node's JSON representation.

    To save the whole tree, call your main TJSONObject.ToString, to rebuild it TJSONObject(TJSONObject.ParseJSONValue).

     

    This is by using the Delphi provided System.JSON unit, but you can use any other library you prefer. The idea behind can stay the same.


  10. If you can detect a malformed packet, you simply can log the input and the output. Once the issue is detected, dump these in a local file so the customer can send it to you.

    Then, it's a matter of running the same byte sequence against your "processor" to see what / why it happens.

     

    You also can do the same if you can't detect the failure, but in this case dumping has to be initiated by a user input.

     

    In unexplainable situations I find it helpful to divide my classes up even further. Have a class ONLY for the COM handling, outputting TBytes. Have one converting these to AnsiSting-based "packets". Have an other one to process these. Usually during coding these tiny classes I'm fixing the issue I had in the beginning.


  11. 1 hour ago, Zazhir said:

     

    
        SetProcessWorkingSetSize(GetCurrentProcess, $FFFFFFFF, $FFFFFFFF);

     

    When I was hired this was the first thing I removed from our legacy application.

    This command only pushes most of the applications data in memory to the swap file, drastically slowing down your application when it tries to access those. It won't decrease your applications memory usage, only relocates it to the hard disk. Avoid it, it does more harm than good.

     

    Btw, at us it was in a Timer's OnTimer event, ran once about every minute...

     

    As for the memory usage, use a tool like DeLeaker. Put a breakpoint in your loop and make a snapshot each time. When you compare those snapshots, you'll see the objects in memory and the callstack which created said objects. It'll point you in the generic direction of what you should .Close or .Free,

     

    WITH THAT SAID

     

    Using Task manager to monitor your application usage is vaguely incorrect. It includes memory areas what your code already released but the OS did not take back yet due to memory allocation speed optimization as far as I know.


  12. On 11/29/2022 at 8:27 AM, Fr0sT.Brutal said:

    Well, you should investigate why the 2-char buffer became insufficient for date sep. What result that WinAPI function returns if you call it directly (or just step into call to it from RTL)?

    And what exactly means " 3rd-party controls handle this incorrectly "?

    It returns a dot, a space and #0 (which makes sense) and DevExpress's date editor started to misbehave... normally it showed correct format but when you clicked in it it showed "____ / " and accepted years only.

    Spent my last day trying to understand how this works and why some hacks were needed at some places. And I think I finally understood.

     

    The issue is NOT with Delphi 11.2 but some random unit changing some properties in the global FormatSettings variable (probably as an attempt to fix the wrongly detected separator). Once I found and got rid of that, VCL controls started to work but some (previously fine) calls to StrToDate started to throw exceptions. The reason is, Emba reworked the logic of date and time parsing, which is more strict in the later. While having a short date format of "yyyy. mm. dd." could parse "2022.11.30" in 10.4.2, Delphi 11.2 actually requires the final separator.

     

    Loads of experiments and this knowledge resulted a custom DateUtils unit to attempt to unify the behavior if the application is compiled with 10.4.2 and 11.2, I'll still have to roll back some local fixes tomorrow and start to use the helper instead... after that we'll see if my idea works.


  13. 41 minutes ago, Fr0sT.Brutal said:

    When function encounters it, it replaces slash to current DateSeparator. This way date format becomes independent from date separator change so user can just modify date separator to get desired format instead of changing whole date format.

    I don't know if I follow you here. Does it mean that I must never use FormatSettings.ShortDateFormat as it is, only FormatSettings.ShortDateFormat.Replace('/', FormatSettings.DateSeparator)?

     

    The reason I'm asking is that 3rd-party controls (like DevExpress) started to show (and save) dates differently just because of compiling under D11. That makes me think that either the above statement is incorrect, or all 3rd-party controls handle this incorrectly.

    To correct this I had to manually correct FormatSettings.ShortDateFormat but that immediately breaks all logic which relies on TFormatSettings.Create and StrToDate - which our tool is using a lot.

     

    54 minutes ago, Fr0sT.Brutal said:

    Regarding the separator itself, probably it couldn't be retrieved from the system (as https://learn.microsoft.com/en-us/windows/win32/intl/locale-sdate says, this constant is deprecated) so it gets value of slash.

    The reason Delphi 10.4.2 could not get it is because Embarcadero didn't provide a buffer big enough. As both versions are calling the same WinApi function, changing the buffer to array[0..1] retrieving will fail on D11 aswell; calling RaiseLastOSError will reveal the reason:

    image.png.66ad55a8e820d3460c36f5cfa655ce41.png

     

    Maybe I should have been more precise. I personally don't care about the date separator alone, only the malformation of ShortDateFormat because of it, which seem to affect appearance and logic.


  14. Hello,

     

    I am in the process of verifying if we can update our environment to use Delphi 11.2 instead of the current 10.4.2. However compilation was successful, even the very basic tests failed because of an incorrect date format detected by Delphi.

     

    Reproduction is really easy, just execute a single line:

    TFormatSettings.Create(1038);

    Result on Delphi 11.2:

    image.thumb.png.b6c2debd0fc4d5aa201220371e7ea388.png

     

    On 10.4.2:

    image.thumb.png.0ccc1e78f6b2e6f68da82ffd9c99b50e.png

     

    So 11.2 correctly detects the date separator as "." in the locale, however the very next line will call this method, which will replace all separators with a forward slash instead:

      procedure FixDateSeparator(var DateFormat: string);
      var
        P: PChar;
        InsideLiteral: Boolean;
      begin
        InsideLiteral := False;
        P := PChar(DateFormat);
        if P = nil then
          Exit;
        while P^ <> #0 do
        begin
          if P^ = '''' then
            InsideLiteral := not InsideLiteral;
          if (P^ = Separator) and (not InsideLiteral) then
            P^ := '/';
          Inc(P);
        end;
      end;

    So because 10.4.2 could not detect the separator, it returns the correct date format. 11.2 detects the separator, ruining the date format instead. The WinAPi definition is the same, buffer is different in the implementation:

    function GetLocaleInfo; external kernel32 name 'GetLocaleInfoW';
    
    // Delphi 10.4.2 MSWindows implementation
    function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char;
    var
      Buffer: array[0..1] of Char;
    begin
      if GetLocaleInfo(Locale, LocaleType, Buffer, 2) > 0 then
        Result := Buffer[0] else
        Result := Default;
    end;
    
    // Delphi 11.2 MSWindows implementation
    function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char;
    var
      Buffer: array[0..2] of Char;
    begin
      if GetLocaleInfo(Locale, LocaleType, Buffer, 3) > 0 then
        Result := Buffer[0] else
        Result := Default;
    end;

    I can not wrap my head around this. I believe it should be a faulty FixDateSeparator implementation, as it makes no sense to change the correct separators to incorrect ones based on the selected locale.

     

    Is this an issue in Delphi and I should raise a ticket or maybe I'm missing something...?


  15. MyFileSourceSize := MyFileStreamSource.Size;

    Is unnecessary, MyFileSourceSize.Size - MyFileSourceSize.Position will always return the unprocessed bytes.

     

      MyFileStreamSource.Read(MyBuffer[0], MyBufferSize); // read and put on the next position...

    Make sure you also check if .Read returns MyBufferSize. Otherwise you will have "rouge" data what you will write back to the destination, effectively corrupting the output.

     

    Also, the pseudocode above won't handle data, which is interrupted by the end of a buffer.


  16. 9 minutes ago, robertjohns said:

    It is 32 bit and the file size is 3GB

    a 32 bit process can allocate a total of 2 GBs of RAM (or 3 if it's large address aware). Loading 3 GBs of data in the memory would use that up by itself, leaving no room for anything else (like the TMemoryStream class or any other variables). So it is expected.

    As @PeterBelow advised, build a 64 bit executable, or use FileStreams instead of loading everything in the memory.


  17. As a general rule of thumb, never call Application.ProcessMessages by yourself. Let your thread "ping" back with updates from time to time with TThread.Synchronize, you can even have a TTimer in your VCL thread to poll updates from the Thread via public properties. Just make sure you are using some king of locking (TCriticalSection, TMonitor, mutexes, events, etc.) when reading from and writing to these variables.


  18. I discovered a trick. If you put SelStart before a link and set SelLength to 1, RichEdit will select the FULL link text... like

    Quote

    HYPERLINK "https://en.delphipraxis.net"Delphi Praxis

    The good news in this is that we can optimize the checking cycle to use this feature:

      Self.SelStart := Integer.MaxValue;
      max := Self.SelStart;
    
      a := 0;
      While a < max Do
      Begin
        Self.SelStart := a;
        Self.SelLength := 1;
    
        If Self.SelAttributes.Color = clWindowtext Then
          Self.SelAttributes.Color := TStyleManager.ActiveStyle.GetStyleFontColor(sfEditBoxTextNormal);
    
        a := Self.SelStart + Self.SelLength;
      End;

    Instead of 3000+ msec, the cycle now finishes in 500!

     

    Still not ideal, but a lot more bearable 🙂


  19. 1 hour ago, robertjohns said:

    How can I find all instances of a string in a file , replace all instance of the string and save the copy of the file in Delphi

    TFile.WriteAllText('OutFileName.txt', TFile.ReadAllText('FileName.txt').Replace('Replace from', 'Replace To'));


  20. Ok, I'm getting baffled. Time is lost in the space-time continuum.

     

    I added a ton of measuring to the code, looks like this now:

    Procedure TRichEdit.StreamIn(Var Msg: TMessage);
    Var
     a, b: Integer;
     ro: Boolean;
     sw, fsw: TStopWatch;
     needcolor: Boolean;
     sscount, slcount, clcount, gscount, nccount: Integer;
     ssdelay, sldelay, cldelay, gsdelay, lddelay, ulddelay, loading, rodelay, moddelay, ncdelay: Int64;
     s: String;
    Begin
     fsw := TStopWatch.StartNew;
    
     sscount := 0;
     ssdelay := 0;
     slcount := 0;
     sldelay := 0;
     clcount := 0;
     cldelay := 0;
     gscount := 0;
     gsdelay := 0;
     lddelay := 0;
     ulddelay := 0;
     rodelay := 0;
     moddelay := 0;
     nccount := 0;
     ncdelay := 0;
    
     sw := TStopWatch.StartNew;
     Self.LockDrawing;
     lddelay := sw.ElapsedMilliseconds;
     Try
      ro := Self.ReadOnly;
      Try
       sw := TStopWatch.StartNew;
       Self.ReadOnly := False;
       Inc(rodelay, sw.ElapsedMilliseconds);
    
       sw := TStopWatch.StartNew;
       inherited;
       loading := sw.ElapsedMilliseconds;
      Finally
       sw := TStopWatch.StartNew;
       Self.ReadOnly := ro;
       Inc(rodelay, sw.ElapsedMilliseconds);
      End;
    
      Inc(sscount);
      sw := TStopWatch.StartNew;
      Self.SelStart := Integer.MaxValue;
      Inc(ssdelay, sw.ElapsedMilliseconds);
    
      Inc(gscount);
      sw := TStopWatch.StartNew;
      b := Self.SelStart;
      Inc(gsdelay, sw.ElapsedMilliseconds);
    
      For a := 0 To b Do
       Begin
        Inc(sscount);
        sw := TStopWatch.StartNew;
        Self.SelStart := a;
        Inc(ssdelay, sw.ElapsedMilliseconds);
    
        Inc(gscount);
        sw := TStopWatch.StartNew;
        Try
         If Self.SelStart <> a Then Continue;
        Finally
         Inc(gsdelay, sw.ElapsedMilliseconds);
        End;
    
        Inc(slcount);
        sw := TStopWatch.StartNew;
        Self.SelLength := 1;
        Inc(sldelay, sw.ElapsedMilliseconds);
    
        Inc(nccount);
        sw := TStopWatch.StartNew;
        needcolor := Self.SelAttributes.Color = clWindowtext;
        Inc(ncdelay, sw.ElapsedMilliseconds);
    
        If needcolor Then
        Begin
         Inc(clcount);
         sw := TStopWatch.StartNew;
         Self.SelAttributes.Color := TStyleManager.ActiveStyle.GetStyleFontColor(sfEditBoxTextNormal);
         Inc(cldelay, sw.ElapsedMilliseconds);
        End;
       End;
    
      Inc(sscount);
      sw := TStopWatch.StartNew;
      Self.SelStart := 0;
      Inc(ssdelay, sw.ElapsedMilliseconds);
    
      sw := TStopWatch.StartNew;
      Self.Modified := False;
      moddelay := sw.ElapsedMilliseconds;
     Finally
      sw := TStopWatch.StartNew;
      Self.UnlockDrawing;
      ulddelay := sw.ElapsedMilliseconds;
     End;
    
     sw := TStopWatch.StartNew;
     s := 'SelStart count: ' + sscount.ToString + ', total time: ' + ssdelay.ToString + ' ms' + sLineBreak +
                 'SelLength count: ' + slcount.ToString + ', total time: ' + sldelay.ToString + ' ms' + sLineBreak +
                 'NeedColor count: ' + nccount.ToString + ', total time: ' + ncdelay.ToString + ' ms' + sLineBreak +
                 'Coloring count: ' + clcount.ToString + ', total time: ' + cldelay.ToString + ' ms' + sLineBreak +
                 'GetSelStart count: ' + gscount.ToString + ', total time: ' + gsdelay.ToString + ' ms' + sLineBreak +
                 'Locking: ' + lddelay.ToString + ' ms, unlocking: ' + ulddelay.ToString + ' ms, loading: ' + loading.ToString + ' ms, read-only: ' + rodelay.ToString + ' ms, modified: ' + moddelay.ToString + ' ms' + sLineBreak +
                 'Full cycle: ' + fsw.ElapsedMilliseconds.ToString + ' ms' + sLineBreak +
                 'Building message: ' + sw.ElapsedMilliseconds.ToString + ' ms';
     ShowMessage(s);
    End;

    Loading a 7 kb RTF file with no coloring, only links and a bulleted list results:

    Quote

    SelStart count: 2140, total time: 730 ms
    SelLength count: 703, total time: 816 ms
    NeedColor count: 703, total time: 0 ms
    Coloring count: 85, total time: 5 ms
    GetSelStart count: 2139, total time: 0 ms
    Locking: 0 ms, unlocking: 0 ms, loading: 6 ms, read-only: 0 ms, modified: 0 ms
    Full cycle: 3247 ms
    Building message: 0 ms

    Setting SelStarts took 730 ms, setting SelLengths took 816 ms, a total of 1546 ms.  So how the full cycle is 3247 ms, which is more than double...?

     

    Loading the same with no VCL styles active makes it even more visible:

    Quote

     

    SelStart count: 2140, total time: 11 ms
    SelLength count: 703, total time: 111 ms
    NeedColor count: 703, total time: 0 ms
    Coloring count: 85, total time: 0 ms
    GetSelStart count: 2139, total time: 0 ms
    Locking: 0 ms, unlocking: 0 ms, loading: 5 ms, read-only: 0 ms, modified: 0 ms
    Full cycle: 1847 ms
    Building message: 0 ms

     

    What I did not measure yet...?

     

    I know TStopWatch is not the most precise system, but it can not be missing this much...

×