<p>C# 中应优先使用 Windows API 的 GetPrivateProfileString 和 WritePrivateProfileString 进行 INI 文件读写,因其轻量、稳定、兼容传统格式;需注意绝对路径、缓冲区大小、编码一致性和跨平台限制。</p>

INI 文件读写在 C# 里根本不用自己解析
Windows API 提供了 GetPrivateProfileString 和 WritePrivateProfileString 这两个函数,C# 可以直接 P/Invoke 调用,比手写解析器更轻、更稳、更兼容老式 INI 格式(比如节名大小写不敏感、支持空行和注释)。
别用第三方库或手动 StreamReader + 字符串分割——INI 看似简单,但处理转义、等号位置、括号嵌套、BOM、编码混合时容易出错。系统 API 是经过几十年验证的。
-
GetPrivateProfileString返回的是实际读到的字符数,不是布尔值;返回 0 不一定失败,要检查GetLastError() - 路径必须是绝对路径,相对路径会被解释为相对于当前进程工作目录,不是 EXE 所在目录
- 传入的缓冲区大小必须包含结尾的
\0,否则可能截断或越界
调用 GetPrivateProfileString 的典型坑
最常见的错误是传入的 lpReturnedString 缓冲区太小,导致读不到完整值,或者没清零就复用,残留旧内容。
- 缓冲区建议至少分配 32767 字节(Windows INI 值最大长度),用
StringBuilder更安全,别用string直接传 - 如果想判断键是否存在,不要只看返回值是否为 0;应先传入空字符串作为默认值,再比对返回结果是否等于该默认值
- 节名和键名不区分大小写,但 API 不会帮你做大小写归一化,
"Section"和"section"在同一个文件里会被视为相同节
示例:读取 config.ini 中 [Network]Timeout 的值
StringBuilder sb = new StringBuilder(256);
int len = GetPrivateProfileString("Network", "Timeout", "30", sb, sb.Capacity, @"C:\app\config.ini");
string value = sb.ToString(); // 注意:value 是纯字符串,不含末尾 \0
WritePrivateProfileString 写入后文件没更新?
写入失败通常不是权限问题,而是路径不可写或父目录不存在。API 不会自动创建中间目录,也不会抛托管异常,失败时只返回 false。
- 确保
config.ini所在目录存在且进程有写权限(尤其在 Program Files 或系统目录下,UAC 会拦截) - 写入后不会自动刷新磁盘缓存,但后续读操作一般能立刻看到新值(Windows 内部做了同步)
- 如果写入的是空字符串(
""),效果等同于删除该键;写入null会导致行为未定义,务必避免 - 写入中文时,文件编码会被保持为原编码;若原文件是 ANSI(如 GBK),写入 UTF-8 字符串会导致乱码 —— 必须保证传入字符串与文件当前编码一致
跨平台项目别碰 Windows INI API
.NET Core / .NET 5+ 在非 Windows 平台调用 GetPrivateProfileString 会直接抛 DllNotFoundException,因为 kernel32.dll 不存在。这不是配置问题,是架构限制。
- 如果项目要跑 Linux/macOS,必须提前检测运行环境:
OperatingSystem.IsWindows(),再分支处理 - 更稳妥的做法是统一用
Microsoft.Extensions.Configuration.Ini(仅限 .NET 5+),它内部不依赖 Win32 API,而是纯托管解析,但不支持所有传统 INI 特性(如多行值、注释保留) - 已有大量遗留 INI 文件要兼容?建议封装一层适配器:Windows 下走 P/Invoke,其他平台走文本解析 + 严格格式校验
真正麻烦的从来不是“怎么读”,而是“读出来的值到底算不算数”——比如空格是否被 trim、注释行是否影响键匹配、BOM 是否导致节名比对失败。这些细节藏在 API 文档角落,但线上故障往往就卡在这儿。









