Reputation: 2244
I have a VB6 app which gets a file path and must check if this path points to an actual file, as opposed to an LPT device, pipe, etc. I'm using the APIs CreateFile, GetFileType, and then CloseHandle.
Although this code works, when I attach a debugger to the program, the debugger breaks on an exception, saying that CloseHandle is trying to close a non-existing handle (or, sometimes, saying "invalid argument"). This happens even though I check (as you can see from the code) that the handle <> INVALID_HANDLE_VALUE.
I have two questions.
First, I think I need to change the call to CreateFile, from
handle = CreateFile(FilePath, 0, 0, ByVal 0&, OPEN_EXISTING, 0&, 0&)
...to
handle = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)
am I correct about this? I'm only using CreateFile to get the handle to pass over to GetFileType. I'm not actually doing anything with this file.
Second, why could this be happening? I'm checking if handle <> INVALID_HANDLE_VALUE, but still getting this error!
The results of the a dump are attached after the code.
Private Const OPEN_EXISTING As Long = 3
Private Const FILE_SHARE_READ As Long = &H1
Private Const INVALID_HANDLE_VALUE As Long = -1
Private Const FILE_TYPE_DISK As Long = &H1
Private Const FILE_TYPE_CHAR As Long = &H2
Private Const FILE_TYPE_PIPE As Long = &H3
Private Const FILE_TYPE_REMOTE As Long = &H8000
Private Const FILE_TYPE_UNKNOWN As Long = &H0
Private Declare Function CloseHandle_API Lib "kernel32" Alias "CloseHandle" (ByVal hObject As Long) As Long
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function GetFileType_API Lib "kernel32" Alias "GetFileType" (ByVal hFile As Long) As Long
Private Function IsAnActualFile(FilePath As String, fileType As Long) As Boolean
On Error GoTo errHandle
Dim handle As Long
Dim lpSecurityAttributes As Long
lpSecurityAttributes = 0
handle = CreateFile(FilePath, 0, 0, ByVal 0&, OPEN_EXISTING, 0&, 0&)
If handle <> INVALID_HANDLE_VALUE Then
fileType = GetFileType_API(handle)
If fileType = FILE_TYPE_DISK Then
IsAnActualFile = True
End If
CloseHandle_API handle
End If
errHandle:
If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function
Here is the dump:
0:000> kb
ChildEBP RetAddr Args to Child
0018edf0 74ebc463 000038ec 000038ec 0018ee10 ntdll!NtClose+0x12
0018ee00 75141418 000038ec 00000001 0018ee68 KERNELBASE!CloseHandle+0x2d
0018ee10 1107ee38 000038ec 0018f32c 00000000 kernel32!CloseHandleImplementation+0x3f
0018ee68 1107d3ef 19cb5968 0018f32c 0018f158 MyProgram!Document::IsAnActualFile+0xb8
Upvotes: 0
Views: 1067
Reputation: 597971
Yes, you should change the permissions you are requesting when opening the file. You are requesting exclusive access, which will fail if anybody has the file open for any reason.
As for the CloseHandle()
error, when the file is successfully opened, you are closing the file handle twice. Your errHandle
is always entered, but you did not reset your variable after closing it the first time:
Private Function IsAnActualFile(FilePath As String) As Boolean
On Error GoTo errHandle
Dim handle As Long
handle = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE Or FILE_SHARE_DELETE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)
If handle <> INVALID_HANDLE_VALUE Then
fileType = GetFileType_API(handle)
If fileType = FILE_TYPE_DISK Then
IsAnActualFile = True
End If
CloseHandle_API handle
handle = INVALID_HANDLE_VALUE ' <-- add this!
End If
errHandle:
If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function
Alternatively, don't let the code get into the errHandle
after calling CloseHandle()
the first time:
Private Function IsAnActualFile(FilePath As String, fileType As Long) As Boolean
On Error GoTo errHandle
Dim handle As Long
handle = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE Or FILE_SHARE_DELETE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)
If handle <> INVALID_HANDLE_VALUE Then
fileType = GetFileType_API(handle)
If fileType = FILE_TYPE_DISK Then
IsAnActualFile = True
End If
CloseHandle_API handle
Exit Function ' <-- add this!
End If
errHandle:
If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function
That being said, you might also want to consider what happens if the caller passes in a path to a directory or a root drive, instead of an actual file. CreateFile()
can still succeed, and GetFileType()
is likely to still return FILE_TYPE_DISK
since it is on the hard drive.
A simpler solution would be to use GetFileAttributes()
instead of CreateFile()
so that you do not have to actually open the file at all. GetFileAttributes()
can only query files and directories on the hard drive, anything else will fail. So no need for GetFileType()
, either.
Private Const FILE_ATTRIBUTE_DIRECTORY as Long = &H10
Private Const FILE_ATTRIBUTE_DEVICE as Long = &H40
Private Const FILE_ATTRIBUTE_REPARSE_POINT as Long = &H400
Private Const INVALID_FILE_ATTRIBUTES as Long = -1
Private Declare Function GetFileAttributes_API Lib "kernel32" Alias "GetFileAttributesA" (ByVal lpFileName As String) As Long
Private Function IsAnActualFile(FilePath As String) As Boolean
Dim attrs As Long
attrs = GetFileAttributes_API(FilePath)
If attrs <> INVALID_FILE_ATTRIBUTES Then
If (attrs And (FILE_ATTRIBUTE_DIRECTORY Or FILE_ATTRIBUTE_DEVICE Or FILE_ATTRIBUTE_REPARSE_POINT)) = 0 Then
IsAnActualFile = True
End If
End If
End Function
Upvotes: 2