我正在尝试使用 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, ¶ms);
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
?
问题是您正在使用
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, ¶ms);
// ....
// 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
破坏之前不会被删除。