首页 > 解决方案 > 当我从 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?或者也许是别的什么?

标签: delphiwinapidrag-and-dropdelphi-10.4-sydneygetfiles

解决方案


我看到几个问题:

  • DragAcceptFiles()只在表单的OnCreate事件中调用。如果在HWND表单的生命周期内重新创建了表单(可能会发生!),您将失去接收WM_DROPFILES消息的能力。

    您需要DragAcceptFiles()使用更新后的HWND. 您可以覆盖表单的虚拟CreateWnd()方法来处理它。

    或者,您可以覆盖 Form 的虚拟CreateParams()方法以启用每个创建的WS_EX_ACCEPTFILES扩展窗口样式。HWND

  • 您的消息处理程序正在调用inherited. 你不需要这样做。默认处理程序不会对消息做任何事情。

  • 您为CurrFile. 从技术上讲,您在调用时不需要包含空终止符SetLength(),因为它会自动为空终止符分配额外的空间(Delphistring是隐式以空终止的,因此PChar强制转换可以与期望空终止字符指针的 C 样式 API 一起使用) .

    如果您确实在string' 的长度中包含空终止符,则必须在之后显式缩小strings 长度,您正在这样做(但效率不高,因为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;

推荐阅读