I wrote about autocompletion for TEdits before here, here and here.
My dzlib contains several helper functions to add TEdit-autocompletion for directories, files (with a filter) and generic strings from a string list. All of them worked fine for me and were used in several our internal programs. But that changed when trying them with Delphi 11 as Carlo Barazzetta found out: Using these functions now raised a system exception as soon as the IEnumString.Next method was being called the first time. It turned out that the way I tried to cheat with the memory allocation of WideStrings no longer works. This is the code that failed:
function TEnumStringDirectories.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; type TPointerList = array[0..0] of Pointer; var i: Integer; wStr: WideString; begin i := 0; while (i < celt) and FindNextDir(wStr) do begin TPointerList(elt)[i] := Pointer(wStr); Pointer(wStr) := nil; // <=== here Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end;
In the line with the "<=== here" comment I assign a nil pointer to the wStr variable. The idea is to clear the variable without triggering reference counting, so the pointer assigned to the pointer list was the only reference left. This no longer worked with Delphi 11 and 12, so the memory for the string was freed and when the autocomplete interface tried to free it again this caused the exception.
The fix was simple. Rather than cheat, do it the right way: Allocate some memory, copy the content of wStr there and assign that memory to the pointer list.
function TEnumStringHelper.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; function AllocNewStr(const _Wstr: WideString): Pointer; var Size: Integer; begin Size := SizeOf(Wchar) * (Length(_Wstr) + 1); Result := CoTaskMemAlloc(Size); StringToWideChar(_Wstr, Result, Size); end; type TPointerList = array[0..0] of Pointer; var i: Integer; s: string; wStr: WideString; begin i := 0; while (i < celt) and TryGetNext(s) do begin wStr := s; TPointerList(elt)[i] := AllocNewStr(wStr); // <== here Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end;
This solved the problem. The fix has been committed to revision #1737 on SourceForge.
If you paid attention to the code above you might have noticed that the first example was a method of TEnumStringDirectories while the second was a method of TEnumStringHelper. That’s because I took the opportunity to consolidate the boiler plate code from all the TEnumStringXxx implementations into a new TEnumStringHelper class. It implements the methods Next, Clone, Skip and Reset and only has two virtual abstract methods that need to be overridden by descendants: TryGetNext and doReset.
So even though I added some code for allocating that string, the net result was less (duplicate) code. By some metrics this would be negative productivity, but I love it.
Discussion about this post in the international Delphi Praxis forum.