Home > Back-end >  Why do I get a stack overflow when I call the Windows API from my Delphi program?
Why do I get a stack overflow when I call the Windows API from my Delphi program?

Time:10-09

My form supports drag'n'drop of files from the Windows Explorer:

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;

Now if I don't strip the NULL (#0) character off the CurrFile string (see 2nd SetLength) I get a stack overflow when I call TDirectory.GetFiles and I'm now sure why.

Is the second SetLength (that strips #0) really necessary or should I do NameLen - 1 for the first SetLength? Or maybe something else?

CodePudding user response:

I see a few issues:

  • you are calling DragAcceptFiles() only in the Form's OnCreate event. If the Form's HWND is ever re-created during the Form's lifetime (it can happen!), you will lose the ability to receive WM_DROPFILES messages.

    You would need to call DragAcceptFiles() again with the updated HWND. You can override the Form's virtual CreateWnd() method to handle that.

    Alternatively, you can override the Form's virtual CreateParams() method to enable the WS_EX_ACCEPTFILES extended window style for each HWND that is created.

  • your message handler is calling inherited. You don't need to do that. The default handler will not do anything with the message.

  • you are over-allocating memory for CurrFile. You technically DO NOT need to include the null terminator when calling SetLength(), as it will automatically allocate extra space for one (a Delphi string is implicitly null-terminated, so that PChar casts can be used with C-style APIs that expect null-terminated character pointers).

    If you DO include the null terminator in the string's length, you have to explicitly shrink the strings length afterwards, which you are doing (but not as efficiently as you could be, as DragQueryFile(i) will tell you the length to use without a null terminator, so you don't have to calculate it manually with StrLen()). But, it is better to simply not over-allocate to begin with.

    Apparently having that extra #0 in the string's length is causing problems for TDirectory.GetFiles() (or more likely, TPath, which TDirectory uses internally). You should file a bug report about that. But, you do need to make sure you don't leave the terminating #0 in the string's length to begin with, since filesystem path APIs don't accept it anyway.

Try this instead:

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;
  • Related