Jump to content

Marsil

Members
  • Content Count

    33
  • Joined

  • Last visited

Everything posted by Marsil

  1. Hi, I have a ListView with CheckBoxes enabled (CheckBoxes = True) and an OnItemChecked event handler, if I try to add an item to the ListView: var Item := ListView1.Items.Add; // this immediately triggers OnItemChecked Item.Data := SomeLinkedObject; Item.Checked := SomeLinkedObject.Enabled; the OnItemChecked event handler is called immediatley not giving me a chance to assign the Item.Data property. The handler is procedure TForm1.ListView1ItemChecked(Sender: TObject; Item: TListItem); begin if not Assigned (Item.Data) then Memo1.Lines.Add ('ListView1ItemChecked called with nil Data '); // this debug always executed with nil data TSomeLinkedObject (Item.Data).Enabled := Item.Checked // AV here when ListView1.Items.Add executed end; Any idea why this is happening? or is it a bug?
  2. Yes you are right, I verified that the item itself is added to Items, and no duplication as documentation says: Assert (Item = ListView1.Items.AddItem (Item), 'Item is different than the one in Items'); // always passes Now the documentation is buggy too
  3. I tried to reproduce the leak but I couldn't , I'm sorry, the leak probably was accidental and not related to the code I said it caused a leak. I could not edit my post about the leak, no edit button.
  4. The idea was to first set item properties then add it to Items as Kas Ob suggested that it may solve the unwanted too early OnItemChecked event trigger: if I coded it as you showed the Items.AddItem behaves just like Items.Add, Actually Items.Add calls Items.AddItem to do the work, But Items.AddItem gives us a chance to set properties beforehand, but it's buggy and useless. Indeed looks like a bug because the AddItem method documentation says: Link But clearly the properties of Item are NOT duplicated. Thanks for reporting that. I'm not sure why item properties are duplicated when the item itself is supposed to be added to the Items, why the properties are duplicated and where?
  5. Thanks again Remy!, I'll try to keep that in mind. Whatever the technical details behind this, But as a component user, I still consider it awkward to work with because It doesn't behave like other components, at least not in this case, which makes it a bit weird.
  6. I tried this var Item := TListItem.Create (ListView1.Items); Item.Caption := SomeLinkedObject.Name; Item.Checked := SomeLinkedObject.Enabled; Item.Data := SomeLinkedObject; ListView1.Items.AddItem (Item); but the item is shown without caption!, and memory leak occurred! --------------------------- Unexpected Memory Leak --------------------------- An unexpected memory leak has occurred. The unexpected small block leaks are: 13 - 20 bytes: TList x 4, Unknown x 4 21 - 28 bytes: UnicodeString x 3 37 - 44 bytes: UnicodeString x 1 45 - 52 bytes: TListItem x 4 77 - 84 bytes: TSubItems x 4 I don't think solves the issue since the only way to implement your suggestion is to use ListView.Items.AddItem which is called by ListView.Items.Add.
  7. I mean my own initialization! No, the OnItemChecked event is triggered by this statement var Item := ListView1.Items.Add; // this immediately triggers OnItemChecked As I explained in the comment, I tried and removed the Item.Checked := SomeLinkedObject.Enabled; and same problem!! Thanks!, I will try what you suggested here!
  8. Thanks Remy, I implemented the first workaround. But I still don't understand why the ListView is firing the checked event for an item that still being created and still not properly initialized yet?😕 this behavior was really unexpected I always use VirtualTreeView, but this time I decided to use ListView because it will only contain just one or two items, a quick lightweight job.
  9. Hello, Any differences between declaring an inline variable inside a loop and declaring it before the loop? I know that declaring an inline variable inside a loop limits its scope which is a good thing, but any difference in term of performance, memory allocation, extra work,...especially in big loops, or anything I don't know about? Many thanks! Outside loop var FileRec : TSearchRec; while not StopSearch do begin ... GetData (FileRec); UseData; ... end; Inside loop while not StopSearch do begin ... var FileRec : TSearchRec; GetData (FileRec); UseData; ... end;
  10. Hi, (note: complete project in attachment) Was experimenting with dynamically loaded packages and call routines there. while it seems everything works properly I got problem which is the executable (project1.exe) stays in memory after closing the application. Running the app under the debugger shows AVs occurring during the call to UnloadPackage (PkgHandle) and the debugger jumps to GETMEM.INC showing assembly code YIIIIKES!!!, oh no not for me! and the call stack shows only 3 lines but nothing of my source code System.InsertMediumBlockIntoBin(???,???) System.SysFreeMem(???) :0092557a InsertMediumBlockIntoBin + $2A I saw the the AV dialog when I moved UnloadPackage (PkgHandle) from TForm1.FormDestroy to right after the calling code in TForm1.Button1Click and exe did not stay in memory. the shared definition unit: unit TestApi; interface type TIdentRec = record Name : string; Guid : string; Version : Integer; ApiVersion : Integer; end; type TPlugIdentifier = procedure (var IdentRec : TIdentRec); const PlugIdentifierName = 'Identify'; implementation end. In a package I have only one procedure unit TestPlugIn; interface uses TestApi; procedure Identify (var IdentRec : TIdentRec); exports Identify; implementation procedure Identify (var IdentRec : TIdentRec); begin IdentRec.Name := 'Sample PlugIn'; IdentRec.Guid := 'F84E4265C05D4854A2BD781ED7A1BA66'; IdentRec.Version := 1; IdentRec.ApiVersion := 1; end; Calling code: procedure TForm1.Button1Click (Sender: TObject); begin var Identify : TPlugIdentifier; @Identify := GetProcAddress (PkgHandle, PlugIdentifierName); if @Identify = nil then begin Log ('Cannot find Identifier proc!'); Exit; end; var IdentRec : TIdentRec := Default (TIdentRec); Identify (IdentRec); Log ('PlugIn Name: ' + IdentRec.Name); Log ('PlugIn Guid: ' + IdentRec.Guid); Log ('PlugIn Version: ' + IdentRec.Version.ToString); Log ('PlugIn API Version: ' + IdentRec.ApiVersion.ToString); end; Package loaded in TForm1.OnCreate and unloaded inTForm1.FormDestroy procedure TForm1.FormCreate (Sender : TObject); begin ReportMemoryLeaksOnShutdown := True; try PkgHandle := LoadPackage ('PlugInPkg.bpl'); Button1.Enabled := True; except on E : EPackageError do Log ('Cannot load package: ' + E.Message); end; end; // --------------------------------- procedure TForm1.FormDestroy (Sender : TObject); begin if PkgHandle <> 0 then UnloadPackage (PkgHandle) end; I noticed if I comment out the lines then problem goes away, so it seems it is related to strings : procedure Identify (var IdentRec : TIdentRec); begin // IdentRec.Name := 'Sample PlugIn'; // IdentRec.Guid := 'F84E4265C05D4854A2BD781ED7A1BA66'; IdentRec.Version := 1; IdentRec.ApiVersion := 1; end; I tried to reset the record after the call but no luck!: IdentRec := Default (TIdentRec); Pass Record to package 4.zip
  11. I just found if I use SysUtils in any unit contained in the package then the problem is solved, no more AVs while closing the app!!! But what exactly SysUtils do to fix the problem ? no idea! I checked SysUtils and found some code related to package support in initialization section: {$IFDEF PACKAGE_SUPPORT} AddModuleUnloadProc(ModuleUnloaded); {$ENDIF PACKAGE_SUPPORT} finalization section {$IFDEF PACKAGE_SUPPORT} RemoveModuleUnloadProc(ModuleUnloaded); ClearHashTables; {$ENDIF PACKAGE_SUPPORT} So, it seems some packages need to use SysUtils to work correctly! Anyone knows if this is documented somewhere?! Just for testing I went a step further and extracted the package support code from SysUtils into a standalone unit named PackageSupport and then used this unit instead of SysUtils and the result is success too!, no more AVs. This unit is just for testing only, I will be using SysUtils of course. Here is the PackageSupport unit source , Sorry for the a bit long code but I don't know how to add collapse/expand on click feature like some other forums : unit PackageSupport; interface implementation uses Winapi.Windows; const cBucketSize = 1021; // better distribution than 1024 type PUnitHashEntry = ^TUnitHashEntry; TUnitHashEntry = record Next, Prev: PUnitHashEntry; LibModule: PLibModule; UnitName: MarshaledAString; DupsAllowed: Boolean; FullHash: Cardinal; end; TUnitHashArray = TArray<TUnitHashEntry>; TUnitHashBuckets = array[0..cBucketSize-1] of PUnitHashEntry; PModuleInfo = ^TModuleInfo; TModuleInfo = record Validated: Boolean; UnitHashArray: TUnitHashArray; end; var SysInitHC: Cardinal; ValidatedUnitHashBuckets: TUnitHashBuckets; UnitHashBuckets: TUnitHashBuckets; function AnsiStrUpper(Str: PWideChar): PWideChar; begin Result := CharUpperW(Str); end; function FindLibModule(Module: HModule): PLibModule; inline; begin Result := LibModuleList; while Result <> nil do begin if Result.Instance = Cardinal(Module) then Exit; Result := Result.Next; end; end; function HashNameMBCS(Name: MarshaledAString): Cardinal; const BufferLen = 261; var Len, NameLen: Cardinal; Data: PWideChar; Buffer: array[0..BufferLen - 1] of WideChar; I: Integer; begin NameLen := Length(Name); Len := UnicodeFromLocaleChars(CP_UTF8, 0, Name, NameLen, nil, 0); if Len > BufferLen then GetMem(Data, Len * SizeOf(Char)) else Data := @Buffer[0]; UnicodeFromLocaleChars(CP_UTF8, 0, Name, NameLen, Data, Len); AnsiStrUpper(Data); Result := 0; for I := 0 to Len - 1 do begin Result := (Result shl 5) or (Result shr 27); //ROL Result, 5 Result := Result xor Cardinal(Data[I]); end; if Data <> @Buffer[0] then FreeMem(Data); end; {$DEFINE X86ASM} function HashName(Name: MarshaledAString): Cardinal; {$IFDEF PUREPASCAL} var LCurr: MarshaledAString; begin { ESI -> Name } Result := 0; LCurr := Name; while LCurr^ <> #0 do begin { Abort on a MBCS character } if Ord(LCurr^) > 127 then begin Result := HashNameMBCS(LCurr); Exit; end; { Update the hash. Lowercase the uppercased charaters in the process } if LCurr^ in ['A' .. 'Z'] then Result := Result xor (Ord(LCurr^) or $20) else Result := Result xor Ord(LCurr^); { Go to next } Inc(LCurr); { Update the hashed value } Result := (Result shr 27) or (Result shl 5); end; end; {$ELSE !PUREPASCAL} {$IFDEF X86ASM} asm PUSH ESI PUSH EBX MOV ESI, Name XOR EAX, EAX PUSH ESI @@loop: ROL EAX, 5 MOV BL, [ESI] TEST BL, $80 // abort if there is a multibyte character and call HashNameMBCS JNZ @@MBCS CMP BL, 0 JE @@done CMP BL, 'A' JL @@LowerCased CMP BL, 'Z' JG @@LowerCased OR BL, 20H // make lower case @@LowerCased: XOR AL, BL INC ESI JMP @@loop @@done: POP ECX POP EBX POP ESI RET @@MBCS: POP EAX POP EBX POP ESI JMP HashNameMBCS end; {$ENDIF X86ASM} {$ENDIF !PUREPASCAL} procedure ModuleUnloaded(Module: HINST); var LibModule: PLibModule; ModuleInfo: PModuleInfo; I: Integer; HC: Cardinal; Buckets: ^TUnitHashBuckets; begin LibModule := FindLibModule(Module); if (LibModule <> nil) and (LibModule.Reserved <> 0) then begin ModuleInfo := PModuleInfo(LibModule.Reserved); if ModuleInfo.Validated then Buckets := @ValidatedUnitHashBuckets else Buckets := @UnitHashBuckets; for I := Low(ModuleInfo.UnitHashArray) to High(ModuleInfo.UnitHashArray) do begin if ModuleInfo.UnitHashArray[I].Prev <> nil then ModuleInfo.UnitHashArray[I].Prev.Next := ModuleInfo.UnitHashArray[I].Next else if ModuleInfo.UnitHashArray[I].UnitName <> nil then begin HC := HashName(ModuleInfo.UnitHashArray[I].UnitName) mod cBucketSize; if Buckets[HC] = @ModuleInfo.UnitHashArray[I] then Buckets[HC] := ModuleInfo.UnitHashArray[I].Next; end; if ModuleInfo.UnitHashArray[I].Next <> nil then ModuleInfo.UnitHashArray[I].Next.Prev := ModuleInfo.UnitHashArray[I].Prev; end; Dispose(ModuleInfo); LibModule.Reserved := 0; end; end; procedure ClearHashTables; var Module: PLibModule; begin Module := LibModuleList; while Module <> nil do begin if Module.Reserved <> 0 then begin Dispose(PModuleInfo(Module.Reserved)); Module.Reserved := 0; end; Module := Module.Next; end; end; initialization {$IFDEF PACKAGE_SUPPORT} AddModuleUnloadProc(ModuleUnloaded); {$ENDIF PACKAGE_SUPPORT} finalization {$IFDEF PACKAGE_SUPPORT} RemoveModuleUnloadProc(ModuleUnloaded); ClearHashTables; {$ENDIF PACKAGE_SUPPORT} end.
  12. Thanks Peter!, useful information! indeed! I will try what you suggested soon, but for now I have a question: I used to unload/destroy anything I load /create, if I use LoadLibrary () I need to use UnLoadLibrary () and If I use LoadPackage I need to use UnloadPackage, ... but you said: if I remove the UnloadPackage from my code, the app does not report any memory leak which is good!, but handles are a Windows thing which Delphi memory manager does not have control of them. But it seems the package did not get unloaded because the finalization section of package unit did not get executed! I created a small test app and package, the package contains only one unit like this: unit PkgUnit1; interface implementation uses SysUtils, Classes; var FileName : string; procedure FinalizeProc; begin var slTest := TStringList.Create; try slTest.Add ('Finalizing PkgUnit1'); slTest.SaveToFile (FileName); finally slTest.Free; end; end; initialization FileName := ExtractFilePath (ParamStr(0)) + '_TestPlugIn.txt'; DeleteFile (FileName); finalization FinalizeProc; end. If I don't explicitly unload the package the _TestPlugIn.txt will not be created which means the finalization section did not execute which means the package was not unloaded by the RTL package management If I call UnloadPackage the file will be created. How is that? I'm using Delphi 11 Community.
  13. There are some changes to the question but I cannot edit my post above!! I'm working to apply suggestions Peter mentioned in the post below.
  14. Marsil

    Removing String

    I don't understand what you just said!. As the OP did not completely describe what the input data will be, The answer is not meant to be valid for any data input. It's just a sample describing how to remove occurrences from a string. Can be adapter by OP to his own needs as he wish. If you have not noticed I didn't write this as ready to use function , Just a sample code snippet.
  15. Marsil

    Removing String

    As you changed your question I changed my answer above, I Hope it's what you are looking for. BTW, Your question has nothing to do with VCL, You should have posted it in General Help forum.
  16. Marsil

    Removing String

    var s:= 'This is (string) which need to (remove)'; var LeftIndex, RightIndex : Integer; LeftIndex := S.IndexOf (' ('); while LeftIndex > -1 do begin RightIndex := S.IndexOf (')'); if RightIndex = -1 then Break; S := S.Remove (LeftIndex, RightIndex - LeftIndex + 1); LeftIndex := S.IndexOf (' ('); end;
  17. I put a like for this hint! On the contrary I did get some useful information from this question and probably more coming
  18. Running a more realistic loop count of 100,000 times: TSearchRec var declared before loop : 0 ms inside loop: 3 ms 3 ms difference each 100,000 loop is a very small price to pay in exchange of encapsulation earned by limiting scope?? what do you think?
  19. Just ran a quick test (I'm not sure if it is a very good test) : // Before loop var FileRec : TSearchRec; var Stopwatch := TStopwatch.StartNew; for var i := 1 to 100000000 do begin FileRec.Name := 'Test'; end; Log (Stopwatch.ElapsedMilliseconds); // logs 205 // Inside loop var Stopwatch := TStopwatch.StartNew; for var i := 1 to 100000000 do begin var FileRec : TSearchRec; FileRec.Name := 'Test'; end; Log (Stopwatch.ElapsedMilliseconds); // logs 3129 (more than 15x) But when changing the inline variable type to Integer declare before loop timings: 161 161 161 161 161 declare Inside loop timings: 161 160 160 161 160 Increasing loop to 100 million times: before 1624 inside 1617 for a loop of millions times this is really negligible difference, nothing at all. So declaring simple types inside a loop may be the better choice after all??
  20. Yes I'm planning to do that but I thought I may get more useful insights by asking first. What I know now is that an inline variable is allocated at the point of declaration, not at the start of the block where it is declared. If I am not mistaken.
  21. That was one of my thoughts too!, Limiting scope is good, but we cannot get all good things at once can we?
  22. Thanks again, so a rule of thumb is never to declare inline variables inside a loop
  23. Thanks for the quick reply! I used to declare inline variables of simple types inside the loop, but this time it's a record of type TSearchRec, and I wondered if that was going to degrade performance. I guess that goes for other structured types too.
  24. I'm using the IDE dark theme and when I press F1 for help my eyes hurt (really) from the glare of help white pages! I uninstalled help in the hope of IDE will use the online help at https://docwiki.embarcadero.com but that didn't work, (the funny thing here is that I had to uninstall the whole thing not just help, no option for help uninstall only ) checked command line params but nothing there. Anyone knows a way to do this? PS: In case you may think online docwiki pages are also white?, I use the Dark Reader browser extension: https://i.ibb.co/py2qyxB/dt.png
  25. Yes it is. Dark Reader only changes web page colors, It does not change fonts or font sizes, Here is how Google Translator looks in my browser with Dark Reader enabled https://i.ibb.co/yn3vL2F/gt.png Some web sites already have dark mode so Dark Reader can be disabled for those specific sites, Click the site address under Dark Reader title to Disable/Enable Reader for this site, example: https://i.ibb.co/MVjqQ94/dr.png
×