我正在使用 IStream 获取文件的 SHCreateStreamOnFileEx ,但是当查找指针的新位置为 2 ** 32 字节或更远的文件时,其 Read() 方法似乎在超大文件上表现不佳。

ISequentialStream::Read 's documentation says :



这与我所知道的所有平台上的 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 call SetFilePointer(hFile, 1-1000, 0/*lpDistanceToMoveHigh*/, FILE_CURRENT)
    (1 这是缓冲区的内部位置,因为我们读取了 1 个字节减去缓冲区大小 1000)。如果不考虑溢出可以是 (100000FFF + (1 - 1000)) == 100000000 但是

    了解 SetFilePointer



    结果 SetFilePointer 失败(返回 INVALID_SET_FILE_POINTER )但 CFileStream 甚至不检查这个。然后它调用 SetFilePointerEx(hFile, 0, &newpos, FILE_CURRENT) 并返回给你仍然是 100000FFF 的 newpos

    关于c++ - SHCreateStreamOnFileEx 在大于 2**32 字节的文件上,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42399071/

    10-12 07:40