delphi - 当我从 Delphi 程序调用 Windows API 时,为什么会出现堆栈溢出?
问题描述
我的表单支持从 Windows 资源管理器拖放文件:
uses
ShellApi, System.IOUtils;
procedure TFormMain.FormCreate(Sender: TObject);
begin
DragAcceptFiles(Self.Handle, True);
end;
procedure TFormMain.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount, NameLen, i: Integer;
CurrFile: String;
FileSysEntries: TArray<String>;
begin
inherited;
hDrop := Msg.wParam;
try
FileCount := DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
for i := 0 to FileCount - 1 do
begin
NameLen := DragQueryFile(hDrop, i, nil, 0) + 1; //+1 for NULL
SetLength(CurrFile, NameLen);
DragQueryFile(hDrop, i, PWideChar(CurrFile), NameLen);
//If I don't do this...
SetLength(CurrFile, StrLen(PWideChar(CurrFile)));
if DirectoryExists(CurrFile) then
begin
//...I get a stack overflow here!
FileSysEntries := TDirectory.GetFiles(CurrFile, '*.*', TSearchOption.soAllDirectories);
//Rest removed for clarity...
end;
end;
finally
DragFinish(hDrop);
end;
end;
现在,如果我不#0
从字符串中删除 NULL ( ) 字符CurrFile
(请参阅 2nd SetLength
),我在调用时会出现堆栈溢出,TDirectory.GetFiles
现在我知道为什么了。
第二个SetLength
(那条#0
)真的有必要还是我应该NameLen - 1
为第一个做SetLength
?或者也许是别的什么?
解决方案
我看到几个问题:
您
DragAcceptFiles()
只在表单的OnCreate
事件中调用。如果在HWND
表单的生命周期内重新创建了表单(可能会发生!),您将失去接收WM_DROPFILES
消息的能力。您需要
DragAcceptFiles()
使用更新后的HWND
. 您可以覆盖表单的虚拟CreateWnd()
方法来处理它。或者,您可以覆盖 Form 的虚拟
CreateParams()
方法以启用每个创建的WS_EX_ACCEPTFILES
扩展窗口样式。HWND
您的消息处理程序正在调用
inherited
. 你不需要这样做。默认处理程序不会对消息做任何事情。您为
CurrFile
. 从技术上讲,您在调用时不需要包含空终止符SetLength()
,因为它会自动为空终止符分配额外的空间(Delphistring
是隐式以空终止的,因此PChar
强制转换可以与期望空终止字符指针的 C 样式 API 一起使用) .如果您确实在
string
' 的长度中包含空终止符,则必须在之后显式缩小string
s 长度,您正在这样做(但效率不高,因为DragQueryFile(i)
会告诉您在没有空终止符的情况下使用的长度,因此您不必使用StrLen()
) 手动计算它。但是,最好一开始就不要过度分配。显然,
#0
在字符串的长度中有多余的部分会导致问题TDirectory.GetFiles()
(或更可能的是TPath
,在TDirectory
内部使用)。您应该提交一份关于此的错误报告。但是,您确实需要确保不要将终止符留#0
在字符串的开头,因为文件系统路径 API 无论如何都不接受它。
试试这个:
uses
ShellApi, System.IOUtils;
procedure TFormMain.CreateWnd;
begin
inherited;
DragAcceptFiles(Self.Handle, True);
end;
procedure TFormMain.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount, NameLen, i: Integer;
CurrFile: String;
FileSysEntries: TArray<String>;
begin
hDrop := Msg.wParam;
try
FileCount := DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
for i := 0 to FileCount - 1 do
begin
NameLen := DragQueryFile(hDrop, i, nil, 0);
SetLength(CurrFile, NameLen);
DragQueryFile(hDrop, i, PChar(CurrFile), NameLen + 1);
if TDirectory.Exists(CurrFile) then
begin
FileSysEntries := TDirectory.GetFiles(CurrFile, '*.*', TSearchOption.soAllDirectories);
//...
end;
end;
finally
DragFinish(hDrop);
end;
end;
推荐阅读
- docker - Docker 容器消失得无影无踪
- php - 指向 XLS 的 Excel 超链接尝试使用 MS WORD 而不是 EXCEL 打开
- python - 在Python中以逆时针方向对多边形坐标列表进行排序
- tcp - Superagent 中的 TCP 会话重用
- python - mxnet-cu102mkl==1.6.0 今天从 pypi 消失了,为什么?
- laravel - 如何更改订阅表中的列类型
- sql - 如何在postgres中查询最近更新日期的同类别AVG值?
- python - 将使用 BeautifulSoup 检索到的数据保存到数组中
- node.js - 如何使用 Passport JS 在“登录逻辑”中添加错误/成功消息?
- flutter - 如何测试我的模型是否能够读取 json