【问题标题】:How to find out if a directory exists on Delphi XE2 *correctly*?如何确定Delphi XE2上是否存在目录*正确*?
【发布时间】:2013-08-07 17:13:57
【问题描述】:

我只需要检查一个目录是否存在!但是如果目录是“E:\Test”,其中 E: 是 CD/DVD 驱动器,并且没有插入磁盘,我会看到以下 Delphi 和 Windows 问题。

第一种方法:

function DirExists(Name: string): Boolean;
var
  Code: Integer;
begin
  Code := GetFileAttributesW(PChar(Name));
  Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;

它给出Range Check Error。我不能使用{$RANGECHECKS OFF}{$RANGECHECKS ON} 块,因为:

  1. 它打破了$RANGECHECKS 选项的当前状态。
  2. 我们将看到另一个系统错误Drive is not ready 而不是Range Check Error。但我只需要检查目录是否存在,而不会为用户提供任何错误对话框。

第二种方法:

if DirectoryExists(Name, True) then ...

对于空 CD/DVD 驱动器上不存在的 E:\Test 目录,此函数返回 True。所以不能使用它,因为它工作不正确。

那么,如何判断一个目录是否存在呢?

附注我认为任何 CD/DVD 驱动器都存在该错误。但是我在带有外部 CD/DVD 驱动器的 Mac OS X 10.8.4 下的 VMWare Fusion 5 上使用 Windows 7 x64。

【问题讨论】:

  • 太奇怪了——DirectoryExists() 确实在我的 PC 中为空的 DVD 驱动器返回 true。使用 Delphi XE 和 Windows 8。
  • 如何正确书写?
  • 这是目录存在的长期错误。 See here。我不知道手头的解决方案。
  • 在 XE3 中,他们添加了对 ERROR_NOT_READY 的检查以“修复”此问题。在我看来,一切都很虚假。
  • @Altaveron 它不应该在 XE3+ 中修复;它应该在 XE2 中修复。当我出售软件时,由我们端的某些东西引起的任何问题都会在软件的生命周期内免费修复。当您销售软件时,您会说“产品做 X”。如果它不执行 X(错误),那就是在撒谎。拒绝履行交易(修复软件以使其执行 X)是在欺骗客户,恕我直言。 EMBT 一直在要求更新以修复错误,这也是开源开发工具击败它们的众多原因之一。

标签: delphi filesystems delphi-xe2


【解决方案1】:

您可以修复您的函数,使其不会导致范围检查错误:

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;

范围检查错误是由于您混合了有符号和无符号类型。 Remy 还指出了设置编译器选项然后恢复到流行状态的非常有用的技巧。这是一个很好的学习技巧,但在这里你不需要它。

DirectoryExists 的 XE3 实现已修改以解决您遇到的问题。所以,如果使用 XE3+ 是一个选项,你应该接受它。


要抑制系统错误对话框,请在进程启动时调用:

procedure SetProcessErrorMode;
var
  Mode: DWORD;
begin
  Mode := SetErrorMode(SEM_FAILCRITICALERRORS);
  SetErrorMode(Mode or SEM_FAILCRITICALERRORS);
end;

MSDN 所述,这样做是最佳实践:

最佳实践是所有应用程序都调用该流程 - 参数为 SEM_ 的宽 SetErrorMode 函数 启动时的 FAILCRITICALERRORS。这是为了防止错误模式 挂起应用程序的对话框。

【讨论】:

  • 但是会在所有项目中抑制错误对话框吗?仅仅为了检查一个目录是否存在于 1 个单元上是不可能破坏整个项目的逻辑的。
  • @Altaveron 在这种情况下调用SetThreadErrorMode(如果在 Win7 上),然后恢复错误模式。但是,如果您的程序是可执行文件,我认为您确实想添加FAILCRITICALERRORS。默认错误模式是保持古老的向后兼容。您永远不想显示这些对话框。您希望函数调用失败并以自己的方式报告错误。
  • @Altaveron 无论如何,您都需要正视处理错误模式。如果您不添加SEM_FAILCRITICALERRORS,那么您将看到要禁止显示的对话框。 SEM_FAILCRITICALERRORS 是抑制该对话框的方法。
  • 大卫,你的方法真的很难理解。所以老板不会相信我需要很多时间来学习错误模式只是为了检查一个目录是否存在......
  • @Altaveron 我不确定我能不能帮你。要么你想抑制对话,要么你不想。无论如何,将您的老板指向documentation 还不够:系统不显示关键错误处理程序消息框。相反,系统将错误发送到调用进程。最佳实践是所有应用程序在启动时使用参数 SEM_FAILCRITICALERRORS 调用进程范围的 SetErrorMode 函数。这是为了防止错误模式对话框挂起应用程序。
【解决方案2】:

David 在避免范围检查错误方面有正确的答案。但是如果你不想这样做,你仍然可以手动关闭/打开{$RANGECHECKS},只需使用{$IFOPT}有条件地这样做,这样周围的代码就不会受到影响,例如:

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;

话虽如此,仅检查GetFileAttributes() 的结果是否为INVALID_FILE_ATTRIBUTES 是不够的。目录可能存在但无法访问。这就是为什么 RTL 的 DirectoryExists() 函数会检查 GetLastError() 的多个错误代码(ERROR_PATH_NOT_FOUNDERROR_BAD_NETPATHERROR_NOT_READY 等)以寻找可能的情况。 DirectoryExists() 可以做的另一件事是可选地检查指定路径是否实际上是目录的快捷方式,如果是,则检查目标目录是否存在。

更新:这是SysUtils.DirectoryExists()在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}

XE4 中的实现相同,只有一个区别 - Windows 版本还包括在调用 GetLastError() 时检查 LastError &lt;&gt; ERROR_BAD_NET_NAME

【讨论】:

  • 感谢您的方法!您安装了 XE3 或 XE4 吗?请问你可以在这里写DiretoryExists()代码吗?
  • @DavidHeffernan:来自GetFileAttributes() 的错误代码仅表示无法检索属性,但您必须查看错误代码以找出 WHY 属性无法检索。当目录确实不存在时,只会出现某些错误代码。其他错误代码表示该目录确实存在但无法访问。一个DirectoryExists()类型的函数的目的是判断存在,而不是可访问性,所以需要对错误码进行相应的分析。
  • @Altaveron:DirectoryExists() 的源代码已添加到我的答案中。
  • Embarcadero 应该做的是更新 FileExists()DirectoryExists() 以使用 PathFileExists()PathIsDirectory() 而不是 GetFileAttributes()。它们自 Win2K 以来就存在。
  • 我刚刚在 QC 中创建了一个工单:#117699
【解决方案3】:

将 Delphi XE2 升级到 Delphi XE3+ 或使用以下函数:

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;

【讨论】:

  • 这并没有解决问题的另一部分。
  • 这部分。 我们将看到另一个系统错误 Drive is not ready 而不是 Range Check Error。但我只需要检查目录是否存在而没有任何用户错误对话框。
  • 我没有看到 XE3 中的 DirectoryExists() 有任何错误 - 它可以按要求工作。
  • 在我的试验中,我也没有看到系统错误对话框,即使在 XE2 上也是如此。 DirectoryExists 的 XE2 和 XE3 实现之间的更改不会影响系统错误对话框。如果您再次执行任何引发这些对话框的操作,即使使用 XE3 代码,对话框仍会出现。你真的应该听从MS给出的建议并添加SEM_FAILCRITICALERRORS。当然,您宁愿处理代码中的任何错误,也不愿显示这些对话框?或者您是否真的想看看系统错误对话框?
  • 系统错误对话框似乎不是由DirectoryExists 引发的,而是由一些后续操作引发的,因为DirectoryExists 的输出不是您所期望的。但是,仅仅因为您在DirectoryExists 中修复了这一故障模式并不意味着它们都已修复。如果您不抑制系统错误,那么如果它们出现在其他场景中,请不要感到惊讶。
猜你喜欢
  • 1970-01-01
  • 2011-12-20
  • 2012-01-28
  • 1970-01-01
  • 2012-03-05
  • 1970-01-01
  • 2013-07-03
  • 2013-05-18
  • 2011-05-14
相关资源
最近更新 更多