Delphi,在TPointerlist()中编辑tenumstring中的自动完成访问冲突

时间:2018-04-27 23:03:06

标签: delphi autocomplete delphi-10.1-berlin

this autocomplete example @KenWhite中,当TPointerList()[]被调用时(通过Windows自动完成界面),Next函数会发生访问冲突。

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;

2 个答案:

答案 0 :(得分:5)

在较新的版本(IIRC XE2及以上版本)中,你可以做雷米所说的,但你不应该做IMO。

在XE2之前的版本(或任何版本)中,TPointerList的定义是:

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

在较新的版本中,它是:

type
  TPointerList = array of Pointer;

换句话说,现在它已成为动态数组类型(引用类型),而不是静态数组类型(值类型)。将无类型输出参数的地址强制转换为此类数组 会变得非常棘手。

定义上的差异解释了为什么在较新版本中,代码无法正常工作:还有一个额外的间接级别。

现在,如果您将以下声明添加到 uAutoComplete.pas 文件中:

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

然后文件的其余部分可以保持原来的状态。然后:

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

可以工作,并且不要求你在一些实际上没有的东西上使用稍微棘手的,间接的,强制转换为Delphi动态数组。请注意,这也适用于旧版本。

答案 1 :(得分:3)

(elt)必须为(@elt),而[1]必须为[I]

TPointerList(@elt)[I]

然后代码将不再是AV。

此外,输出字符串必须分配SysAllocString...()CoTaskMemAlloc(),因为调用者将使用COM内存管理器释放它们。您可以使用RTL的ComObj.StringToLPOLESTR()函数来处理这个问题,这使得COM分配了Delphi String的宽字符串副本:

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

或者,您可以简单地获取WideString的数据指针的所有权,而不是在WideString已经创建一个副本后再在内存中创建另一个副本:

wStr := FStrings[FCurrIndex];
TPointerList(@elt)[I] := Pointer(wStr);
Pointer(wStr) := nil;