5

我有一个非常大的文件,我需要分小块读取它,然后处理每一块。我正在使用 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++;
    }
4

3 回答 3

7

一种可能性是您没有使用分配粒度的倍数的偏移量。来自 MSDN:

高偏移量和低偏移量的组合必须在文件映射中指定偏移量。它们还必须与系统的内存分配粒度相匹配。也就是说,偏移量必须是分配粒度的倍数。要获取系统的内存分配粒度,请使用GetSystemInfo 函数,该函数填充SYSTEM_INFO 结构的成员。

如果您尝试以分配粒度的倍数以外的方式进行映射,则映射将失败并GetLastError返回ERROR_MAPPED_ALIGNMENT.

除此之外,代码示例中存在许多问题,使您很难看到您正在尝试做什么以及哪里出错了。至少,您需要解决内存泄漏问题。您似乎正在分配然后泄漏完全不必要的缓冲区。给它们起更好的名字可以清楚地说明它们的实际用途。

然后我建议在对 MapViewOfFile 的调用上放置一个断点,然后检查您传入的所有参数值以确保它们看起来正确。首先,在第二次调用时,您希望 offsetHigh 为 0,offsetLow 为 bufferSize。

一些可疑的事情:

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

每个演员都应该让你怀疑。有时它们是必要的,但请确保您了解原因。此时,您应该问自己为什么您使用的所有其他文件 API 都需要 aHANDLE并且此函数返回一个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 还是 unsigned int。在这种情况下,它将是一个无符号整数,但这对很多人来说并不明显。事实上,当我第一次写这个答案时,我错了。[本段于 2017 年 5 月 16 日更正] 对于按位运算,您几乎总是希望确保使用无符号值。

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

您正在转换offsetHighoffsetLowints,它们是有符号值。API 实际上DWORD需要 s,它们是无符号值。我不会在调用中进行强制转换,而是将offsetHighand声明offsetLowDWORDs 并在初始化中进行强制转换,如下所示:

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);
  }
}
于 2012-03-27T17:04:49.267 回答
1

您的代码中有内存泄漏:

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);
[ ... ]
}

你永远不是deletenew char[]在每次迭代中分配的东西。如果您的文件足够大/您对该循环进行了足够多的迭代,则内存分配最终将失败-然后您将看到throw()分配器完成了。

Win32 API 调用就像MapViewOfFile()不是 C++ 并且从不抛出,它们返回错误代码(后者NULL在失败时)。因此,如果您看到异常,则说明您的 C++ 代码有问题。大概就是上述情况。

于 2012-03-27T13:06:55.157 回答
1

我在内存映射文件方面也遇到了一些麻烦。基本上我只是想在同一台电脑上的 2 个应用程序之间共享内存(1Mo)。- 使用 Delphi 编写的两个应用程序 - 使用 Windows8 Pro

在第一个应用程序(第一个启动)可以读取和写入memoryMappedFile,但第二个只能读取它(error 5 : AccessDenied

最后经过大量测试,当两个应用程序都使用CreateFileMapping. 我什至尝试创建我的安全描述符,但没有任何帮助。

就在我的应用程序第一次调用之前OpenFileMapping,然后CreateFileMapping如果第一个调用失败

误导我的另一件事是 handle ,尽管MemoryMappedFile在两个应用程序中明显引用相同的地方。

最后一件事,经过这次更正后,我的应用程序似乎可以正常工作,但过了一会儿我出现了 error_NotEnough_Memory。调用 MapViewOfFile 时。这只是我的一个初学者的错误,我并不总是调用 UnmapViewOfFile。

于 2012-09-24T01:38:11.447 回答