Reputation: 14622
I simply need to check if a directory exists! But if the directory is "E:\Test" where E: is CD/DVD drive and there is no disk inserted on it, I see the following Delphi and Windows issues.
The first method:
function DirExists(Name: string): Boolean;
var
Code: Integer;
begin
Code := GetFileAttributesW(PChar(Name));
Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;
It gives Range Check Error
. I can't use {$RANGECHECKS OFF}
, {$RANGECHECKS ON}
blocks because:
$RANGECHECKS
option.Drive is not ready
instead Range Check Error
. But I simply need to check if directory exists without any error dialogs for user.The second method:
if DirectoryExists(Name, True) then ...
This function returns True
for non-existing E:\Test
directory on empty CD/DVD drive. So can't use it because it works incorrectly.
But then, how to find out if a directory exists?
P.S. I think the error exists with any CD/DVD drive. But I am using Windows 7 x64 on VMWare Fusion 5 under Mac OS X 10.8.4 with external CD/DVD drive.
Upvotes: 4
Views: 4650
Reputation: 596437
David has the right answer in regards to avoiding the range check error. But if you don't want to do that, you can still turn off/on {$RANGECHECKS}
manually, just use {$IFOPT}
to do it conditionally so surrounding code is not affected, eg:
function DirExists(Name: string): Boolean;
var
Code: Integer;
begin
{$IFOPT R+}
{$DEFINE _RPlusWasEnabled}
{$R-}
{$ENDIF}
Code := GetFileAttributesW(PChar(Name));
Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
{$IFDEF _RPlusWasEnabled}
{$UNDEF _RPlusWasEnabled}
{$R+}
{$ENDIF}
end;
With that said, checking the result of GetFileAttributes()
for INVALID_FILE_ATTRIBUTES
alone is not enough. A directory could exist but just not be accessible. That is why the RTL's DirectoryExists()
function checks GetLastError()
for multiple error codes (ERROR_PATH_NOT_FOUND
, ERROR_BAD_NETPATH
, ERROR_NOT_READY
, etc) looking for that possible condition. Another thing DirectoryExists()
can do is optionally check if the specified path is actually a shortcut to a directory, and if so check if the target directory exists or not.
Update: here is the implementation of SysUtils.DirectoryExists()
in XE3:
function DirectoryExists(const Directory: string; FollowLink: Boolean = True): Boolean;
{$IFDEF MSWINDOWS}
var
Code: Cardinal;
Handle: THandle;
LastError: Cardinal;
begin
Result := False;
Code := GetFileAttributes(PChar(Directory));
if Code <> INVALID_FILE_ATTRIBUTES then
begin
if faSymLink and Code = 0 then
Result := faDirectory and Code <> 0
else
begin
if FollowLink then
begin
Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle <> INVALID_HANDLE_VALUE then
begin
CloseHandle(Handle);
Result := faDirectory and Code <> 0;
end;
end
else if faDirectory and Code <> 0 then
Result := True
else
begin
Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle <> INVALID_HANDLE_VALUE then
begin
CloseHandle(Handle);
Result := False;
end
else
Result := True;
end;
end;
end
else
begin
LastError := GetLastError;
Result := (LastError <> ERROR_FILE_NOT_FOUND) and
(LastError <> ERROR_PATH_NOT_FOUND) and
(LastError <> ERROR_INVALID_NAME) and
(LastError <> ERROR_BAD_NETPATH) and
(LastError <> ERROR_NOT_READY);
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF POSIX}
var
StatBuf, LStatBuf: _stat;
Success: Boolean;
M: TMarshaller;
begin
Success := stat(M.AsAnsi(Directory, CP_UTF8).ToPointer, StatBuf) = 0;
Result := Success and S_ISDIR(StatBuf.st_mode);
if not Result and (lstat(M.AsAnsi(Directory, CP_UTF8).ToPointer, LStatBuf) = 0) and
S_ISLNK(LStatBuf.st_mode) then
begin
if Success then
Result := S_ISDIR(StatBuf.st_mode)
else if not FollowLink then
Result := True;
end;
end;
{$ENDIF POSIX}
The implementation in XE4 is the same with only one difference - the Windows version also includes a check for LastError <> ERROR_BAD_NET_NAME
when calling GetLastError()
.
Upvotes: 3
Reputation: 612993
You can just fix your function so that it doesn't cause range check errors:
function DirExists(Name: string): Boolean;
var
Code: DWORD;
begin
Code := GetFileAttributes(PChar(Name));
Result := (Code <> INVALID_FILE_ATTRIBUTES)
and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;
The range check errors are due to you mixing signed and unsigned types. Remy also points out the very useful trick to set compiler options and then restore to the prevailing state. That's a good trick to learn, but you don't need it here.
The XE3 implementation of DirectoryExists is modified to address the problem that you have encountered. So, if using XE3+ was an option, you should take it.
To suppress the system error dialogs call this at process startup:
procedure SetProcessErrorMode;
var
Mode: DWORD;
begin
Mode := SetErrorMode(SEM_FAILCRITICALERRORS);
SetErrorMode(Mode or SEM_FAILCRITICALERRORS);
end;
Doing this is best practise as described on MSDN:
Best practice is that all applications call the process- wide SetErrorMode function with a parameter of SEM_ FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application.
Upvotes: 4
Reputation: 14622
Update Delphi XE2 to Delphi XE3+ or use the following function:
function DirectoryExistsDelphiXE2(const Directory: string; FollowLink: Boolean = True): Boolean;
var
Code: Cardinal;
Handle: THandle;
LastError: Cardinal;
begin
Result := False;
Code := GetFileAttributes(PChar(Directory));
if Code <> INVALID_FILE_ATTRIBUTES then
begin
if faSymLink and Code = 0 then
Result := faDirectory and Code <> 0
else
begin
if FollowLink then
begin
Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle <> INVALID_HANDLE_VALUE then
begin
CloseHandle(Handle);
Result := faDirectory and Code <> 0;
end;
end
else if faDirectory and Code <> 0 then
Result := True
else
begin
Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle <> INVALID_HANDLE_VALUE then
begin
CloseHandle(Handle);
Result := False;
end
else
Result := True;
end;
end;
end
else
begin
LastError := GetLastError;
Result := (LastError <> ERROR_FILE_NOT_FOUND) and
(LastError <> ERROR_PATH_NOT_FOUND) and
(LastError <> ERROR_INVALID_NAME) and
(LastError <> ERROR_BAD_NETPATH) and
(LastError <> ERROR_NOT_READY);
end;
end;
Upvotes: 1