我正在使用
SHCreateStreamOnFileEx
获取一个文件的IStream,但是当搜索指针的新位置是2 ** 32字节或者进一步进入文件时,它的Read()方法似乎在非常大的文件上行为异常.
ISequentialStream::Read
‘s documentation says:
This method adjusts the seek pointer by the actual number of bytes read.
这与我所知道的所有平台上的read(2)和fread(3)行为相同.
但是对于这些流,这不是我在某些情况下看到的实际行为:
>寻求(2 ** 32 – 2,SEEK_SET,& pos),读取(buf,1,& bytesRead),寻找(0,MOVE_CUR,& pos)→bytesRead == 1和pos == 2 **正如预期的那样,32 – 1.
> Seek(2 ** 32 – 1,SEEK_SET,& pos),Read(buf,1,& bytesRead),Seek(0,MOVE_CUR,& pos)→bytesRead == 1,但pos ==(2 ** 32 – 1)4096,这是不正确的.这意味着任何后续读取(没有另一个Seek来修复光标位置)读取错误的数据,我的应用程序不起作用!
我“搞错了”?是否需要设置一些标志才能使此类行为正常?或者这是Shlwapi.dll中的错误?
下面的代码为我重现了这个问题. (设置OFFSET = WORKS以查看成功案例.)
#include "stdafx.h"
static const int64_t TWO_THIRTY_TWO = 4294967296LL;
static const int64_t WORKS = TWO_THIRTY_TWO - 2LL;
static const int64_t FAILS = TWO_THIRTY_TWO - 1LL;
static const int64_t OFFSET = FAILS;
static void checkPosition(CComPtr< IStream > fileStream, ULONGLONG expectedPosition)
{
LARGE_INTEGER move;
ULARGE_INTEGER newPosition;
move.QuadPart = 0;
HRESULT hr = fileStream->Seek(move, SEEK_CUR, &newPosition);
ASSERT(SUCCEEDED(hr));
ULONGLONG error = newPosition.QuadPart - expectedPosition;
ASSERT(error == 0);
}
int main()
{
const wchar_t *path = /* path to a file larger than 2**32 bytes */ L"C:\\users\\wjt\\Desktop\\eos-eos3.1-amd64-amd64.170216-122002.base.img";
CComPtr< IStream > fileStream;
HRESULT hr;
hr = SHCreateStreamOnFileEx(path, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, NULL, &fileStream);
ASSERT(SUCCEEDED(hr));
LARGE_INTEGER move;
ULARGE_INTEGER newPosition;
// Advance
move.QuadPart = OFFSET;
hr = fileStream->Seek(move, SEEK_SET, &newPosition);
ASSERT(SUCCEEDED(hr));
ASSERT(newPosition.QuadPart == OFFSET);
// Check position
checkPosition(fileStream, OFFSET);
// Read
char buf[1];
ULONG bytesRead = 0;
hr = fileStream->Read(buf, 1, &bytesRead);
ASSERT(SUCCEEDED(hr));
ASSERT(bytesRead == 1);
// Check position: this assertion fails if the Read() call moves the cursor
// across the 2**32 byte boundary
checkPosition(fileStream, OFFSET + 1);
return 0;
}
最佳答案 这真的是windows bug.在几个Windows版本上测试,包括最新的SHCore.DLL版本10.0.14393.0 x64.简单的重现方式:
void BugDemo(PCWSTR path)
{
// FILE_FLAG_DELETE_ON_CLOSE !
HANDLE hFile = CreateFile(path, FILE_GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
ULONG dwBytesRet;
// i not want really take disk space
if (DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwBytesRet, NULL))
{
static FILE_END_OF_FILE_INFO eof = { 0, 2 };// 8GB
if (SetFileInformationByHandle(hFile, FileEndOfFileInfo, &eof, sizeof(eof)))
{
IStream* pstm;
if (!SHCreateStreamOnFileEx(path, STGM_READ|STGM_SHARE_DENY_NONE, 0,FALSE, NULL, &pstm))
{
LARGE_INTEGER pos = { 0xffffffff };
ULARGE_INTEGER newpos;
if (!pstm->Seek(pos, STREAM_SEEK_SET, &newpos) && !pstm->Read(&newpos, 1, &dwBytesRet))
{
pos.QuadPart = 0;
if (!pstm->Seek(pos, STREAM_SEEK_CUR, &newpos))
{
DbgPrint("newpos={%I64x}\n", newpos.QuadPart);//newpos={100000fff}
}
}
pstm->Release();
}
}
}
// close and delete
CloseHandle(hFile);
}
}
void BugDemo()
{
WCHAR path[MAX_PATH];
if (ULONG len = GetTempPath(RTL_NUMBER_OF(path), path))
{
if (len + 16 < MAX_PATH)
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
swprintf(path + len, L"%08x%08x", ~ft.dwLowDateTime, ft.dwHighDateTime);
BugDemo(path);
}
}
}
我跟踪虚拟长CFileStream :: Seek(LARGE_INTEGER,ULONG,ULARGE_INTEGER *);在调试器下,可以确认此功能不适用于超过4GB大小的文件
如果更确切地说,为什么是100000FFF偏移量 – CFileStream使用内部缓冲区来读取1000字节大小.当你要求从FFFFFFFF偏移读取1个字节时 – 它实际上读取1000个字节到缓冲区,文件偏移量变为100000FFF.然后当你调用Seek(0,STREAM_SEEK_CUR,& newpos)时 – CFileStream调用SetFilePointer(hFile,1-1000,0 / * lpDistanceToMoveHigh * /,FILE_CURRENT)
(1这是缓冲区中的内部位置,因为我们读取1个字节减去缓冲区大小1000).如果不考虑溢出可以(100000FFF(1 – 1000))== 100000000但是
If lpDistanceToMoveHigh is NULL and the new file position does not fit
in a 32-bit value, the function fails and returns
INVALID_SET_FILE_POINTER.
结果SetFilePointer失败(返回INVALID_SET_FILE_POINTER),但CFileStream甚至没有检查这个.然后它调用SetFilePointerEx(hFile,0,& newpos,FILE_CURRENT)并返回你的newpos仍然是100000FFF