Delphi中的DirectoryExists函数对于网络映射单元的行为有些奇怪。

6
在Delphi XE中,当我使用以下输入调用SysUtils DirectoryExists函数时:
'Y:\blabla\'
其中Y是网络映射单元,它正确地返回false,因为blabla不存在。
但是当我使用以下输入调用时:
'Y:\blabla\Y:\bla'
它返回true。
文档不太好,我在互联网上没有找到任何人有同样的问题。
也许这里已经有人遇到了这个问题,或者知道发生了什么?
3个回答

6
似乎DirectoryExists函数的实现存在一个错误。
以下是该函数的相关代码。
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
    ...
    //more code
    ...
  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);
  end;
end;
{$ENDIF MSWINDOWS}

当你调用GetFileAttributes函数失败时,将会对GetLastError方法的结果与一组可能的值进行比较。但在你的情况下,传递一个无效路径将返回一个ERROR_BAD_PATHNAME(161)代码,因此该函数返回True。

1
+1. 根据使用与原始问题相同的路径进行的快速测试,此问题已在XE3中得到修复。虽然我不知道第一个修复是在哪个版本中进行的,但我猜测是XE2,因为它包含了XPlatform相关内容。 - Ken White
在XE3 up1中,该代码还会检查ERROR_NOT_READY。但是当我运行代码时,我得到的是LastErrorERROR_INVALID_NAME,或者如果Y:没有映射,则为ERROR_PATH_NOT_FOUND。我从未见过ERROR_BAD_PATHNAME。@RRUZ你使用的是哪个操作系统?一旦我们能够解决这个问题,似乎应该提交一个QC报告。 - David Heffernan
1
@DavidHeffernan,我正在使用Win7 x64,这个DirectoryExists('Y:\blabla\Y:\bla')返回true,但是这个DirectoryExists('Y:\blabla')返回false。 - RRUZ
1
@DavidHeffernan,您是否创建了一个Y:网络驱动器并测试了代码? - RRUZ
1
@DavidHeffernan,是的,这个问题在网络驱动器中可以复现。 - RRUZ
显示剩余5条评论

0

这个问题在XE8中仍然存在(可能也存在于其他版本中)。如上所指出的RRUZ,问题出在SysUtils实现的DirectoryExists()TDirectory.Exists()两者之中。

问题出在那个冗长的“检查”列表上,它假设这些是“无效文件属性”在“文件夹存在”的上下文中被返回的唯一有效原因。但我们调用这些例程的几乎总是为了检查我们是否能够实际使用所询问的文件夹。INVALID_FILE_ATTRIBUTES几乎总是意味着我们不能使用。无论哪种方式,目前正在进行的测试都不能产生合理的结果。仅此事实就使它成为完全多余的练习,因为它允许某些其他故障代码通过网络并将最终结果设置为TRUE,而不应该是这样。虽然我只能想象最初有一些方法来解决这种疯狂的方法,比如“哦,它存在但可能是无效的”,但它违反了未来可能会带来许多由尚未知道的文件系统和/或硬件生成的代码的内在真相:因此,对于99.9%的用途,获得INVALID_FILE_ATTRIBUTES应该意味着文件夹不可用,因此在已经建立了这一点之后允许返回TRUE是不合逻辑的。

很遗憾,我们无法确定是否存在依赖于当前行为来识别“存在问题路径”的代码 - 在这种情况下,现有的例程调用必须先进行路径语法验证检查:否则它会说一个用错误语法描述的路径“存在”,这是荒谬的!之后你不能重新执行GetLastError,因为错误已经被清除。

因此,唯一的解决方法是在任何Sysutils.DirectoryExists和(不幸的是)TDirectory.Exists调用周围包装消除问题的代码。例如:

DirectoryUsable(const Directory: string; FollowLink: Boolean = True): Boolean;
begin
   Result := GetFileAttributes(PChar(Directory)) <> INVALID_FILE_ATTRIBUTES;
   if Result then Result := DirectoryExists( Directory, FollowLink );
end;

要么这样,要么你对所有你所了解到的“坏”情况进行单独的前期验证,而Embarcadero却忽略了这些情况 - 也就是说,玩的是和他们一样的失败游戏。 可怕,但这就是现实。
你也可以自己修改SysUtils库,添加缺失的情况,但你需要在每个Delphi版本发布时重新做同样的工作,并且对于每个新的情况都要进行修改。也许Embarcadero最好能够“下定决心”,找到一个更好的解决方案来解决这个问题。也许可以通过使用另一个默认标志参数来实现,该参数表示“拒绝所有无效的目录”。我进一步建议将其默认设置为TRUE。

不必在这里感到压力。除了代码中拼接路径错误之外,此函数可能收到畸形路径字符串的唯一真正方式是用户输入,因此应该进行验证或限制(如果您允许用户自由输入,则他们可以输入任何内容)。对于验证,您可以使用例如PathIsDirectory函数。 - Victoria
谢谢你提供PathIsDirectory的提示 - 我甚至不知道有这个例程。你说得对,验证用户输入确实很麻烦,因为用户可能使用各种排列组合,例如\?\开头和嵌入的%token%替换。 - Alex T
我仍然不完全相信用户是导致出现INVALID_FILE_ATTRIBUTES的唯一原因,从而导致DirectoryExists返回TRUE时实际上不应该返回的情况。这引发了一个问题,即在确定目录存在后,您是否总是需要调用GetFileAttributes来确保它实际上可用(这意味着您正在显式编码已经在DirectoryExists中的内容,仅用于检测进一步的情况)。对我来说,这仍然是错误的。 - Alex T
@Victoria - 函数DirectoryExists假装返回true,如果文件夹存在,则返回true,否则返回false。因此,该函数应进行所有可能的验证,以确保其结果准确无误。否则,它应该被称为MaybeDirectoryExists。 - undefined

0
我遇到了与映射路径到驱动器(Y:)相同的问题。
与完整路径\\IP\dir\dir相同的问题。
所以现在我使用的是不太好但是有效的函数:
function FolderExists(const BasePath: WideString): boolean;
const
  FIND_FIRST_EX_LARGE_FETCH          = $00000002;
  FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = $00000004;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  i, ii           : Integer;
  folderTime: TDateTime;
begin
  result := false;
  FindExInfoLevels := _FINDEX_INFO_LEVELS.FindExInfoBasic;
  FindExSearchOps  := _FINDEX_SEARCH_OPS.FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(PWideChar(IncludeTrailingBackslash(TDirectory.GetParent(ExcludeTrailingPathDelimiter(BasePath))) + '*.*' ), FindExInfoLevels, @Win32FindData, FindExSearchOps, nil ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName = ExtractFileName(ExcludeTrailingPathDelimiter(BasePath))) and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then begin
        result := true;
        break;
      end;
    until not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData);
  Winapi.Windows.FindClose(FindExHandle);
end;

功能在使用完整路径时正常工作,例如\\IP\dir\dir
是的,函数使用的算法不是最优的(乐观模式:开启),但它能正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接