Raymond Chen 2007年07月11日
SetFilePointer
函数以两种不同的方式报告错误,这取决于你是否传递了NULL
作为lpDistanceToMoveHigh
参数。MSDN中的文档是正确的,但我发现人们更喜欢我以不同的方式重述相同的事实,所以这里是文档的表格版本。
If lpDistanceToMoveHigh == NULL | If lpDistanceToMoveHigh != NULL | |
---|---|---|
If success | retVal != INVALID_SET_FILE_POINTER | retVal != INVALID_SET_FILE_POINTER || |
If failed | retVal == INVALID_SET_FILE_POINTER | retVal == INVALID_SET_FILE_POINTER && |
我想展示一些示例代码,但MSDN中的文档已经包含了lpDistancetoMoveHigh == NULL
情况和lpDistancetoMoveHigh != NULL
情况的示例代码。
一个常见的错误是在返回值不是INVALID_SET_FILE_POINTER
的情况下调用GetLastError
。换句话说,人们忽略了“函数成功还是失败?”测试中的retVal == INVALID_SET_FILE_POINTER
部分。仅仅因为GetLastError()
返回了一个错误代码,并不意味着SetFilePointer
函数失败了。返回值也必须是INVALID_SET_FILE_POINTER
。我必须承认MSDN中的文档在这一点上可以更清晰,但示例代码希望解决任何悬而未决的歧义。
但为什么当lpDistanceToMoveHigh
不是NULL
时,SetFilePointer
使用这种奇怪的错误报告方式呢?MSDN文档也解释了这个细节:如果文件大小大于4GB,那么INVALID_SET_FILE_POINTER
是文件位置低阶32位的有效值。例如,如果你将指针移动到位置0x00000001FFFFFFFF,那么
*lpDistanceToMoveHigh将被设置为结果的高阶32位(1),返回值是结果的低阶32位(0xFFFFFFFF,恰好是
INVALID_SET_FILE_POINTER的数值)。在那种情况下(只有在那种情况下),系统需要使用
SetLastError(ERROR_SUCCESS)来告诉你,“不,那个值是完全正常的。它只是巧合等于
INVALID_SET_FILE_POINTER`”。
为什么不在所有成功路径上调用SetLastError(ERROR_SUCCESS)
,而不仅仅是当结果的低阶32位恰好是0xFFFFFFFF的时候呢?这只是Win32的一个通用约定:如果函数成功,它不需要调用SetLastError(ERROR_SUCCESS)
。成功的返回值告诉你函数成功了。这个约定的例外是当返回值是模糊的,就像我们在这里有当结果的低阶32位恰好是0xFFFFFFFF。
你可能会认为这是一个愚蠢的约定。但木已成舟,在时间旅行技术落地之前,你只能接受现实。(请注意,UNIX使用相同的约定与errno
变量。只有在之前的函数调用失败时,errno
的值才被定义。)
回顾过去,SetFilePointer
的设计者有点过于聪明了。他们试图将32位和64位文件管理合并到一个单一的函数中。“它是通用的!”问题在于,你必须以两种不同的方式检查错误,这取决于你是使用32位变体还是64位变体。幸运的是,内核团队意识到他们的聪明反被聪明误,并想出了一个新函数SetFilePointerEx
。该函数直接产生一个64位值,返回值是一个简单的BOOL
,这使得检查成功或失败变得简单。
练习:GetFileSize
函数是怎么回事?