为什么在DLL库中调用GetLastError返回0?

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;

为什么像上面那样在 DLL 库中使用 GetLastError 函数时会返回 0?有没有办法获取此情况下的最后错误代码?


1
你会期望它是什么?GetLastError返回Windows API函数生成的最后一个错误,而且只有一个插槽,所以Delphi异常处理系统可能对Windows API执行的任何操作都可能重置或更改该值。通常,自定义异常(其LastError值相关)将该值包装在自定义属性中。 - 500 - Internal Server Error
2
你必须在 API 调用之后立即调用它。你没有这样做。尝试在 CreateFile 失败后立即执行它。总之,你的代码是无效的。 - David Heffernan
2个回答

10

GetLastError 返回0是因为在CreateFile返回后,还有其他API被调用,并且执行了异常代码。

GetLastError返回的错误码是一个线程本地变量,会被所有在该线程中运行的代码共享。所以为了获取错误码,你需要在失败的函数返回后立即调用GetLastError

文档这样解释:

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

如果正在使用TFileStream.Create,则框架不会在适当的时刻给您机会调用GetLastError。如果你真的想获得那个信息,你将不得不自己调用CreateFile,并使用THandleStream而不是TFileStream

这里的想法是,使用THandleStream,您负责合成文件句柄,然后将其传递给THandleStream的构造函数。这给了你在失败的情况下捕获错误码的机会。


2
这实际上是由RTL中的一个错误引起的,我在2004年向QC报告了这个问题([QC#8680](http://qc.embarcadero.com/wc/qcmain.aspx?d=8680)),并在2010年关闭了该问题。 - Remy Lebeau
3
@TLama - 不是的,David的回答才是正确的。TFileStream类是一个抽象类,它不能保证保留最后一个错误或任何其他内容。实际上,它使用最后一个错误值来引发带有适当消息的异常。此外,我想知道RTL如何重置错误,它只是调用FormatMessage并传入最后一个错误值。如果它确实有漏洞,我想知道为什么可执行文件和库的行为会有所不同? - Sertac Akyuz
5
我认为Remy报告的那个问题并不是真正的错误。我认为期望RTL保留上一个错误那么长时间是不公平的。我可能会确保将错误代码放入引发的异常的字段中。如果框架调用了RaiseLastOSError,那么就会这样做。遗憾的是它没有使用EOSError。 - David Heffernan
2
@TLama - 我同意David的观点。此外,Remy的报告并没有说明是什么重置了最后的错误。 - Sertac Akyuz
5
@TLama - 这并没有什么意义,不是RTL设置了最后的错误。你想知道的是我在评论中提到的一个让人怀疑RTL bug的原因。事实很简单,RTL从来没有显式地调用SetLastError。这只是操作系统中的一个实现细节,当在dll中调用FormatMessage时,某些代码路径会经过IsProcessorFeaturePresent,导致调用SetLastError。 - Sertac Akyuz
显示剩余11条评论

3
在更高层次上,这段代码的真正问题在于它混合了模型。你试图使用一个系统(VCL TStream系统)创建或打开文件,但你正在测试由另一个系统(Win32 API)产生的错误。
唯一可以依靠Win32 GetLastError结果的方法是如果你自己调用Win32函数。为什么?因为这是确保在Win32函数调用和调用GetLastError之间没有其他对Win32函数的调用的唯一方法。每个Win32 API调用都有可能(重新)设置GetLastError。
即使VCL位于Win32之上,也有很多机会让其他Win32 API调用发生在错误发生和异常到达处理程序之间。即使今天一切都运作良好,未来VCL实现中的某些变化也很容易破坏当前情况的愉快巧合。
避免数据在易受覆盖的“挂起时间”内的最佳方法是尽可能靠近故障点捕获GetLastError值并将其合并到VCL异常对象的属性中。这几乎消除了某些无辜闯入者在你的异常处理程序和故障点之间抹去GetLastError全局状态的风险。

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