【问题标题】:[WIN API]Why sharing a same HANDLE of WriteFile(sync) and ReadFile(sync) cause ReadFile error?[WIN API]为什么WriteFile(sync)和ReadFile(sync)共享同一个HANDLE会导致ReadFile错误?
【发布时间】:2017-07-15 15:13:32
【问题描述】:

我搜索了 MSDN,但没有找到任何关于与 WriteFile 和 ReadFile 共享同一个 HANDLE 的信息。注意:我没有使用create_always 标志,所以文件不可能被空文件替换。 我尝试使用相同 HANDLE 的原因是出于性能考虑。我的代码基本上是下载一些数据(写入文件),立即读取然后删除它。 在我看来,文件句柄只是一个内存地址,也是做 I/O 工作的入口。 错误是这样发生的:

CreateFile(OK) --> WriteFile(OK) --> GetFileSize(OK) --> ReadFile(Failed) --> CloseHandle(OK)

如果WriteFile调用了synchronized,这个ReadFile动作应该没有问题,即使WriteFile后的GetFileSize返回正确的值!!(新修改的文​​件大小),但事实是ReadFile读取的是修改前的值( lpNumberOfBytesRead 始终是旧值)。我突然想到一个想法,缓存!

然后我尝试了解更多关于我不知道的Windows File Caching 的信息。我什至尝试过 Flag FILE_FLAG_NO_BUFFERINGFlushFileBuffers 功能,但没有运气。当然我知道我可以在 WriteFile 和 ReadFile 之间再次执行 CloseHandle 和 CreateFile,我只是想知道是否有一些可能的方法可以在不再次调用 CreateFile 的情况下实现这一点?

上面是我的问题的最低限度,下面是我为这个概念制作的演示代码:

int main()
{

    HANDLE hFile = CreateFile(L"C://temp//TEST.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL| FILE_FLAG_WRITE_THROUGH, NULL);

    //step one write 12345 to file
    std::string test = "12345";
    char * pszOutBuffer;
    pszOutBuffer = (char*)malloc(strlen(test.c_str()) + 1); //create buffer for 12345 plus a null ternimator
    ZeroMemory(pszOutBuffer, strlen(test.c_str()) + 1); //replace null ternimator with 0
    memcpy(pszOutBuffer, test.c_str(), strlen(test.c_str())); //copy 12345 to buffer



    DWORD wmWritten;
    WriteFile(hFile, pszOutBuffer, strlen(test.c_str()), &wmWritten, NULL); //write 12345 to file

    //according to msdn this refresh the buffer
    FlushFileBuffers(hFile);

    std::cout << "bytes writen to file(num):"<< wmWritten << std::endl; //got output 5 here as expected, 5 bytes has bebn wrtten to file.

    //step two getfilesize and read file

    //get file size of C://temp//TEST.txt
    DWORD dwFileSize = 0;
    dwFileSize = GetFileSize(hFile, NULL);
    if (dwFileSize == INVALID_FILE_SIZE)
    {
        return -1; //unable to get filesize
    }
    std::cout << "GetFileSize result is:" << dwFileSize << std::endl; //got output 5 here as expected

    char * bufFstream;

    bufFstream = (char*)malloc(sizeof(char)*(dwFileSize + 1)); //create buffer with filesize & a null terminator
    memset(bufFstream, 0, sizeof(char)*(dwFileSize + 1));
    std::cout << "created a buffer for ReadFile with size:" << dwFileSize + 1 << std::endl; //got output 6 as expected here
    if (bufFstream == NULL) {
        return -1;//ERROR_MEMORY;
    }
    DWORD nRead = 0;
    bool bBufResult = ReadFile(hFile, bufFstream, dwFileSize, &nRead, NULL); //dwFileSize is 5 here

    if (!bBufResult) {
        free(bufFstream);
        return -1; //copy file into buffer failed
    }


    std::cout << "nRead is:" << nRead << std::endl; //!!!got nRead 0 here!!!? why?


    CloseHandle(hFile);
    free(pszOutBuffer);
    free(bufFstream);
    return 0;
}

那么输出是:

bytes writen to file(num):5
GetFileSize result is:5
created a buffer for ReadFile with size:6
nRead is:0

nRead 应该是 5 而不是 0。

【问题讨论】:

  • 错误是什么?文件结束。为什么在这里问之前不检查错误代码。
  • @i486,我检查了,我不知道有文件结束问题。那个文件指针的概念以前在我的脑海中是不存在的。我为此付出了很大的努力,甚至写了一个测试代码并反复审查了近两天但仍然不明白,这让我非常沮丧。不管你说什么,我仍然接受所有批评,因为我是初学者。你不知道初学者遇到瓶颈时的感受,没有人愿意在这里发布愚蠢的问题来受到批评。作为一个初学者,我不认为这是愚蠢的,我尝试了很多,所以我问。
  • 好的,但在 MSDN 中您可以阅读有关 ReadFile 及其返回值的信息“如果函数失败,则返回值为零 (0)。要获取扩展错误信息,请调用 GetLastError”。然后检查GetLastError的值,看看有什么问题。

标签: c++ winapi readfile createfile writefile


【解决方案1】:

Win32 文件只有一个文件指针,用于读写;在WriteFile 之后它位于文件的末尾,因此如果您尝试从中读取它将失败。要阅读您刚刚编写的内容,您必须使用SetFilePointer 函数将文件指针重新定位在文件的开头。

此外,不需要FlushFileBuffer - 操作系统确保文件句柄上的读取和写入看到相同的状态,而不管缓冲区的状态如何。

【讨论】:

  • SetFilePointer 也不需要。只需对ReadFile 使用显式偏移量
  • 我通常避免让新手立即接触lpOverlapped 的东西;它的文档(和名字,真的)混合了同步和异步 IO,很容易混淆;作为起点,使用“经典”文件指针模型通常更容易,它几乎与任何其他 IO API(C stdio、C++ iostream、“经典”UNIX 文件)共享。此外,如果您如此频繁地寻找 SetFilePointer 调用(而不是实际的 IO)开始成为一个问题,您可能应该完全转储“常规”IO 并将文件映射到内存中(或进行更好的用户模式缓存)。
  • OVERLAPPED 包含ReadFile 的附加参数。它与异步 io 没有直接关系。它用于两者。仅对于同步 io,它是可选的,对于异步是强制性的。但是 - 什么更简单有效 - 使用单个 api 调用或 2 个具有相同效果的 api 调用?
  • 知道,但是去阅读文档 - OVERLAPPED 是关于异步 IO 的,你必须深入了解它是如何与同步 IO 一起使用的(以及何时允许您使用它)。如果我说的是使用大多数程序员已经想到的经典文件指针模型读取文件,那么解释一个调用寻找,另一个读取,就像fseek/fread (C) , istream::seek/istream::read (C++), lseek/read (UNIX)。
  • 再次OVERLAPPED 用于任何 io 。也适用于同步。它总是允许使用。看来你也不够了解这个
【解决方案2】:

第一次写入文件后,光标指向文件末尾。没有什么可读的。您可以使用SetFilePointer 将其倒回到开头:

::DWORD const result(::SetFilePointer(hFile, 0, nullptr, FILE_BEGIN));
if(INVALID_SET_FILE_POINTER == result)
{
    ::DWORD const last_error(::GetLastError());
    if(NO_ERROR != last_error)
    {
        // TODO do error handling...
    }
}

【讨论】:

  • 对于SetFilePointer 的绝对无用之处,当更好地直接设置偏移量时调用ReadFile。结果我们将有 1 个 api 调用而不是 2
  • 我的意思是更好地在调用ReadFile 中使用显式偏移并且只有一个api调用,而不是使用SetFilePointer
  • 你绝对错了。接受 - 查看最后一个参数 - pOverlapped - 这里和偏移量
【解决方案3】:

当您尝试读取文件时 - 您尝试从哪个位置读取它?

FILE_OBJECT 保持“当前”位置(CurrentByteOffset 成员),可用作默认位置(仅适用于同步文件 - 在没有 FILE_FLAG_OVERLAPPED 的情况下打开!!)当您读取或写入文件时。并且每次读取或写入 n 个字节后,此位置都会更新(向前移动 n 个字节)。

最好的解决方案总是在 ReadFile(或 WriteFile)中使用显式文件偏移。最后一个参数中的此偏移量OVERLAPPED lpOverlapped - 查找 Offset[High] 成员 - 读取操作从 OVERLAPPED 结构中指定的偏移量开始

使用这个更有效,并简单地比较使用特殊 api 调用 SetFilePointer 调整 FILE_OBJECT 中的 CurrentByteOffset 成员(这不适用于异步文件句柄(使用 FILE_FLAG_OVERLAPPED 标志创建)

尽管很常见的混淆 - OVERLAPPED 不仅仅用于异步 io - 这只是 ReadFile(或 WriteFile)的附加参数,并且可以始终使用 - 用于任何文件句柄

【讨论】:

  • 感谢重叠事物的明确声明。我确实认为重叠的东西都是异步的。看起来值得实施。 :0
  • @LynchChen - 这是非常常见的错误。即使是经验丰富的 Windows 程序员。阅读Considerations for working with synchronous file handles:if lpOverlapped is NULL, the read operation starts at the current file position.. If lpOverlapped is not NULL, the read operation starts at the offset that is specified in the OVERLAPPED structure
  • 需要了解SetFilePointer - 这是内核模式的额外 api cal,开销很大。当我们可以通过一个电话完成所有事情时怎么办?甚至在源代码级别我们也赢了 - 尝试编写两个代码(使用 ovelpapped 和使用 SetFilePointer)和错误检查。哪个代码会更小更简单?
  • 是的,如果我没有阅读您的帖子,我将永远不会注意到同步信息。大多数关于重叠的文档都是关于异步的,我认为这就是它成为一个常见错误的原因。我确实试图弄清楚之前发生了什么重叠,但从未注意到同步部分。我很感激这个伟大的教训和你的好意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-07
  • 1970-01-01
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 1970-01-01
相关资源
最近更新 更多