Starting with Windows Vista (and Windows Server 2008) the Windows API arsenal has a new and more powerful way of performing file operations like copy, move, delete and similar file and folder shell actions. The old (but still valid) SHFileOperation function is now replaced by the IFileOperation interface.
The IFileOperation exposes methods to copy, move, rename, create, and delete Shell items as well as methods to provide progress and error dialogs.
If you’ve used to working with SHFileOperation, and have been hit (for example) by the MAX_PATH problem, a solution can be found in IFileOperation. Here’s how to use IFileOperation in Delphi and ensure non existing folders are created in a copy operation.
The IFileOperation has many advantages over the SHFileOperation function, like being able to perform different operations in one call. You can delete a few files, copy some more, rename a folder, apply properties to a file – all in one operation. SHFileOperation can only do one operation at a time: copy, move, rename, or delete.
In this post I’ll concentrate on copying files. To make it simple the copy operation will copy a single file from its location to a destination folder and have the file name changed at the destination.
SHFileOperation
While you can locate lots of examples on how to use the SHFileOperation in Delphi, and the Jedi library also has it nicely wrapped in a component, here’s a short code to show you how to copy a file:
uses ShellApi; function CopyFileSHFileOperation(const srcFile, destFile : string) : boolean; var shFOS : TShFileOpStruct; begin ZeroMemory(@shFOS, SizeOf(TShFileOpStruct)); shFOS.Wnd := Application.MainForm.Handle; shFOS.wFunc := FO_COPY; shFOS.pFrom := PChar(srcFile + #0); shFOS.pTo := PChar(destFile + #0); //Do not ask the user to confirm the creation of a //new directory if the operation requires one to be created. shFOS.fFlags := FOF_NOCONFIRMMKDIR; result := SHFileOperation(shFOS) = 0; end;
There’s one nice feature of the SHFileOperation: if the destination folder does not exist the function will create it! The FOF_NOCONFIRMMKDIR flag ensures no dialogs are presented to the user by Windows asking if the destination folder should be created.
IFileOperation
Here’s how you can copy a file using the IFileOperation interface
uses ActiveX, ComObj, ShlObj;; function CopyFileIFileOperation(const srcFile, destFile : string) : boolean; //works on Windows >= Vista and 2008 server var r : HRESULT; fileOp: IFileOperation; siSrcFile: IShellItem; siDestFolder: IShellItem; destFileFolder, destFileName : string; begin result := false; destFileFolder := ExtractFileDir(destFile); destFileName := ExtractFileName(destFile); //init com r := CoInitializeEx(nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE); if Succeeded(r) then begin //create IFileOperation interface r := CoCreateInstance(CLSID_FileOperation, nil, CLSCTX_ALL, IFileOperation, fileOp); if Succeeded(r) then begin //set operations flags r := fileOp.SetOperationFlags(FOF_NOCONFIRMATION OR FOFX_NOMINIMIZEBOX); if Succeeded(r) then begin //get source shell item r := SHCreateItemFromParsingName(PChar(srcFile), nil, IShellItem, siSrcFile); if Succeeded(r) then begin //get destination folder shell item r := SHCreateItemFromParsingName(PChar(destFileFolder), nil, IShellItem, siDestFolder); //add copy operation if Succeeded(r) then r := fileOp.CopyItem(siSrcFile, siDestFolder, PChar(destFileName), nil); end; //execute if Succeeded(r) then r := fileOp.PerformOperations; result := Succeeded(r); OleCheck(r); end; end; CoUninitialize; end; end;
Note that the PerformActions method executes actions added by calling individual methods like CopyItem(s), DeleteItem(s) and alike.
However, there’s one BIG problem in using IFIleOperation: in a copy action, if the destination folder does not exist the action will fail!
This is true even if FOF_NOCONFIRMMKDIR is set using the SetOperationFlags method.
Note the line where SHCreateItemFromParsingName is used for the second time:
//get destination folder shell item r := SHCreateItemFromParsingName(PChar(destFileFolder), nil, IShellItem, siDestFolder);
It creates and initializes the shell item for the destination folder, and if this folder does not exist the call would fail.
IFileOperation.CopyItem + Force Destination Directory
The solution is to be found in the second parameter: const pbc: IBindCtx; This is a pointer to a bind context used to pass parameters to the parsing function (in this case SHCreateItemFromParsingName). We can use the binding context to force SHCreateItemFromParsingName not to query the file system – rather to just use what we provide. And what we will provide is a complex mixture of WIN32_FIND_DATA structure (specifying FILE_ATTRIBUTE_DIRECTORY), and instance of an object implementing IFileSystemBindData interface.
Here’s the full code, and the implementation of the required interface.
uses ActiveX, ComObj, ShlObj; function CopyFileIFileOperationForceDirectories(const srcFile, destFile : string) : boolean; //works on Windows >= Vista and 2008 server var r : HRESULT; fileOp: IFileOperation; siSrcFile: IShellItem; siDestFolder: IShellItem; destFileFolder, destFileName : string; pbc : IBindCtx; w32fd : TWin32FindData; ifs : TFileSystemBindData; begin result := false; destFileFolder := ExtractFileDir(destFile); destFileName := ExtractFileName(destFile); //init com r := CoInitializeEx(nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE); if Succeeded(r) then begin //create IFileOperation interface r := CoCreateInstance(CLSID_FileOperation, nil, CLSCTX_ALL, IFileOperation, fileOp); if Succeeded(r) then begin //set operations flags r := fileOp.SetOperationFlags(FOF_NOCONFIRMATION OR FOFX_NOMINIMIZEBOX); if Succeeded(r) then begin //get source shell item r := SHCreateItemFromParsingName(PChar(srcFile), nil, IShellItem, siSrcFile); if Succeeded(r) then begin //create binding context to pretend there is a folder there if NOT DirectoryExists(destFileFolder) then begin ZeroMemory(@w32fd, Sizeof(TWin32FindData)); w32fd.dwFileAttributes := FILE_ATTRIBUTE_DIRECTORY; ifs := TFileSystemBindData.Create; ifs.SetFindData(w32fd); r := CreateBindCtx(0, pbc); r := pbc.RegisterObjectParam(STR_FILE_SYS_BIND_DATA, ifs); end else pbc := nil; //get destination folder shell item r := SHCreateItemFromParsingName(PChar(destFileFolder), pbc, IShellItem, siDestFolder); //add copy operation if Succeeded(r) then r := fileOp.CopyItem(siSrcFile, siDestFolder, PChar(destFileName), nil); end; //execute if Succeeded(r) then r := fileOp.PerformOperations; result := Succeeded(r); OleCheck(r); end; end; CoUninitialize; end; end;
Here’s the TFileSystemBindData, IFileSystemBindData interface implementation:
type TFileSystemBindData = class (TInterfacedObject, IFileSystemBindData) fw32fd: TWin32FindData; function SetFindData(var w32fd: TWin32FindData): HRESULT; stdcall; function GetFindData(var w32fd: TWin32FindData): HRESULT; stdcall; end; ... function TFileSystemBindData.GetFindData(var w32fd: TWin32FindData): HRESULT; begin w32fd:= fw32fd; Result := S_OK; end; function TFileSystemBindData.SetFindData(var w32fd: TWin32FindData): HRESULT; begin fw32fd := w32fd; Result := S_OK; end;
Finally, the usage goes like:
//works even if "d:\f1\f2\f3\" does not exist! CopyFileIFileOperationForceDirectories('c:\somefile.png', 'd:\f1\f2\f3\copiedfile.png');
That’s it, now you can use IFIleOperation instead of SHFileOperation. Of course, you need to make sure your code runs on at least Windows Vista or Windows Server 2008. You can use the TOSVersion to check the operating system your code runs on.
Hi Žarko, thanks a lot for your posting on IFileOperation.
Could that code be expanded to copy folders (including all subfolders) instead of single files?
@Joe:
Just use:
CopyFileIFileOperationForceDirectories(‘c:\sourcefolder’, ‘d:\destination’);
And all files and subfolders of “c:\sourcefolder” would be copied into “d:\destination”.
i can’t compile this code under delphi 7
This code is very useful and I would try to use it in my program, but how can I use a progressbar with it that was embedded in the application gui and not as a dialogue?
The IFileOperation is implemented Winapi.ShlObj from version D2010
Thanks of the interesting article! Are there any performance difference doing the same operations using SHFileOperations and using IFileOperations? I am very interested in timing results. Thanks
TWin32FindData is defined in WinApi.Windows, so this should be included in the uses statement:
uses ActiveX, ComObj, ShlObj, Windows;