Я пытаюсь писать и читать из временного файла с API Win32.
#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
?
Вы вызываете ReadFile
сразу после WriteFile
, поэтому вы пытаетесь прочитать дальше конца файла. Используйте SetFilePointer
, чтобы вернуться назад, если вы хотите прочитать то, что вы только что написали. Я ожидаю, что std::ifstream
не сможет открыть файл, потому что он не указывает общий доступ (даже если вы разрешаете общий доступ, другие пользователи файла также должны согласиться на общий доступ, чтобы иметь возможность открыть его).
Звонок GetLastError
не даст достоверной информации. Между системой, устанавливающей последний код ошибки, и ThrowLastFSError
попыткой его подобрать, есть как минимум два вызова c'tor. Вам нужно будет вызвать GetLastError
сразу после оценки условий, при которых задокументирован соответствующий вызов API, для установки кода ошибки.
Спасибо обоим. Я исправил и отредактировал вопрос, чтобы сосредоточиться на проблеме ifstream
. @JonathanPotter, глядя на реализацию Microsoft STL, ifstream
открывается с помощью _wfsopen
с использованием _SH_DENYNO
по умолчанию, что , по-видимому, означает «Разрешает доступ для чтения и записи», поэтому я думаю, что он использует правильный режим общего доступа.
@IInspectable Хороший вопрос. Когда я пытаюсь открыть вторую ручку вручную вместо использования ifstream
: HANDLE hfile2 = ::CreateFile2(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, ¶ms);
я получаю ту же ошибку.
Проблема в том, что вы создаете файл с помощью 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
уничтожит.
Ах, разочаровывающая, но отличная информация! Спасибо. Может быть, я напишу iostream
настройку, которая работает с CreateFile2
с этими режимами обмена вместо _wfsopen
с _SH_DENYNO
@MHebes, возможно, вы могли бы обойти это, прыгнув через несколько обручей: 1. Создайте файл с помощью CreateFile
/ CreateFile2
с правильными режимами общего доступа - 2. Вызовите _open_osfhandle
, чтобы создать дескриптор файла среды выполнения c из дескриптора - 3. Вызовите _fdopen
, чтобы получить a FILE*
для дескриптора файла - 4. Создайте std::basic_filebuf
из FILE*
с помощью общедоступного конструктора -
Затем создайте std::ifstream
и поменяйте местами файловый буфер, например. std::ifstream stream; stream.rdbuf()->swap(yourPreparedBasicFilebuf);
. К сожалению, это довольно много обручей, через которые вам нужно будет пройти, чтобы настроить его, но к этому моменту у вас должен быть полностью функциональный ifstream
от данного дескриптора.
@MHebes Вот небольшой пример, демонстрирующий, как это может работать. (К сожалению, у меня нет под рукой компьютера с Windows, поэтому я не могу его проверить)
Вы не проверили
bytes_read
. Бьюсь об заклад, это ноль, потому что вы читаете дальше конца файла