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'sOnCreateevent. If the Form'sHWNDis ever re-created during the Form's lifetime (it can happen!), you will lose the ability to receiveWM_DROPFILESmessages.You would need to call
DragAcceptFiles()again with the updatedHWND. You can override the Form's virtualCreateWnd()method to handle that.Alternatively, you can override the Form's virtual
CreateParams()method to enable theWS_EX_ACCEPTFILESextended window style for eachHWNDthat 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 callingSetLength(), as it will automatically allocate extra space for one (a Delphistringis implicitly null-terminated, so thatPCharcasts 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 thestrings length afterwards, which you are doing (but not as efficiently as you could be, asDragQueryFile(i)will tell you the length to use without a null terminator, so you don't have to calculate it manually withStrLen()). But, it is better to simply not over-allocate to begin with.Apparently having that extra
#0in the string's length is causing problems forTDirectory.GetFiles()(or more likely,TPath, whichTDirectoryuses internally). You should file a bug report about that. But, you do need to make sure you don't leave the terminating#0in 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;
