Delphi,在 TPointerlist() 的 tenumstring 中编辑自动完成访问冲突
Delphi, edit Auto-Complete access violation in tenumstring at TPointerlist()
在@KenWhite 的 this autocomplete example 中,当调用 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;
(elt)
需要(@elt)
,[1]
需要[I]
:
TPointerList(@elt)[I]
那么代码就不会AV了
此外,输出字符串必须分配 SysAllocString...()
或 CoTaskMemAlloc()
,因为调用者将使用 COM 内存管理器来释放它们。您可以使用 RTL 的 ComObj.StringToLPOLESTR()
函数来为您处理该问题,这会生成 Delphi String
:
的 COM 分配的宽字符串副本
TPointerList(@elt)[I] := StringToLPOLESTR(FStrings[FCurrIndex]);
或者,您可以简单地获取 WideString
的数据指针的所有权,而不是在 WideString
已经创建一个后在内存中创建另一个副本:
wStr := FStrings[FCurrIndex];
TPointerList(@elt)[I] := Pointer(wStr);
Pointer(wStr) := nil;
在较新的版本(IIRC XE2 及更高版本)中,您可以按照 Remy 所说的进行操作,但在我看来您不应该这样做。
在 XE2 之前的版本(或任何版本)中,TPointerList
的定义是:
type
...
TPointerList = array[0..MaxListSize] of Pointer;
在较新的版本中,它是:
type
TPointerList = array of Pointer;
换句话说,它不再是static数组类型(值类型),而是变成了dynamic数组类型(a参考类型)现在。将未类型化输出参数的地址转换为这样的数组 can 结果很棘手。
定义上的差异解释了为什么在较新的版本中,代码无法正常工作:存在一个额外的间接级别。
现在,如果您将以下声明添加到 uAutoComplete.pas 文件:
type
TPointerList = array[0..65535] of Pointer; // assuming 65536 (2^16) entries are enough
然后文件的其余部分可以保持原来的样子。那么:
TPointerList(elt)[I] := ...
有效并且不需要您使用稍微棘手的间接强制转换为 Delphi 动态数组,而实际上不是。请注意,这也适用于旧版本。
在@KenWhite 的 this autocomplete example 中,当调用 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;
(elt)
需要(@elt)
,[1]
需要[I]
:
TPointerList(@elt)[I]
那么代码就不会AV了
此外,输出字符串必须分配 SysAllocString...()
或 CoTaskMemAlloc()
,因为调用者将使用 COM 内存管理器来释放它们。您可以使用 RTL 的 ComObj.StringToLPOLESTR()
函数来为您处理该问题,这会生成 Delphi String
:
TPointerList(@elt)[I] := StringToLPOLESTR(FStrings[FCurrIndex]);
或者,您可以简单地获取 WideString
的数据指针的所有权,而不是在 WideString
已经创建一个后在内存中创建另一个副本:
wStr := FStrings[FCurrIndex];
TPointerList(@elt)[I] := Pointer(wStr);
Pointer(wStr) := nil;
在较新的版本(IIRC XE2 及更高版本)中,您可以按照 Remy 所说的进行操作,但在我看来您不应该这样做。
在 XE2 之前的版本(或任何版本)中,TPointerList
的定义是:
type
...
TPointerList = array[0..MaxListSize] of Pointer;
在较新的版本中,它是:
type
TPointerList = array of Pointer;
换句话说,它不再是static数组类型(值类型),而是变成了dynamic数组类型(a参考类型)现在。将未类型化输出参数的地址转换为这样的数组 can 结果很棘手。
定义上的差异解释了为什么在较新的版本中,代码无法正常工作:存在一个额外的间接级别。
现在,如果您将以下声明添加到 uAutoComplete.pas 文件:
type
TPointerList = array[0..65535] of Pointer; // assuming 65536 (2^16) entries are enough
然后文件的其余部分可以保持原来的样子。那么:
TPointerList(elt)[I] := ...
有效并且不需要您使用稍微棘手的间接强制转换为 Delphi 动态数组,而实际上不是。请注意,这也适用于旧版本。