2017-02-22 55 views
4

Я получаю IStream для файла с помощью SHCreateStreamOnFileEx, но его метод Read() появляется безобразничать на очень больших файлов, когда новое положение указателя искать на 2 ** 32 байт или далее в файл.SHCreateStreamOnFileEx на файлы размером более 2 ** 32 байт

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 == 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; 
} 
+0

да, может подтвердить. это действительно ошибка Windows – RbMm

+0

это ошибка 'Shlwapi.dll' - текущая реализация не дизайн для работы с файлами больше, чем размер' 0xffffffff'. все, что вы можете сделать, при необходимости IStream на больших файлах - реализовать его самостоятельно – RbMm

+0

@RbMm это документировано где угодно? Или это просто знание, которое вы накапливаете? ☺ – wjt

ответ

2

это действительно ошибка окна. был протестирован на нескольких версиях Windows, включая последние версии SHCore.DLL10.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 байт размер. когда вы спрашиваете читать 1 байт с FFFFFFFF offset - он фактически считывает 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) и возвращает вам новые товары, которые все еще 100000FFF

+0

Спасибо за то, что вы перешли в отладчик! – wjt

+0

@wjt - я добавляю объяснение, почему это происходит, почему именно '100000FFF' смещение. действительно наступать на отладчик очень легко) – RbMm