You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ofstream无法打开已有文件:OpenXcom配置保存故障求助

问题:OpenXcom中options.cfg无法更新(ofstream创建失败)

问题概述

OpenXcom的Options::save()方法在创建options.cfg的std::ofstream对象时失败,导致配置文件无法更新,仅能通过删除文件重建解决。已通过简化C++程序复现该问题。

复现代码

#include <iostream>
#include <fstream>

void check(const std::string& filename)
{
    std::ofstream stream(filename.c_str());

    if (!stream) {
        std::cout << "FAIL " << filename << std::endl;
    }
    else {
        std::cout << "PASS " << filename << std::endl;
    }
}

int main()
{
    check("options1.cfg");
    check("options2.cfg");
}

复现步骤

  • 创建仅包含测试程序的目录
  • 运行程序生成两个空.cfg文件
  • 再次运行程序,确认可正常操作两个文件
  • 删除options2.cfg
  • 复制options1.cfg为options2.cfg
  • 再次运行程序,发现无法为options2.cfg创建ofstream对象

额外观察

  • 关闭或用管理员权限重启命令行无法解决问题
  • 重启系统可解决,排除文件权限问题
  • 手动用编辑器创建文件也会触发问题
  • 问题可持续数小时,期间Python可正常写入该文件
  • Process Monitor显示C++程序调用CreateFile时返回ACCESS DENIED,但Python调用成功

成因分析

  1. Windows下std::ofstream默认行为限制:默认使用ios::out | ios::trunc模式打开文件,对应Windows API的TRUNCATE_EXISTING创建方式,且共享模式仅允许FILE_SHARE_READ(即不允许其他进程对文件执行写入/删除操作)。
  2. 系统后台进程临时锁定:复制文件后,Windows Defender实时扫描、搜索索引服务等后台进程可能会临时获取文件的写锁定或独占锁定,导致C++程序无法以默认模式打开文件。
  3. Python与C++的文件打开逻辑差异:Python的文件写入使用更宽松的共享模式(允许读写删除共享),且采用CREATE_ALWAYS创建方式,因此可以绕过这类临时锁定。

修复方案

针对OpenXcom的Options::save()方法,推荐以下两种修复方式:

方案1:原子性临时文件替换(跨平台推荐)

通过临时文件中转,避免直接写入原文件时的锁定问题,同时保证配置文件的原子性(不会出现半写入的损坏文件):

void Options::save() {
    const std::string tmpFile = "options.tmp.cfg";
    const std::string cfgFile = "options.cfg";

    // 1. 写入临时文件
    std::ofstream tmpStream(tmpFile);
    if (!tmpStream) {
        // 处理临时文件创建失败(如日志记录)
        return;
    }

    // 执行原有配置写入逻辑
    // 示例:tmpStream << "key=value" << std::endl;
    // ... 原有的配置序列化代码 ...

    // 确保内容写入磁盘并关闭流
    tmpStream.flush();
    tmpStream.close();

    // 2. 删除原配置文件(跨平台处理)
#if defined(_WIN32)
    DeleteFileA(cfgFile.c_str());
#else
    std::remove(cfgFile.c_str());
#endif

    // 3. 将临时文件重命名为原配置文件
#if defined(_WIN32)
    MoveFileA(tmpFile.c_str(), cfgFile.c_str());
#else
    std::rename(tmpFile.c_str(), cfgFile.c_str());
#endif

    // 可选:重命名失败时清理临时文件
    // if (std::filesystem::exists(tmpFile)) std::filesystem::remove(tmpFile);
}

方案2:Windows平台显式设置共享模式

直接调用Windows API设置宽松的共享模式,绕过临时锁定:

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

void Options::save() {
    const wchar_t* cfgPath = L"options.cfg";
    // 打开文件时允许读写删除共享,使用CREATE_ALWAYS覆盖原有文件
    HANDLE hFile = CreateFileW(cfgPath, GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (hFile == INVALID_HANDLE_VALUE) {
        // 处理打开失败(如记录GetLastError())
        return;
    }

    // 将Windows句柄转换为C++流
    int fd = _open_osfhandle(reinterpret_cast<intptr_t>(hFile), _O_WRONLY);
    std::ofstream cfgStream(_fdopen(fd, "wb"));

    if (cfgStream) {
        // 执行原有配置写入逻辑
        // ... 原有的配置序列化代码 ...
    }

    // 关闭流会自动释放句柄
}

内容的提问来源于stack exchange,提问作者James Lent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:20:26