1. ホーム
  2. c++

[解決済み] MapViewOfFileを使用した大容量ファイルのマッピング

2022-02-19 02:14:58

質問

非常に大きなファイルがあり、それを細かく分割して読み込んで、それぞれの部分を処理する必要があります。MapViewOfFile関数を使用してメモリ内のピースをマッピングしていますが、最初の部分を読み取った後、2番目の部分を読み取ることができません。それは私がそれをマップしようとしているときにスローされます。

    char *tmp_buffer = new char[bufferSize];
    LPCWSTR input = L"input";   
    OFSTRUCT tOfStr;
    tOfStr.cBytes = sizeof tOfStr;

    HANDLE inputFile = (HANDLE)OpenFile(inputFileName, &tOfStr, OF_READ); 
    HANDLE fileMap = CreateFileMapping(inputFile, NULL, PAGE_READONLY, 0, 0, input);

    while (offset < fileSize)
    {
        long k = 0;
        bool cutted = false;
        offset -= tempBufferSize;

        if (fileSize - offset <= bufferSize)
        {
            bufferSize = fileSize - offset;
        }

        char *buffer = new char[bufferSize + tempBufferSize];

        for(int i = 0; i < tempBufferSize; i++)
        {
            buffer[i] = tempBuffer[i];
        }

        char *tmp_buffer = new char[bufferSize];
        LPCWSTR input = L"input";
        HANDLE inputFile;
        OFSTRUCT tOfStr;
        tOfStr.cBytes = sizeof tOfStr;

        long long offsetHigh = ((offset >> 32) & 0xFFFFFFFF);
        long long offsetLow = (offset & 0xFFFFFFFF);

        tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);

        memcpy(&buffer[tempBufferSize], &tmp_buffer[0], bufferSize);

        UnmapViewOfFile(tmp_buffer);

        offset += bufferSize;
        offsetHigh = ((offset >> 32) & 0xFFFFFFFF);
        offsetLow = (offset & 0xFFFFFFFF);

        if (offset < fileSize)
        {
            char *next;
            next = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, 1);

            if (next[0] >= '0' && next[0] <= '9')
            {
                cutted = true;
            }

            UnmapViewOfFile(next);
        }

        ostringstream path_stream;
        path_stream << tempPath << splitNum;

        ProcessChunk(buffer, path_stream.str(), cutted, bufferSize);

        delete buffer;

        cout << (splitNum + 1) << " file(s) sorted" << endl;
        splitNum++;
    }

解決方法は?

1つの可能性は、アロケーション粒度の倍数のオフセットを使用していないことです。 MSDNより。

ハイオフセットとローオフセットの組み合わせは、ファイルマッピング内のオフセットを指定する必要があります。また、システムのメモリ割り当て粒度と一致する必要があります。つまり、オフセットは割り当て粒度の倍数でなければなりません。システムのメモリ割り当て粒度を得るには、SYSTEM_INFO構造体のメンバを埋めるGetSystemInfo関数を使用します。

アロケーション粒度の倍数以外でマッピングしようとすると、マッピングに失敗し GetLastError が返されます。 ERROR_MAPPED_ALIGNMENT .

それ以外にも、コードサンプルには多くの問題があり、何をしようとしているのか、どこで間違っているのかが非常にわかりにくくなっています。 最低限、メモリーリークを解決する必要があります。 完全に不要なバッファを確保し、リークさせているようです。 より良い名前を付けることで、実際に何に使われるかが明確になります。

それから、MapViewOfFileの呼び出しにブレークポイントを設定し、渡しているすべてのパラメータ値が正しいかどうかを確認することをお勧めします。 まず、2回目の呼び出しでは、offsetHighは0、offsetLowはbufferSizeであることが期待されます。

さっそく怪しい点がいくつか。

HANDLE inputFile = (HANDLE)OpenFile(inputFileName, &tOfStr, OF_READ); 

どのキャストも疑心暗鬼になるはずです。 時には必要な場合もありますが、その理由をきちんと理解してください。 この時点で、あなたが使っている他のすべてのファイルAPIがなぜ HANDLE を返し、この関数は HFILE . もし OpenFileのドキュメント この関数は機能が限られており、推奨できません。新しいアプリケーションを開発する場合は、CreateFile関数を使用してください」とあります。

long long offsetHigh = ((offset >> 32) & 0xFFFFFFFF);

どのようなタイプか offset ? おそらく、それが unsigned long long またはそれと同等です。 ビットシフトするとき、特に右にシフトするときは、符号拡張を避けるためにほとんどの場合符号なし型が必要です。 32ビットの値を32ビット(またはそれ以上)シフトすることは、CやC++では実際には未定義であり、コンパイラがある種の最適化を行うことができるのです。

long long offsetLow = (offset & 0xFFFFFFFF);

この2つの文で、注意しなければならないのは 0xFFFFFFFF の値です。 キャストもサフィックスもつけていないので、コンパイラがこれをintとして扱うかunsigned intとして扱うか予測するのは難しいかもしれません。 この場合 しかし、それは多くの人にとって明らかではないでしょう。 実際、そうなのだ。 この答えを最初に書いたとき、私はこれを間違えていました。 [ この段落を修正 16-MAY-2017 ] ビット演算では、ほとんどの場合、符号なし値を使用することを確認したいものです。

tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);

キャスティングしている offsetHighoffsetLow から int は、符号付きの値です。 APIは実際には DWORD は、符号なし値です。 呼び出しの中でキャストするのではなく、私なら offsetHighoffsetLow として DWORD のように、初期化でキャスティングを行います。

DWORD offsetHigh = static_cast<DWORD>((offset >> 32) & 0xFFFFFFFFul);
DWORD offsetLow  = static_cast<DWORD>( offset        & 0xFFFFFFFFul);
tmp_buffer = reinterpret_cast<const char *>(MapViewOfFile(fileMap, FILE_MAP_READ, offsetHigh, offsetLow, bufferSize));

これらの修正は、あなたの問題を解決するかもしれないし、しないかもしれません。 不完全なコードサンプルからは、何が起こっているのかを判断することは困難です。

比較できる動作サンプルはこちらです。

// Calls ProcessChunk with each chunk of the file.
void ReadInChunks(const WCHAR *pszFileName) {
  // Offsets must be a multiple of the system's allocation granularity.  We
  // guarantee this by making our view size equal to the allocation granularity.
  SYSTEM_INFO sysinfo = {0};
  ::GetSystemInfo(&sysinfo);
  DWORD cbView = sysinfo.dwAllocationGranularity;

  HANDLE hfile = ::CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ,
                               NULL, OPEN_EXISTING, 0, NULL);
  if (hfile != INVALID_HANDLE_VALUE) {
    LARGE_INTEGER file_size = {0};
    ::GetFileSizeEx(hfile, &file_size);
    const unsigned long long cbFile =
        static_cast<unsigned long long>(file_size.QuadPart);

    HANDLE hmap = ::CreateFileMappingW(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hmap != NULL) {
      for (unsigned long long offset = 0; offset < cbFile; offset += cbView) {
        DWORD high = static_cast<DWORD>((offset >> 32) & 0xFFFFFFFFul);
        DWORD low  = static_cast<DWORD>( offset        & 0xFFFFFFFFul);
        // The last view may be shorter.
        if (offset + cbView > cbFile) {
          cbView = static_cast<int>(cbFile - offset);
        }
        const char *pView = static_cast<const char *>(
            ::MapViewOfFile(hmap, FILE_MAP_READ, high, low, cbView));
        if (pView != NULL) {
          ProcessChunk(pView, cbView);
        }
      }
      ::CloseHandle(hmap);
    }
    ::CloseHandle(hfile);
  }
}