在 Win32 中写入和读取 FILE_FLAG_DELETE_ON_CLOSE 文件

问题描述 投票:0回答:1

我正在尝试使用 Win32 API 写入和读取临时文件。

#include <Windows.h>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>

void ThrowLastFSError(std::string name, std::filesystem::path path) {
  throw std::filesystem::filesystem_error{
      name, path,
      std::error_code{(int)::GetLastError(), std::system_category()}};
}

int main() {
  HANDLE hfile = NULL;
  try {
    // Create a temporary file
    CREATEFILE2_EXTENDED_PARAMETERS params{};
    params.dwSize = sizeof(params);
    params.dwFileAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_HIDDEN;
    params.dwFileFlags = FILE_FLAG_DELETE_ON_CLOSE;
    params.lpSecurityAttributes = nullptr;
    params.hTemplateFile = NULL;

    auto path = L"test.txt";

    hfile = ::CreateFile2(
        path,
        GENERIC_READ | GENERIC_WRITE,        // Open for reading and writing
        FILE_SHARE_READ | FILE_SHARE_WRITE,  // Share both of those modes
        CREATE_NEW, &params);

    if (!hfile || hfile == INVALID_HANDLE_VALUE) {
      ThrowLastFSError("CreateFile2", path);
    }

    // Write something to it
    char data[] = "hello world\n";
    DWORD bytes_written;
    bool ok =
        ::WriteFile(hfile, data, sizeof(data) - 1, &bytes_written, nullptr);
    if (!ok) ThrowLastFSError("WriteFile", path);

    // Read something from it

    char inbuf[100];
    DWORD bytes_read = 0;
    ::SetFilePointer(hfile, 0, nullptr, FILE_BEGIN);
    ok = ::ReadFile(hfile, inbuf, sizeof(inbuf), &bytes_read, nullptr);
    if (!ok) ThrowLastFSError("ReadFile", path);
    std::cout << "contains: " << std::string_view(inbuf, bytes_read)
              << std::endl;
    // contains: hello world

    //////////////////////////////
    // PROBLEM: ifstream can't open the file
    //////////////////////////////

    ::SetFilePointer(hfile, 0, nullptr, FILE_BEGIN);
    std::ifstream ifs(path);
    if (!ifs.is_open()) ThrowLastFSError("ifstream()", path);
    // ifstream(): The process cannot access the file because it is being used
    // by another process. : "test.txt"

    std::stringstream ss;
    ss << ifs.rdbuf();

    ::CloseHandle(hfile);
    return 0;
  } catch (std::exception& e) {
    std::cerr << e.what() << std::endl;
    ::CloseHandle(hfile);
    return 1;
  }
}

注意我创建的文件是:

  • FILE_FLAG_DELETE_ON_CLOSE
    ,所以我无法关闭手柄并重新打开它
  • GENERIC_READ | GENERIC_WRITE
    ,所以我可以读写手柄
  • FILE_SHARE_READ | FILE_SHARE_WRITE
    ,所以我可以(理论上)打开它的其他句柄

WriteFile
ReadFile
似乎工作正常。

但是,尝试打开同一路径下文件的另一个句柄,例如

ifstream
导致访问冲突。

如何打开一个文件句柄,我有一个当前打开的句柄,用

ifstream

c++ file winapi
1个回答
2
投票

问题是您正在使用

FILE_FLAG_DELETE_ON_CLOSE
创建文件。

您不能为该文件创建第二个句柄,除非您指定

FILE_SHARE_DELETE
.

这在

CREATEFILE2_EXTENDED_PARAMETERS
文档(dwFileFlags 部分)

国旗 意义
FILE_FLAG_DELETE_ON_CLOSE 文件将在其所有句柄关闭后立即删除,包括指定的句柄和任何其他打开或重复的句柄。如果存在文件的打开句柄,调用将失败,除非它们都是使用 FILE_SHARE_DELETE 共享模式打开的。

对该文件的后续打开请求失败,除非指定了 FILE_SHARE_DELETE 共享模式。

所以你需要用

FILE_SHARE_DELETE
打开两个手柄:

// ...
hfile = ::CreateFile2(
        path,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        CREATE_NEW, &params);

// ....

// open a second handle
// MUST specify FILE_SHARE_DELETE
anotherFile = ::CreateFile2(
        path,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        OPEN_EXISTING, nullptr);
// ...

不幸的是,这对于 C++ iostreams 是不可能的(您只能共享读/写访问权限(使用

_SH_DENYNO
),但不能删除访问权限)

您可以通过自己创建句柄然后跳过几个环将句柄放入 ifstream 来解决此限制:godbolt

#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <fstream>

// create handle with appropriate sharing flags
HANDLE hFile = CreateFile2(
    L"somefile.txt",
    GENERIC_READ,
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    OPEN_EXISTING,
    nullptr
);

// convert the handle into a c runtime file descriptor
int cHandle = _open_osfhandle(
    reinterpret_cast<intptr_t>(hFile),
    _O_TEXT | _O_RDONLY
);

// create a file stream from the file descriptor
FILE* file = _fdopen(
    cHandle,
    "r"
);

// create a filebuf from the filestream
std::basic_filebuf<char, std::char_traits<char>> fileBuf(file);

// create an ifstream and swap in our filebuf
std::ifstream stream;
stream.rdbuf()->swap(fileBuf);

// use stream to read from the file, e.g.:
std::string str;
stream >> str;

请记住,如果您在文件仍有句柄的情况下删除文件,

ifstream
很可能不会很高兴(这很可能是您需要经历所有这些步骤才能获得句柄的原因用
FILE_SHARE_DELETE
变成
ifstream
)。

所以为了安全起见,我建议确保文件在

ifstream
破坏之前不会被删除。

© www.soinside.com 2019 - 2024. All rights reserved.