我在VirtualBox的Windows XP上遇到了一个非常奇怪的问题。
ReadFile()
函数拒绝在单个调用中读取超过16Mb的数据。
它返回错误代码87(ERROR_INVALID_ARGUMENT
)。
看起来数据长度限制为24位。
以下示例代码允许我找出确切的限制。
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
int _tmain(int argc, _TCHAR* argv[])
{
int fd,len,readed;
char *buffer;
char *fname="Z:\\test.dat";
fd=_open(fname,_O_RDWR|_O_BINARY,_S_IREAD|_S_IWRITE);
if (fd==-1) {
printf("Error opening file : %s\n",strerror(errno));
getch();
return -1;
}
len=_lseek(fd,0,SEEK_END);
_lseek(fd,0,SEEK_SET);
if (!len) {
printf("File length is 0.\n");
getch();
return -2;
}
buffer=(char *)malloc(len);
if (!buffer) {
printf("Failed to allocate memory.\n");
getch();
return -3;
}
readed=0;
while (readed<len) {
len-=100;
readed=_read(fd,buffer,len);
if (len<=100) break;
}
if (readed!=len) {
printf("Failed to read file: result %d error %s\n",readed,strerror(errno));
getch();
return -4;
}
_close(fd);
printf("Success (%u).",len);
getch();
return 0;
}
文件Z:\test.dat
长度为21Mb。
结果是“Success (16777200).
”
我试图在Google中找到相同的问题而没有任何成功:(
可能有人知道问题的原因是什么?
答案 0 :(得分:3)
问题不在于ReadFile()
本身。真正的问题是你的while()
循环开始时是错误的。您对len
和readed
变量管理不善。在循环的每次迭代中,您递减len
并重置readed
。最终,len
递减到与readed
匹配的值,循环停止运行。您的“成功”消息报告16MB这一事实很巧合,因为您在读取文件时正在修改这两个变量。 len
最初设置为21MB并倒计时直到_read()
在要求16MB时才返回16MB缓冲区。这并不意味着ReadFile()
在16MB读取时失败(如果是这种情况,第一次循环迭代将失败,因为它要求读取21MB)。
您需要修复while()
循环,而不是责备ReadFile()
。正确的循环逻辑看起来应该更像这样:
int total = 0;
while (total < len)
{
readed = _read(fd, &buffer[total], len-total);
if (readed < 1) break;
total += readed;
}
_close(fd);
if (total != len)
{
printf("Failed to read file: %d out of %d, error %s\n", total, len, strerror(errno));
...
return -4;
}
printf("Success (%u).",total);
...
答案 1 :(得分:2)
设备驱动程序返回的字节数少于请求数,这完全合法。这就是ReadFile()具有lpNumberOfBytesRead参数的原因。您应该避免使用低级CRT实现细节,例如_read()。改为使用fread()。
更新:这不是正确的答案。看起来您的虚拟机只是拒绝考虑要求超过16MB的ReadFile()调用。可能与用于与主机操作系统通信的内部缓冲区有关。除了在循环中调用fread()以便你可以保持低于这个上限时,你无能为力。
答案 2 :(得分:2)
我建议您使用Memory-Mapped Files。 (另见http://msdn.microsoft.com/en-us/library/aa366556.aspx)。以下简单代码显示了一种方法:
LPCTSTR pszSrcFilename = TEXT("Z:\\test.dat");
HANDLE hSrcFile = CreateFile (pszSrcFilename, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
HANDLE hMapSrcFile = CreateFileMapping (hSrcFile, NULL, PAGE_READONLY, 0, 0, NULL);
PBYTE pSrcFile = (PBYTE) MapViewOfFile (hMapSrcFile, FILE_MAP_READ, 0, 0, 0);
DWORD dwInFileSizeHigh, dwInFileSizeLow;
dwInFileSizeLow = GetFileSize (hInFile, &dwInFileSizeHigh);
经过一些简单的步骤后,您有一个指针pSrcFile
,表示整个文件内容。这不是你需要的吗?存储在dwInFileSizeHigh
和dwInFileSizeLow
中的内存块的总大小:((__int64)dwInFileSizeHigh << 32)+dwInFileSizeLow
。
这使用了用于实现交换文件(页面文件)的Windows内核的相同功能。它由磁盘缓存缓冲并且非常高效。如果计划主要按顺序访问文件,包括调用CreateFile()
中的标志FILE_FLAG_SEQUENTIAL_SCAN,则会向系统提示此事实,导致它尝试提前读取以获得更好的性能。
我看到你在测试例中读到的文件名为“Z:\ test.dat”。如果它是来自网络驱动器的文件,您将看到明显的性能优势。 Morover对应http://msdn.microsoft.com/en-us/library/aa366542.aspx,你的限制大约是2 GB而不是16Mb。我建议您将文件映射到1 GB,然后创建一个关于MapViewOfFile
的网络视图(我不确定您的代码是否需要使用如此大的文件)。然后,在同一个MSDN页面上,您可以阅读
文件映射对象的大小 你选择控制多远 你可以用内存“看到”的文件 映射。如果您创建文件映射 对象,大小为500 Kb,你 只能访问前500 Kb 该文件的大小,无论大小 文件。因为它不会花费你 任何系统资源来创建 更大的文件映射对象,创建一个 文件映射对象的大小 的文件(设置
dwMaximumSizeHigh
和dwMaximumSizeLow
参数CreateFileMapping
均为零 如果你不期望查看 整个文件。系统成本 资源来自于创建视图 并访问它们。
因此内存映射文件的使用非常便宜。如果你的程序只读取部分文件内容跳过文件的大部分内容,那么你也会有很大的性能优势,因为它只读取你真正访问过的文件部分(舍入到16K页面)。
更为干净的文件映射代码如下
DWORD MapFileInMemory (LPCTSTR pszFileName,
PBYTE *ppbyFile,
PDWORD pdwFileSizeLow, OUT PDWORD pdwFileSizeHigh)
{
HANDLE hFile = INVALID_HANDLE_VALUE, hFileMapping = NULL;
DWORD dwStatus = NO_ERROR;
const DWORD dwSourceId = MSG_SOURCE_MAP_FILE_IN_MEMORY;
__try {
hFile = CreateFile (pszFileName, FILE_READ_DATA, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
dwStatus = GetLastError();
__leave;
}
*pdwFileSizeLow = GetFileSize (hFile, pdwFileSizeHigh);
if (*pdwFileSizeLow == INVALID_FILE_SIZE){
dwStatus = GetLastError();
__leave;
}
hFileMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMapping){
dwStatus = GetLastError();
__leave;
}
*ppbyFile = (PBYTE) MapViewOfFile (hFileMapping, FILE_MAP_READ, 0, 0, 0);
if (*ppbyFile == NULL) {
dwStatus = GetLastError();
__leave;
}
}
__finally {
if (hFileMapping) CloseHandle (hFileMapping);
if (hFile != INVALID_HANDLE_VALUE) CloseHandle (hFile);
}
return dwStatus;
}
BOOL UnmapFileFromMemory (LPCVOID lpBaseAddress)
{
return UnmapViewOfFile (lpBaseAddress);
}
答案 3 :(得分:2)
我假设,Z:在您的示例中是共享文件夹。我偶然发现了同样的错误,花了一些时间试图把它钉死。
看来,这个问题已经存在了一段时间:https://www.virtualbox.org/ticket/5830。
答案 4 :(得分:1)
我认为这是Windows的限制。根据我的经验,在Windows XP和2003 x86和x64上,ReadFile()
无法读取超过16 MB的内容。在Windows 2008 r2和Windows 8 x64上,阈值要高得多> 1 GB。我正在使用无缓冲的IO作为备份实用程序。
我从未使用MMF
,但ReadFile和WriteFile使用FILE_FLAG_NO_BUFFERING
非常快。并且CPU使用率*几乎为0,而读取速度为147 MB / s。
Intel i7