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调用成功
成因分析
- Windows下
std::ofstream默认行为限制:默认使用ios::out | ios::trunc模式打开文件,对应Windows API的TRUNCATE_EXISTING创建方式,且共享模式仅允许FILE_SHARE_READ(即不允许其他进程对文件执行写入/删除操作)。 - 系统后台进程临时锁定:复制文件后,Windows Defender实时扫描、搜索索引服务等后台进程可能会临时获取文件的写锁定或独占锁定,导致C++程序无法以默认模式打开文件。
- 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
相关产品推荐
相关产品推荐

