Отображение больших файлов с помощью MapViewOfFile - PullRequest
3 голосов
/ 27 марта 2012

У меня очень большой файл, и мне нужно прочитать его небольшими частями, а затем обработать каждый кусок.Я использую функцию MapViewOfFile для отображения фрагмента в памяти, но после прочтения первой части я не могу прочитать вторую.Он выдает, когда я пытаюсь отобразить его.

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

Ответы [ 3 ]

6 голосов
/ 27 марта 2012

Одна из возможностей заключается в том, что вы не используете смещение, кратное гранулярности распределения. Из MSDN:

Комбинация верхнего и нижнего смещений должна указывать смещение в отображении файла. Они также должны соответствовать гранулярности распределения памяти системы. То есть смещение должно быть кратным гранулярности распределения. Чтобы получить детализацию выделения памяти системы, используйте функцию GetSystemInfo, которая заполняет члены структуры SYSTEM_INFO.

Если вы попытаетесь отобразить что-то, отличное от кратности гранулярности распределения, сопоставление завершится неудачно и GetLastError вернет ERROR_MAPPED_ALIGNMENT.

Кроме этого, в примере кода есть много проблем, из-за которых очень трудно увидеть, что вы пытаетесь сделать, и в чем дело. Как минимум, вам нужно решить утечки памяти. Вы, кажется, распределяете, а затем пропускаете совершенно ненужные буферы. Если дать им более подходящие имена, можно понять, для чего они на самом деле используются.

Затем я предлагаю поставить точку останова на вызовах MapViewOfFile, а затем проверить все значения параметров, которые вы передаете, чтобы убедиться, что они выглядят правильно. Для начала при втором вызове можно ожидать, что offsetHigh будет 0, а offsetLow будет bufferSize.

Несколько подозрительных вещей вне летучей мыши:

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

Каждый актерский состав должен вызывать у вас подозрения. Иногда они необходимы, но убедитесь, что вы понимаете, почему. В этот момент вы должны спросить себя, почему для каждого используемого вами файлового API требуется HANDLE, а эта функция возвращает HFILE. Если вы посмотрите документацию OpenFile , вы увидите: «Эта функция имеет ограниченные возможности и не рекомендуется. Для разработки новых приложений используйте функцию CreateFile». Я знаю, это звучит странно, потому что вы хотите открыть существующий файл, но CreateFile может сделать именно это, и он возвращает правильный тип.

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

Какой тип offset? Вы, вероятно, хотите убедиться, что это unsigned long long или эквивалент. При сдвиге битов, особенно вправо, вы почти всегда хотите, чтобы беззнаковый тип избегал расширения знака. Вы также должны убедиться, что этот тип имеет больше битов, чем количество, на которое вы сдвигаете - сдвиг 32-битного значения на 32 (или более) битов фактически не определен в C и C ++, что позволяет компиляторам сделать определенные типы оптимизаций.

long long offsetLow = (offset & 0xFFFFFFFF);

В обоих этих утверждениях вы должны быть осторожны со значением 0xFFFFFFFF. Поскольку вы не приводили его или не присваивали ему суффикс, может быть трудно предсказать, будет ли компилятор рассматривать его как int или беззнаковое int. В этом случае, это будет неподписанный int, но это не будет очевидно для многих людей. По факту, Я неправильно понял, когда впервые написал этот ответ. [ Этот параграф исправлен 16-May-2017 ] С побитовыми операциями вы почти всегда хотите убедиться, что используете значения без знака.

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

Вы приводите offsetHigh и offsetLow к int с, которые являются знаковыми значениями. API фактически хочет DWORD s, которые являются беззнаковыми значениями. Вместо приведения в вызове я объявил бы offsetHigh и offsetLow как DWORD s и произвел бы приведение в инициализации, например так:

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);
  }
}
1 голос
/ 24 сентября 2012

У меня также были некоторые проблемы с отображенными в память файлами.По сути, я просто хотел разделить память (1Mo) между двумя приложениями на одном ПК.- Оба приложения были написаны на Delphi - Использование Windows8 Pro

Сначала одно приложение (первое запущенное) могло читать и записывать memoryMappedFile, а второе - только (error 5 : AccessDenied)

Наконец, после большого тестирования Это неожиданно сработало, когда оба приложения использовали CreateFileMapping.Я даже пытался создать свой дескриптор безопасности, но ничего не помогло.

Непосредственно перед тем, как мои приложения сначала вызывали OpenFileMapping, а затем CreateFileMapping, если первое не удалось

Еще одна вещь, которая вводила меня в заблуждениезаключается в том, что дескрипторы, хотя и визуально ссылаются на один и тот же MemoryMappedFile, где различны в обоих приложениях.

И последнее, после этого исправления мое приложение работало нормально, но через некоторое время у меня возникла error_NotEnough_Memory.при вызове MapViewOfFile.С моей стороны это была просто ошибка новичка, я не всегда вызывал UnmapViewOfFile.

1 голос
/ 27 марта 2012

В вашем коде утечка памяти:

char *tmp_buffer = new char[bufferSize];
[ ... ]
while (offset < fileSize)
{
[ ... ]
    char *tmp_buffer = new char[bufferSize];
[ ... ]
    tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);
[ ... ]
}

Вы никогда не delete, что вы выделяете через new char[] во время каждой итерации там. Если ваш файл достаточно велик / вы выполняете достаточно итераций этого цикла, выделение памяти в конечном итоге завершится неудачей - тогда вы увидите, что throw() выполнено распределителем.

Вызовы Win32 API, такие как MapViewOfFile(), не являются C ++ и никогда не генерируются, они возвращают коды ошибок (последний NULL при сбое). Поэтому, если вы видите исключения, что-то не так в вашем коде C ++. Скорее всего выше.

...