c++ - 这是 Windows 文件 API 的正确用法吗? (多个重叠请求)

标签 c++ windows winapi file-handling

我在使用 Windows 文件 API 时遇到了一些奇怪的行为,特别是具有重叠 IO 的 ReadFile。

在某些情况下,GetOverlappedResult 将成功地将数据读入提供的缓冲区,但将 lpNumberOfBytesTransferred 设置为零,而不是读取的正确数量。

这似乎只发生在同一句柄上发出多个重叠读取请求时,当文件先前使用 FILE_FLAG_NO_BUFFERING 打开时。

这是说明问题的完整代码示例...

#include <Windows.h>
#include <string>
#include <iostream>

const int PageSize = 4096;
const int BufferSize = PageSize * 4;

struct OperationSlot
{
    OVERLAPPED state;
    unsigned char* buffer;
};

bool enableFail;

bool ReadTest(std::string filename, DWORD flags, int queueSize)
{
    bool result = true;

    if (enableFail)
    {
        HANDLE temp = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
        CloseHandle(temp);
    }

    OperationSlot* slots = new OperationSlot[queueSize];
    for (int i = 0; i < queueSize; ++i)
    {
        slots[i].buffer = (unsigned char*)_aligned_malloc(BufferSize, PageSize);
        ZeroMemory(slots[i].buffer, BufferSize);
    }

    HANDLE file = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, flags, NULL);
    HANDLE controlFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
    unsigned char* controlBuffer = new unsigned char[BufferSize];

    // Start async read operations...
    for (int i = 0; i < queueSize; ++i)
    {
        ZeroMemory(&slots[i].state, sizeof(OVERLAPPED));
        slots[i].state.Offset = i * BufferSize;
        bool ok = ReadFile(file, slots[i].buffer, BufferSize, NULL, &slots[i].state);
        if (!ok)
        {
            DWORD err = GetLastError();
            if (err != ERROR_IO_PENDING)
            {
                std::cout << "ReadFile set error code " << err << std::endl;
            }
        }
    }

    int readId = 0;
    while (true)
    {
        OperationSlot& active_slot = slots[readId % queueSize];
        DWORD bytes = 0;
        bool ok = GetOverlappedResult(file, &active_slot.state, &bytes, true);
        DWORD err = GetLastError();

        DWORD controlBytes = 0;
        ReadFile(controlFile, controlBuffer, BufferSize, &controlBytes, NULL);

        bool dataok = memcmp(active_slot.buffer, controlBuffer, controlBytes) == 0;
        if (!dataok)
            std::cout << "Data mismatch." << std::endl;

        if (bytes != controlBytes)
        {
            std::cout << "Error with QueueSize (" << queueSize << ") and flags: "; 
            if (flags & FILE_FLAG_OVERLAPPED)
            {
                std::cout << "FILE_FLAG_OVERLAPPED";
            }
            if (flags & FILE_FLAG_NO_BUFFERING)
            {
                std::cout << " | FILE_FLAG_NO_BUFFERING";
            }
            if (flags & FILE_FLAG_SEQUENTIAL_SCAN)
            {
                std::cout << " | FILE_FLAG_SEQUENTIAL_SCAN";
            }
            std::cout << std::endl;

            std::cout << "Read size error, expected " << controlBytes << ", got " << bytes << std::endl;
            std::cout << "GetOverlappedResult returned " << ok << " with error code " << err << std::endl;

            result = false;
        }

        if (controlBytes < BufferSize)
            break;

        ZeroMemory(&active_slot.state, sizeof(OVERLAPPED));
        active_slot.state.Offset = (readId + queueSize) * BufferSize;
        ReadFile(file, active_slot.buffer, BufferSize, NULL, &active_slot.state);

        ++readId;
    }

    CloseHandle(file);
    CloseHandle(controlFile);
    delete[] controlBuffer;
    for (int i = 0; i < queueSize; ++i)
    {
        _aligned_free(slots[i].buffer);
    }
    delete[] slots;

    return !result;
}
int main()
{
    enableFail = false;
    int totalfail = 0;

    std::cout << "Testing without fail." << std::endl;

    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);

    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);

    std::cout << totalfail << " calls failed." << std::endl;

    enableFail = true;
    totalfail = 0;
    std::cout << "Testing with fail enabled." << std::endl;

    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
    totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);

    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
    totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);

    std::cout << totalfail << " calls failed." << std::endl;

    system("pause");
    return 0;
}

在我的系统上,这导致 4 个调用“失败”。 (“队列大小”大于一的那些。)

发生的情况是重叠版本报告它只读取 0 个字节,而“正常”文件句柄读取 20 个字节。(一个小文本文件,上面写着“这是一个测试”。) 另一件奇怪的事情是它实际上正在正确读取数据。正确的缓冲区填充了正确的数据,只有报告的传输数据量是错误的...

只有在打开文件之前使用 FILE_FLAG_NO_BUFFERING 打开和关闭文件时才会发生这种情况。

为什么之前接触文件会导致后续访问行为不同?

我是在做不受支持的事情,还是 API 没有按照预期的方式运行? (或者我的代码中可能存在我忽略的错误...?)

编辑: 似乎我被哄骗认为我正确使用了 API,因为它在大多数情况下都奇迹般地工作。正确的方法是为每个重叠结构指定唯一事件,如已接受的答案中所指出的那样。在为每个重叠请求赋予它自己的事件后,它会始终如一地工作。

最佳答案

您没有为您的 OVERLAPPED 结构提供独特的事件,因此所有 GetOverlappedResult() 必须等待的是文件句柄 - 并且无法保证有多个未完成的请求您请求的请求实际上是在文件句柄收到信号时完成的请求。

每个 OVERLAPPED 结构都应将其 hEvent 成员初始化为使用 CreateEvent() 创建的新事件句柄。

关于c++ - 这是 Windows 文件 API 的正确用法吗? (多个重叠请求),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40275466/

相关文章:

windows - 有没有一种方法可以在批处理脚本中存储和检索文件中的密码?

c++ - 处理 GetSidIdentifierAuthority 函数的返回值

c++ - 从外部源向 Windows 调试器发送调试事件

.net - 将窗口置于无焦点的前面

c++ - 在 C++11 "foreach"语句中是否有任何(方便的)方法来检索当前迭代#?

c++ - 填充二维数组或构建具有预定数字的网格?

c++ - 为什么 mersenne_twister_engine 保证某些结果?

c++ - 库项目的预编译 header 中定义的变量和函数是否可供使用该库的应用程序使用?

MySQL 推荐 InnoDB 的小写表名

c# - 模态样式的 Ookii.Dialogs ProgressDialog 在完成后不会聚焦所有者窗口