Запись и чтение файла FILE_FLAG_DELETE_ON_CLOSE в Win32

Я пытаюсь писать и читать из временного файла с 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, &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?

Вы не проверили bytes_read. Бьюсь об заклад, это ноль, потому что вы читаете дальше конца файла

Raymond Chen 21.02.2023 02:34

Вы вызываете ReadFile сразу после WriteFile, поэтому вы пытаетесь прочитать дальше конца файла. Используйте SetFilePointer, чтобы вернуться назад, если вы хотите прочитать то, что вы только что написали. Я ожидаю, что std::ifstream не сможет открыть файл, потому что он не указывает общий доступ (даже если вы разрешаете общий доступ, другие пользователи файла также должны согласиться на общий доступ, чтобы иметь возможность открыть его).

Jonathan Potter 21.02.2023 02:35

Звонок GetLastError не даст достоверной информации. Между системой, устанавливающей последний код ошибки, и ThrowLastFSError попыткой его подобрать, есть как минимум два вызова c'tor. Вам нужно будет вызвать GetLastError сразу после оценки условий, при которых задокументирован соответствующий вызов API, для установки кода ошибки.

IInspectable 21.02.2023 02:56

Спасибо обоим. Я исправил и отредактировал вопрос, чтобы сосредоточиться на проблеме ifstream. @JonathanPotter, глядя на реализацию Microsoft STL, ifstream открывается с помощью _wfsopen с использованием _SH_DENYNO по умолчанию, что , по-видимому, означает «Разрешает доступ для чтения и записи», поэтому я думаю, что он использует правильный режим общего доступа.

MHebes 21.02.2023 03:01

@IInspectable Хороший вопрос. Когда я пытаюсь открыть вторую ручку вручную вместо использования ifstream: HANDLE hfile2 = ::CreateFile2(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, &params); я получаю ту же ошибку.

MHebes 21.02.2023 03:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что вы создаете файл с помощью 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 уничтожит.

Ах, разочаровывающая, но отличная информация! Спасибо. Может быть, я напишу iostream настройку, которая работает с CreateFile2 с этими режимами обмена вместо _wfsopen с _SH_DENYNO

MHebes 21.02.2023 03:26

@MHebes, возможно, вы могли бы обойти это, прыгнув через несколько обручей: 1. Создайте файл с помощью CreateFile / CreateFile2 с правильными режимами общего доступа - 2. Вызовите _open_osfhandle, чтобы создать дескриптор файла среды выполнения c из дескриптора - 3. Вызовите _fdopen, чтобы получить a FILE* для дескриптора файла - 4. Создайте std::basic_filebuf из FILE*с помощью общедоступного конструктора -

Turtlefight 21.02.2023 03:47

Затем создайте std::ifstream и поменяйте местами файловый буфер, например. std::ifstream stream; stream.rdbuf()->swap(yourPreparedBasicFilebuf);. К сожалению, это довольно много обручей, через которые вам нужно будет пройти, чтобы настроить его, но к этому моменту у вас должен быть полностью функциональный ifstream от данного дескриптора.

Turtlefight 21.02.2023 03:49

@MHebes Вот небольшой пример, демонстрирующий, как это может работать. (К сожалению, у меня нет под рукой компьютера с Windows, поэтому я не могу его проверить)

Turtlefight 21.02.2023 04:10

Другие вопросы по теме