Politically Correct File Operations


Deleting, copying, renaming, and moving files should be simple, right? Visual Basic provides the Kill, FileCopy, and Name statements for the first three operations and, contrary to the documentation, you can also use Name to move files across disks. Who could be offended by such simple features?


Well, you might be offended if the FileCopy statement overwrote an important file without asking for confirmation. I might be ticked off if your Kill statement accidentally killed all the files in the DeleteMe.Not folder, with no way to ­recover them. Our customers might be frustrated and confused if our FileCopy brought the whole system to a halt while copying a 1-MB file from a remote drive to a floppy disk with no visual clue to why the keyboard and the mouse weren’t responding.


The SHFileOperation function provides the means to delete, rename, copy, and move files with all the protections, warnings, and status reports you could ever imagine—and some that you never dreamed of. I use SHFileOperation in my CopyAnyFile, DeleteAnyFile, RenameAnyFile, and MoveAnyFile functions.


For the SHFileOperation function, I use both Visual Basic UDT and Declare statements because they work better than the type library for strings in structures. There’s something very unusual about the C structure, so let’s have a look:

Private Declare Function SHFileOperation Lib "shell32.dll" _
Alias "SHFileOperationA" (lpFileOp As SHFILEOPSTRUCT) As Long

Private Type SHFILEOPSTRUCT
hWnd As Long ' Window owner of any dialogs
wFunc As Long ' Copy, move, rename, or delete code
pFrom As String ' Source file
pTo As String ' Destination file or directory
fFlags As Integer ' Options to control the operations

Busted


Many old programs (including the previous version of a well-known programming environment which I won’t name because it has been fixed) provide an example of how not to program files under 32-bit Windows. When these programs modify a file, the creation time and the modification time are always the same. In other words, they don’t really modify the files when you save them. Instead, they re-create the file, probably in the same way most 16-bit programs modify a file. They copy to a temporary file, make the changes on the copy, and, if all goes well, close the temporary, delete the old version, and rename the temporary to the old name.


Uh-oh. What’s that flashing red light in the mirror?


“OK, buddy. You know what you did wrong?”


“I didn’t do anything, officer. I’ve been saving files that way for years, and nobody ever complained before.”


“Yeah, that’s because you were driving on 16-bit roads. We couldn’t recognize you guys there, but I can spot an old MS-DOS programmer a mile off on this new 32-bit superhighway.”


“Hey, how did you know?”

“Look at that file creation time. When I see one that’s the same as the write time, 95 times out of 100 it’s one of you old programmers replacing a file without copying the time and attributes.”

“What’s wrong with that?”

    fAnyOperationsAbortedLo As Integer ' Indicates partial failure
fAnyOperationsAbortedHi As Integer
hNameMappingsLo As Long ' Array indicating each success
hNameMappingsHi As Long
lpszProgressTitleLo As Long ' Title for progress dialog
lpszProgressTitleHi As Long
End Type

The first few fields indicating the operation and the files to work on are easy. But there’s something wrong with the fFlags field. It’s an Integer instead of a Long, and in the C include files the designer took special steps to declare that the structure members shouldn’t be aligned on DWORD boundaries. Visual Basic (and some other languages) always align structure fields on DWORD boundaries because it makes access more efficient. The only way to fake the original alignment is to split the fields into separate high and low words. Fortunately,


“I’ll bet this file is actually three months, maybe a year old. You’re falsifying data. It might be an innocent mistake, but you’re hurting yourself. The file taxes are higher on newer files. And it also screws up our maintenance equipment. Tell you what—I’ll give you a break. Just take this warning ticket into the station within three days and pick up our ReplaceFile function from FILETOOL.BAS. Use it from now on, and we’ll cancel the 4-MB disk fine.”

Sub ReplaceFile(sOld As String, sTmp As String)
Dim fnd As WIN32_FIND_DATA, hFind As Long, hOld As Long, f As Boolean
' Get file time and attributes of old file hFind = FindFirstFile(sOld, fnd)
If hFind = hInvalid Then ApiRaise Err.LastDllError
' Replace by deleting old and renaming new to old
Kill sOld
Name sTmp As sOld
' Assign old attributes and time to new file
hOld = lopen(sOld, OF_WRITE Or OF_SHARE_DENY_WRITE)
If hOld = hInvalid Then ApiRaise Err.LastDllError
f = SetFileTime(hOld, fnd.ftCreationTime, _
fnd.ftLastAccessTime, fnd.ftLastWriteTime)
If f Then ApiRaise Err.LastDllError
lclose hOld
f = SetFileAttributes(sOld, fnd.dwFileAttributes)
If f Then ApiRaise Err.LastDllError
End Sub

“Now why didn’t I think of that? Hey, thanks.”

“No problem, buddy. But if I catch you overwriting file times again, I’m going to throw the disk at you.”

these fields aren’t as important as the other fields, and in fact, my code doesn’t use them. But if you want to, you’re in for a struggle.

Why the packing? Normally, the only reason to pack structures is so that you can store more of them on disk, but nobody’s going to be using a big array of these structures. If the designers had some good reason (that I can’t imagine) to pack the structure and make the fFlags field (with its bad Hungarian name) an Integer rather than a Long, why not put it at the end so it doesn’t throw all the others out of alignment? This is typical of the SH functions, all of which seem half-baked, unfinished, and inconsistent with the rest of the Windows API. For example, why is the structure given the redundant name SHFILEOPSTRUCT instead of SHFILEOP or just FILEOP? If you like hopeless arguments about API design, the shell functions leave plenty of room for debate.


The CopyAnyFile function illustrates the general form of my wrapper functions. You can find the others in FILETOOL.BAS.

Function CopyAnyFile(sSrc As String, sDst As String, _
Optional Options As Long = 0, _
Optional Owner As Long = hNull) As Boolean
If MUtility.HasShell Then
Dim fo As SHFILEOPSTRUCT, f As Long
fo.wFunc = FO_COPY
Debug.Print TypeName(fo.wFunc)
fo.pFrom = sSrc
fo.pTo = sDst
fo.fFlags = Options
fo.hWnd = Owner
' Mask out invalid flags
fo.fFlags = fo.fFlags And FOF_COPYFLAGS
f = SHFileOperation(fo)
CopyAnyFile = (f = 0)
Else
' For Windows NT 3.51
On Error Resume Next
' FileCopy expects full name of destination file
FileCopy sSrc, sDst
If Err Then
Err = 0
' CopyAnyFile can handle destination directory
sDst = MUtility.NormalizePath(sDst) & _
MUtility.GetFileBaseExt(sSrc)
FileCopy sSrc, sDst
End If
' Enhance further to emulate SHFileOperation options
' such as validation and wild cards
CopyAnyFile = (Err = 0)
End If
End Function

The trick in calling FileCopy, FileMove, FileRename, and FileDelete is to pass the correct flags. Table 11-2 offers a brief summary of the flags. The default behavior is to produce a confirmation dialog box if anything is to be deleted or overwritten and to put up a progress dialog box if the operation lasts long enough to make a user wonder what’s going on. You can test other operations with the Windows Interface Tricks application, shown in Figure 11-10 on the following page.

Flag Purpose
FOF_NOCONFIRMATION Overwrite or delete files without confirmation
FOF_ALLOWUNDO Put deleted files (except those from floppy disks) in Recycle Bin
FOF_SILENT Prevent display of a progress dialog box for slow operations
FOF_SIMPLEPROGRESS Simplify the progress dialog box by not showing filenames
FOF_RENAMEONCOLLISION Create new numbered files (Copy #1 of...) if copied or moved files conflict with existing files
FOF_MULTIDESTFILES Copy or move a wildcard source to multiple
target files rather than to a single destination directory
FOF_FILESONLY Interpret a wildcard source to mean files only, not directories
FOF_NOCONFIRMMKDIR Create any needed destination directories without confirmation


Table 11-2. Option flags for file operation functions.