Win32 ReadFile()拒绝一次读取超过16Mb?

时间:2010-08-27 18:34:26

标签: c winapi readfile

我在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中找到相同的问题而没有任何成功:(

可能有人知道问题的原因是什么?

5 个答案:

答案 0 :(得分:3)

问题不在于ReadFile()本身。真正的问题是你的while()循环开始时是错误的。您对lenreaded变量管理不善。在循环的每次迭代中,您递减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,表示整个文件内容。这不是你需要的吗?存储在dwInFileSizeHighdwInFileSizeLow中的内存块的总大小:((__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