-1

In this autocomplete example by @KenWhite, the Next function has an access violation when TPointerList()[] is called ( by the windows autocomplete interface.)

D10.1u2, Win10.64

function TEnumString.Next(celt: Integer; out elt;
   pceltFetched: PLongint): HResult;
var
 I: Integer;
wStr: WideString;
begin
 I := 0;
 while (I < celt) and (FCurrIndex < FStrings.Count) do
   begin
     wStr := FStrings[FCurrIndex];
     TPointerList(elt)[1] := PWideChar('abcd');  //access violation
     TPointerList(elt)[1] := CoTaskMemAlloc(8);  //access violation
     TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1)); //access violation
     StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
     Inc(I);
     Inc(FCurrIndex);
  end;
 if pceltFetched <> nil then
  pceltFetched^ := I;
 if I = celt then
 Result := S_OK
else
  Result := S_FALSE;
end;
Henry Crun
  • 235
  • 1
  • 11

2 Answers2

7

In newer versions (IIRC XE2 and above), you can do what Remy says, but IMO you should not.

In versions before XE2 (or whatever version it was), the definition of TPointerList was:

type
  ...
  TPointerList = array[0..MaxListSize] of Pointer;

In the newer versions, it is:

type
  TPointerList = array of Pointer;

In other words, instead of a static array type (a value type), it has become a dynamic array type (a reference type) now. Casting the address of an untyped out parameter to such an array can turn out to be tricky.

The difference in definition explains why in newer versions, the code does not work properly: there is one extra level of indirection.

Now if you add the following declaration to the uAutoComplete.pas file:

type
  TPointerList = array[0..65535] of Pointer; // assuming 65536 (2^16) entries are enough

then the rest of the file can remain what it used to be. Then:

TPointerList(elt)[I] := ...

works and does not require you to use a slightly tricky, indirect, cast to a Delphi dynamic array on something that is actually not. Note that this will also work in older versions.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • 1
    Nice catch. Indeed this answers the question *"The link was a full working? example.*" which is the only thing that resembles a question posted by the OP, although it's *asked* in the comments and although not exactly sure what it means. – Sertac Akyuz Apr 29 '18 at 16:37
4

(elt) needs to be (@elt), and [1] needs to be [I]:

TPointerList(@elt)[I]

Then the code will not AV anymore.

Also, the output strings must be allocated with either SysAllocString...() or CoTaskMemAlloc(), as the caller is going to use the COM memory manager to free them. You can use the RTL's ComObj.StringToLPOLESTR() function to handle that for you, which makes a COM-allocated wide string copy of a Delphi String:

TPointerList(@elt)[I] := StringToLPOLESTR(FStrings[FCurrIndex]);

Alternatively, you can simply take ownership of the WideString's data pointer instead of making yet another copy in memory after WideString has already made one:

wStr := FStrings[FCurrIndex];
TPointerList(@elt)[I] := Pointer(wStr);
Pointer(wStr) := nil;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks Remy, `TPointerList(@elt)[I] := StringToLPOLESTR(FStrings[FCurrIndex]);` fixed it. Strange that the original code appears to have been used by multiple people successfully, including our esteemed DH – Henry Crun Apr 28 '18 at 02:23
  • Note that the definition of `TPointerList` was changed, IIRC in Delphi XE2. Before XE2 it was `TPointerList = array[0..MaxListSize-1] of Pointer;`. In XE2, it became `TPointerList = array of Pointer;`. That could explain why the original example code, as it was, does not work anymore in newer versions: there is one extra level of indirection. It is also a little problematic to cast to what has become a dynamic array here, IMO. – Rudy Velthuis Apr 28 '18 at 23:41
  • @RudyVelthuis true, which is why I don't like using `TPointerList` at all. I would probably just cast `elt` to `PPointer` and increment+dereference it on each loop iteration – Remy Lebeau Apr 29 '18 at 02:35