我有一个非常大的文件,我需要把它读成小块,然后处理每一块。我正在使用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++;
}
答案 0 :(得分:6)
一种可能性是您没有使用偏移量是分配粒度的倍数。来自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 documentation,您会看到,“此功能的功能有限,不建议使用。对于新的应用程序开发,请使用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。在这种情况下,
它将是一个无符号的int,但对许多人来说这并不明显。事实上,
当我第一次写这个答案时,我弄错了。 [本段修正于2017年5月16日]通过按位运算,您几乎总是希望确保使用无符号值。
tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);
您正在将offsetHigh
和offsetLow
投射到int
,这是签名值。 API实际上需要DWORD
s,它们是无符号值。我会将offsetHigh
和offsetLow
声明为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);
}
}
答案 1 :(得分: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);
[ ... ]
}
在每次迭代期间,您永远不会delete
通过new char[]
分配的内容。如果你的文件足够大/你对这个循环进行了足够的迭代,那么内存分配最终会失败 - 然后你会看到分配器完成throw()
。
像MapViewOfFile()
这样的Win32 API调用不是C ++而且从不抛出,它们会返回错误代码(失败时后者为NULL
)。因此,如果您看到异常,那么您的C ++代码就会出错。可能是上面的。
答案 2 :(得分:1)
我也遇到了内存映射文件的麻烦。 基本上我只是想在同一台PC上的两个应用程序之间共享内存(1Mo)。 - 两个用Delphi编写的应用程序 - 使用Windows8 Pro
首先,一个应用程序(第一个启动)可以读取和写入memoryMappedFile
,但第二个应用程序只能读取它(error 5 : AccessDenied
)
经过大量测试后,当两个应用程序都使用CreateFileMapping
时,它突然起作用。我甚至尝试创建我的安全描述符,没有任何帮助。
在我首次调用OpenFileMapping
的应用程序之前,如果第一个调用失败则调整CreateFileMapping
另一个误导我的是手柄,虽然可以在两个应用程序中明显引用相同的MemoryMappedFile
。
最后一件事,经过这次修正后,我的应用程序似乎工作正常,但过了一段时间后我得到了error_NotEnough_Memory。调用MapViewOfFile时。 这只是我初学者的错误,我并不总是调用UnmapViewOfFile。