文件上的shcreateStreamonFileex大于2 ** 32字节

SHCreateStreamOnFileEx on files larger than 2**32 bytes

本文关键字:32字节 大于 shcreateStreamonFileex 文件      更新时间:2023-10-16

我使用SHCreateStreamOnFileEx获得了文件的IStream,但是当Seek Pointer的新位置为2 ** 32 BYT时,其Read()方法似乎在极大的文件上似乎不当进入文件。

ISequentialStream::Read的文档说:

此方法通过读取的实际字节数调整了Seew指针。

这与我知道的所有平台上的read(2)fread(3)相同。

但是有了这些流,这不是我在某些情况下看到的实际行为:

  • Seek(2 ** 32 - 2, SEEK_SET, &pos)Read(buf, 1, &bytesRead)Seek(0, MOVE_CUR, &pos)bytesRead == 1pos == 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错误。在几个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);
        }
    }
}

我在调试器下跟踪virtual long 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,但

阅读有关SetFilePointer

的信息

如果 lpdistancetomovehigh null ,而新的文件位置不适合 在32位值中,功能失败并返回 Invalid_set_file_pointer

结果SetFilePointer失败(返回INVALID_SET_FILE_POINTER),但CFileStream甚至不检查此问题。然后致电SetFilePointerEx(hFile, 0, &newpos, FILE_CURRENT),然后返回您的newpos,仍然100000FFF