6

假设我有一个带有这个伪代码的 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?有没有办法获得这种情况下的最后一个错误代码?

4

2 回答 2

10

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

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

文档是这样解释的:

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

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

那里的想法是,与THandleStream您一起负责合成您传递给的构造函数的文件句柄THandleStream。这使您有机会在发生故障时捕获错误代码。

于 2013-03-15T17:35:29.667 回答
3

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

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

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

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

于 2013-03-16T07:16:58.623 回答