【问题标题】:Why does GetLastError return 0 when it's called in a DLL library?为什么 GetLastError 在 DLL 库中调用时返回 0?
【发布时间】:2013-03-04 12:31:34
【问题描述】:

假设我有一个带有这个伪代码的 DLL 库:

var
  LastError: DWORD;

procedure DoSomethingWrong; stdcall;
var
  FileStream: TFileStream;
begin
  try
    FileStream := TFileStream.Create('?', fmCreate);
  except
    on EFCreateError do
      LastError := GetLastError; // <- why does GetLastError return 0 here ?
  end;
end;

为什么GetLastError 函数在如上所示的DLL 库中使用时返回0?有没有办法获得这种情况下的最后一个错误代码?

【问题讨论】:

  • 您希望它是什么? GetLastError 返回 Windows API 函数生成的最后一个错误,并且只有一个插槽,因此 Delphi 异常处理系统可能对 Windows API 执行的任何操作都可能重置或更改该值。通常,与 LastError 的值相关的自定义异常会将值包装在自定义属性中。
  • 您必须在 API 调用后立即调用它。你没有那样做。尝试在 CreateFile 失败后立即执行此操作。底线是您的代码无效。

标签: delphi delphi-2009


【解决方案1】:

您对GetLastError 的调用会返回0,因为在CreateFile 返回后还会调用其他API,并且您的异常代码会执行。

GetLastError 返回的错误代码是一个线程局部变量,并在线程中运行的所有代码之间共享。因此,为了捕获错误代码,您需要在失败的函数返回后立即调用GetLastError

documentation 是这样解释的:

调用线程执行的函数通过调用 SetLastError 函数。您应该调用 GetLastError 函数 当函数的返回值表明这样的调用时立即 将返回有用的数据。那是因为有些函数调用 SetLastError 成功时带零,清除错误代码 由最近失败的函数设置。

如果您使用的是TFileStream.Create,那么框架不会让您有机会在适当的时候调用GetLastError。如果您真的想获得该信息,您必须自己致电CreateFile 并使用THandleStream 而不是TFileStream

这里的想法是,使用THandleStream,您负责合成您传递给THandleStream 的构造函数的文件句柄。这使您有机会在失败时捕获错误代码。

【讨论】:

  • 这实际上是由我在 2004 年 (QC #8680) 向 QC 报告并在 2010 年修复的 RTL 中的错误引起的。
  • @TLama - 不,大卫的答案是正确的答案。 TFileStream 类是一个抽象,它不保证保留最后一个错误或任何东西。事实上,它使用最后一个错误来引发带有适当消息的异常。此外,我想知道 RTL 如何帮助重置错误,它所做的只是使用最后一个错误值调用 FormatMessage。如果确实有错误,我想知道为什么可执行文件和库的行为会有所不同?
  • 我不认为 Remy 报告的错误真的是错误。我认为期望 RTL​​ 将最后一个错误保留那么久是不公平的。我可能会确保将错误代码放入引发的异常的字段中。如果框架调用 RaiseLastOSError,就会发生这种情况。遗憾的是它没有使用 EOSError。
  • @TLama - 我同意大卫的观点,此外,雷米的报告没有说明什么重置了最后一个错误。
  • @TLama - 这并不意味着什么,设置最后一个错误的不是 RTL。您想知道的是我在评论中提到的怀疑 RTL 错误的理由。一个简单的事实是,RTL 从不显式调用 SetLastError。这纯粹是操作系统中的一个实现细节,当在 dll 中调用 FormatMessage 时,一些转到 IsProcessorFeaturePresent 的代码路径会导致调用 SetLastError。
【解决方案2】:

在更高级别上,此代码的真正问题在于它混合了模型。您正在尝试使用一个系统(VCL TStream 系统)创建或打开文件,但您正在测试由不同系统(Win32 API)产生的错误。

您可以依赖 Win32 GetLastError 结果的唯一方法是您自己调用 Win32 函数。为什么?因为这是确保在您的 Win32 函数调用和对 GetLastError 的调用之间没有对 Win32 函数的其他调用的唯一方法。每个 Win32 API 调用都有可能(重新)设置 GetLastError。

尽管 VCL 位于 Win32 之上,但在发生错误和异常到达您的处理程序之间,仍有很多机会发生其他一些 Win32 API 调用。即使今天一切正常,未来 VCL 实施中的一些变化也很容易破坏当前情况的巧合。

在您需要的数据容易被覆盖的情况下,避免这种“挂起时间”的最佳方法是在尽可能接近故障点的位置捕获 GetLastError 值并将其合并到 VCL 异常对象的属性中。这几乎可以消除您的异常处理程序和消除 GetLastError 全局状态的故障点之间的一些无辜闯入者的风险。

【讨论】:

    猜你喜欢
    • 2017-12-21
    • 2012-01-30
    • 1970-01-01
    • 2020-10-19
    • 1970-01-01
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多