2017-02-22 4 views
4

Ich bin ein IStream für eine Datei bekommen SHCreateStreamOnFileEx verwenden, aber seine Read() Methode scheint auf extrem große Dateien schlecht zu benehmen, wenn die neue Position des Suchzeigers 2 ** 32 Bytes oder weiter in die Datei.SHCreateStreamOnFileEx auf Dateien, die größer als 2 ** 32 Byte

ISequentialStream::Read's documentation says:

Diese Methode stellt der Zeiger durch die tatsächliche Anzahl der gelesenen Bytes suchen.

Dies ist das gleiche Verhalten wie read(2) und fread(3) auf allen Plattformen von Ich bin mir dessen bewusst.

Aber mit diesen Strömen, ist dies nicht das tatsächliche Verhalten ich in einigen Fällen sehen:

  • Seek(2 ** 32 - 2, SEEK_SET, &pos), Read(buf, 1, &bytesRead), Seek(0, MOVE_CUR, &pos)bytesRead == 1 und pos == 2 ** 32 - 1, wie erwartet.
  • Seek(2 ** 32 - 1, SEEK_SET, &pos), Read(buf, 1, &bytesRead), Seek(0, MOVE_CUR, &pos)bytesRead == 1, aber pos == (2 ** 32 - 1) + 4096, die nicht korrekt ist. Das bedeutet, dass alle nachfolgenden Lesevorgänge (ohne eine andere Seek, um die Cursorposition zu beheben) die falschen Daten lesen, und meine Anwendung funktioniert nicht!

Bin ich "falsch gehalten"? Gibt es ein Flag, das ich setzen muss, damit sich diese Klasse richtig verhält? Oder ist das ein Fehler in Shlwapi.dll?

Der folgende Code reproduziert dieses Problem für mich. (Set OFFSET = WORKS den erfolgreichen Fall zu sehen.)

#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

ja, kann bestätigen. Dies ist wirklich Windows Bug – RbMm

+0

Dies ist ein Bug von 'Shlwapi.dll' - aktuelle Implementierung nicht für die Arbeit mit Dateien mehr als' 0xffffffff' Größe. alles, was Sie tun können, wenn Sie IStream auf großen Dateien benötigen - implementieren Sie es selbst – RbMm

+0

@RbMm ist dies überall dokumentiert? Oder ist es nur Wissen, das du anhäufst? ☺ – wjt

Antwort

2

das ist wirklich Fenster Bug. getestet auf mehreren Windows-Version einschließlich der neuesten SHCore.DLL Version 10.0.14393.0 x64. einfache Art und Weise für reproduce:

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); 
     } 
    } 
} 

I virtual long CFileStream::Seek(LARGE_INTEGER, ULONG, ULARGE_INTEGER*); unter Debugger verfolgen und kann bestätigen, dass diese Funktion


wenn werden genauer nicht Design mehr als 4 GB Größe mit Dateien zu arbeiten, warum ist 100000FFF Offset - CFileStream Verwenden Sie den internen Puffer zum Lesen 1000 Byte-Größe. Wenn Sie fragen, lesen Sie 1 Byte von FFFFFFFF Offset - es tatsächlich lesen 1000 Bytes in den Puffer und Datei-Offset werden 100000FFF. Wenn Sie dann anrufen Seek(0, STREAM_SEEK_CUR, &newpos) - CFileStream Anruf SetFilePointer(hFile, 1-1000, 0/*lpDistanceToMoveHigh*/, FILE_CURRENT)

(1 das ist interne Position im Puffer, weil wir 1 Byte minus Puffergröße 1000 lesen).wenn nicht nehmen Überlauf zu berücksichtigen sein können (100000FFF + (1 - 1000)) == 100000000 aber

lesen über SetFilePointer

Wenn lpDistanceToMoveHigh ist NULL und die neue Dateiposition nicht in einem 32-Bit-Wert passt, die Funktion fehlschlägt und gibt INVALID_SET_FILE_POINTER zurück.

als Ergebnis SetFilePointer fail (Return INVALID_SET_FILE_POINTER) aber CFileStream noch nicht für diese zu überprüfen. und dann rufen Sie SetFilePointerEx(hFile, 0, &newpos, FILE_CURRENT) und zurück zu Ihnen newpos, die immer noch 100000FFF

+0

Vielen Dank für die Mühe des Durchsteigens im Debugger! – wjt

+0

@wjt - ich füge Erklärung hinzu warum das passiert, warum genau '100000FFF' Offset. wirklich unter Debugger zu treten ist sehr einfach) – RbMm

Verwandte Themen