INI配置文件基础读写
一、INI文件核心基础概念
1. 什么是INI配置文件
INI 是 Windows 系统原生的轻量级配置文件,专门用来存储软件固定配置:服务器地址、端口、账号、参数设置等。
特点:纯文本、结构简单、无需第三方组件、开机常驻系统支持、读写稳定。
2. INI固定语法结构(必考)
[节点(Section)]
键名(Key)=值(Value)
层级关系:文件 → 节点(节) → 键 → 值
示例:
[Address]
serve=127.0.0.1
port=8080
3. 读写原理
C# 没有原生INI读写类,必须调用 Windows 系统内核动态链接库:kernel32.dll
通过 DllImport 导入系统原生API,再进行二次封装,方便项目调用。
二、项目前置必备配置(不配置代码直接报错)
1. 程序集引用
右键项目 → 添加引用 → 程序集 → 勾选:System.Configuration
2. 必须引入的命名空间
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Configuration;
三、App.config 配置文件详解(独立模块)
作用:统一管理INI文件路径,避免代码写死路径,方便后期修改路径。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<!-- 自定义配置节点 -->
<appSettings>
<!-- key:配置名 value:INI文件绝对路径 -->
<add key="filePath" value="C:\Users\Administrator\Desktop\第29天\2 ini文件读写\bin\Debug\1.txt"/>
</appSettings>
</configuration>
解析:程序通过 ConfigurationManager 读取 filePath 对应的路径,实现路径统一配置。
四、INI工具类 INITool 逐段拆解讲解
1. 命名空间与全局路径读取
namespace _2_ini文件读写
{
public class INITool
{
//读取App.config中配置的INI文件路径
//必须引用 System.Configuration 否则报错
private static string filePath = ConfigurationManager.AppSettings["filePath"].ToString();
作用:全局静态路径,所有读写方法统一使用该路径,无需重复写路径。
2. 内核DLL写入API声明
//引入Windows系统内核dll
//SetLastError=true 开启错误码捕获
[DllImport("kernel32.dll",CharSet = CharSet.Auto,SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
//原生API:写入INI键值对
public static extern bool WritePrivateProfileString(
string lpAppName, //节点名称(节名)
string lpKeyName, //键名
string lpString, //需要写入的值
string lpFileName //ini文件路径
);
细节说明:
CharSet.Auto:自动适配系统编码,避免中文乱码
SetLastError:开启系统错误捕获,方便排错
MarshalAs:指定非托管返回值类型
3. 内核DLL读取API声明
//原生API:读取INI指定键值
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileString(
string lpAppName, //节点名
string lpKeyName, //键名
string lpDefault, //读取失败默认值
StringBuilder lpReturnedString, //接收读取结果的缓冲区
uint nSize, //缓冲区大小
string lpFileName //文件路径
);
4. 封装写入方法(对外调用)
对原生复杂API进行二次封装,简化调用参数
/// <summary>
/// INI写入方法
/// </summary>
/// <param name="section">节点名称</param>
/// <param name="key">键名称</param>
/// <param name="value">写入的值</param>
public static void Write(string section,string key,string value)
{
//调用系统原生写入方法
WritePrivateProfileString(section,key,value, filePath);
}
调用规则:
-
节点不存在 → 自动创建节点
-
键不存在 → 新增键值对
-
键已存在 → 覆盖原值
5. 封装读取方法(对外调用)
/// <summary>
/// INI读取方法
/// </summary>
/// <param name="section">节点名</param>
/// <param name="key">键名</param>
/// <returns>返回读取到的值</returns>
public static string Read(string section, string key)
{
//创建字符串缓冲区,接收读取数据
StringBuilder sb = new StringBuilder();
//读取数据,默认值为空,缓冲区大小255
GetPrivateProfileString(section, key,"",sb,255,filePath);
//返回读取到的字符串
return sb.ToString();
}
}
}
细节:缓冲区大小255,足够存储普通配置文本,读取不到内容返回空字符串。
五、窗体Form1 代码逐段拆解
1. 窗体全局路径定义
namespace _2_ini文件读写
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//全局读取配置文件路径,与工具类路径保持一致
private static string filePath = ConfigurationManager.AppSettings["filePath"].ToString();
2. 写入按钮功能(Button1)
功能:将文本框内容写入INI文件的Address节点下
//写入INI配置
private void button1_Click(object sender, EventArgs e)
{
//写入节点Address下的 serve 键
INITool.Write("Address","serve",textBox1.Text);
//写入节点Address下的 port 键
INITool.Write("Address", "port", textBox2.Text);
}
执行后INI文件效果:
[Address]
serve=用户输入内容
port=用户输入内容
3. 读取按钮功能(Button2)
功能:读取INI指定节点、指定键的值,赋值给文本框展示
//读取INI配置
private void button2_Click(object sender, EventArgs e)
{
//读取serve的值显示到文本框1
textBox1.Text = INITool.Read("Address","serve");
//读取port的值显示到文本框2
textBox2.Text = INITool.Read("Address", "port");
}
}
}
六、完整执行流程
1、在App.config配置INI文件路径
2、程序启动读取配置路径
3、点击写入:调用封装Write方法 → 调用内核API → 写入/覆盖INI键值
4、点击读取:调用封装Read方法 → 读取内核缓冲区数据 → 展示到文本框
七、核心知识点总结
1、INI结构:节点 键=值
2、读写依赖:kernel32.dll 系统API
3、DllImport:托管代码调用非托管系统方法
4、二次封装意义:屏蔽复杂参数,调用极简、易维护
5、配置文件优势:路径统一管理,无需改代码改路径
八、易错点超级总结
1、未添加 System.Configuration 引用 → 读取配置报错
2、路径写错 → 文件找不到,读写无效果
3、节点名、键名大小写不一致 → 读取不到数据
4、未使用StringBuilder缓冲区 → 无法接收读取数据
5、重复写入同名键 → 自动覆盖旧数据
C# WinForm INI文件读写(第二种笔记)
一、项目功能概述
本项目基于 C# WinForm + Windows kernel32.dll API 实现标准 INI 配置文件的读写功能。
核心功能:
-
1、将文本框输入的服务器地址、端口写入本地INI配置文件
-
2、读取INI文件中指定节点、键名的配置数据,回显到界面文本框
-
3、通过App.config统一管理INI文件路径,实现配置解耦
INI文件标准格式:节点名 键=值,适合存储轻量级程序配置。
二、项目前置环境配置(必看,否则报错)
2.1 项目引用添加
右键项目 → 添加 → 引用,必须引入两个程序集:
-
System.Configuration:用于读取App.config配置文件参数
-
System.Runtime.InteropServices:用于DllImport调用系统外部API
2.2 命名空间引用
代码顶部必须引入以下命名空间,否则关键字无法识别
using System;
using System.Configuration; //读取配置文件
using System.Runtime.InteropServices; //调用系统dll
using System.Text; //字符串缓冲区
using System.Windows.Forms;
三、App.config 配置文件完整解析
3.1 完整源码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<appSettings>
<add key="filePath" value="C:\Users\Administrator\Desktop\第29天\2 ini文件读写\bin\Debug\1.txt"/>
</appSettings>
</configuration>
3.2 逐段解析
-
supportedRuntime:指定程序运行的.NET框架版本,当前为.NET4.8
-
appSettings:自定义配置节点,专门存放自定义键值对
-
key="filePath":自定义键名,用于代码中读取INI文件路径
-
value:INI配置文件的绝对物理路径,程序所有读写操作都针对该文件
作用:将文件路径写在配置文件中,不硬编码在代码里,后续修改路径无需改代码,直接改配置文件即可。
四、核心工具类 INITool 逐行详解
INITool 是自定义封装的INI操作工具类,核心原理:调用Windows系统内核 kernel32.dll 原生API实现读写。
4.1 定义命名空间与全局路径
namespace _2_ini文件读写
{
public class INITool
{
//读取app.config中配置的INI文件路径
private static string filePath = ConfigurationManager.AppSettings["filePath"].ToString();
}
代码解析:
-
ConfigurationManager.AppSettings["filePath"]:读取App.config中appSettings节点下key为filePath的值 -
静态字段:全局唯一,整个工具类共享同一个文件路径
-
解决硬编码问题,统一管理文件路径
4.2 kernel32.dll 原理说明
kernel32.dll 是Windows系统核心内核动态链接库,系统自带,无需手动引入,负责内存管理、文件IO、系统交互。
C# 本身没有原生INI读写方法,只能通过 DllImport 跨语言调用 系统原生API实现功能。
4.3 写入API声明(核心)
[DllImport("kernel32.dll",CharSet = CharSet.Auto,SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName);
特性标签解析:
-
DllImport("kernel32.dll"):声明调用系统内核dll中的外部方法 -
CharSet = CharSet.Auto:自动适配系统字符集,兼容中文不乱码 -
SetLastError = true:开启系统错误捕获,可获取API调用失败错误码 -
MarshalAs(UnmanagedType.Bool):将C++原生布尔类型转换为C#可识别的bool类型
方法参数详解:
-
lpAppName:INI文件的节点名(例如 Address) -
lpKeyName:节点下的键名(serve、port) -
lpString:需要写入的键值 -
lpFileName:INI文件的完整物理路径
4.4 读取API声明(核心)
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName);
参数详解:
-
lpAppName:INI节点名 -
lpKeyName:需要读取的键名 -
lpDefault:读取失败时的默认值(此处为空) -
lpReturnedString:字符串缓冲区,接收读取到的值 -
nSize:缓冲区最大容量(限制读取字符长度) -
lpFileName:文件路径
4.5 自定义封装Write写入方法
/// <summary>
/// 写入ini
/// </summary>
/// <param name="section"> 节名称</param>
/// <param name="key"> 键名称</param>
/// <param name="value"> 值</param>
public static void Write(string section,string key,string value)
{
WritePrivateProfileString(section,key,value, filePath);
}
封装意义:
-
原生API参数多、使用复杂,封装后简化调用
-
外部窗体只需传 节点、键、值 三个参数即可完成写入
-
统一复用文件路径,无需重复传参
4.6 自定义封装Read读取方法
/// <summary>
/// 读取指定节 键的值
/// </summary>
/// <param name="section"></param>
/// <param name="key"></param>
/// <returns></returns>
public static string Read(string section, string key)
{
StringBuilder sb = new StringBuilder();
GetPrivateProfileString(section, key,"",sb,255,filePath);
return sb.ToString();
}
代码解析:
-
StringBuilder sb:创建字符串缓冲区,接收读取结果 -
默认值为空字符串,读取不到数据返回空
-
缓冲区大小255,支持短文本配置存储
-
最后将缓冲区内容转为字符串返回给调用处
五、窗体Form1 业务代码逐段详解
5.1 窗体构造方法
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
作用:初始化窗体所有控件(文本框、按钮等),WinForm程序固定写法。
5.2 写入按钮点击事件(button1)
//写入
private void button1_Click(object sender, EventArgs e)
{
INITool.Write("Address","serve",textBox1.Text);
INITool.Write("Address", "port", textBox2.Text);
}
业务逻辑:
-
调用封装好的工具类写入方法
-
在INI文件中创建/覆盖
[Address]节点 -
将textBox1内容写入 serve 键
-
将textBox2内容写入 port 键
写入后INI文件效果:
[Address]
serve=输入的服务器地址
port=输入的端口号
5.3 读取按钮点击事件(button2)
//读取
private void button2_Click(object sender, EventArgs e)
{
textBox1.Text = INITool.Read("Address","serve");
textBox2.Text = INITool.Read("Address", "port");
}
业务逻辑:
-
读取INI文件Address节点下的serve值,赋值给第一个文本框
-
读取INI文件Address节点下的port值,赋值给第二个文本框
-
实现配置数据回显
六、原代码BUG问题总结(重点避坑)
-
BUG1:重复定义 filePath:窗体类和工具类重复声明静态路径字段,编译报错
-
BUG2:重复嵌套命名空间:代码拼接导致命名空间重复包裹,语法不规范
-
BUG3:无容错处理:配置路径为空、文件不存在时,程序直接崩溃
-
BUG4:缓冲区过小:默认255字符,过长配置会被截断
七、整体运行流程总结
-
程序启动,工具类读取App.config中的INI文件路径
-
点击写入按钮:获取文本框内容 → 调用系统API → 写入本地INI文件
-
点击读取按钮:调用系统API读取文件内容 → 赋值到界面文本框
-
全程基于系统原生API,读写稳定、兼容性强,适合桌面程序配置存储
八、拓展优化方向
-
1、将绝对路径改为相对路径,适配任意电脑运行
-
2、增加文件不存在自动创建逻辑,避免报错
-
3、增加空值判断,防止写入空配置
-
4、扩容字符串缓冲区,支持长文本配置
INI文件高阶读写(完整工具类)了解
一、知识点前置
1、INI文件结构:节点(Section) 键(Key)=值(Value)
2、C#无原生INI操作,依赖 kernel32.dll Windows系统API
3、特点:节点、键名不区分大小写;文件不存在自动创建
4、必备引用:System.Configuration、System.Runtime.InteropServices、System.Text
二、整体结构划分
1、原生API声明(底层系统方法)
2、二次封装工具方法(开发直接调用)
3、窗体调用演示(增、删、改、查、批量读取)
三、底层 Kernel32 API 声明代码
作用:导入系统原生INI读写函数,为上层封装提供底层支撑
namespace Tool
{
public class IniAPI
{
#region 底层API声明
/// <summary>
/// 获取INI所有节点名称
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileSectionNames(IntPtr lpszReturnBuffer, uint nSize, string lpFileName);
/// <summary>
/// 获取指定节点下所有键值对
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileSection(string lpAppName, IntPtr lpReturnedString, uint nSize, string lpFileName);
/// <summary>
/// 读取字符串(char数组缓冲区)
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, [In, Out] char[] lpReturnedString, uint nSize, string lpFileName);
/// <summary>
/// 读取字符串(StringBuilder缓冲区)
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName);
/// <summary>
/// 读取字符串(string缓冲区)
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, string lpReturnedString, uint nSize, string lpFileName);
/// <summary>
/// 写入/覆盖节点整体内容
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool WritePrivateProfileSection(string lpAppName, string lpString, string lpFileName);
/// <summary>
/// 写入/覆盖单个键值对
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName);
#endregion
四、高阶封装:查询系列方法
1、获取INI所有节点名称
/// <summary>
/// 获取所有Section节点
/// </summary>
public static string[] INIGetAllSectionNames(string iniFile)
{
uint MAX_BUFFER = 32767;
string[] sections = new string[0];
//分配非托管内存
IntPtr pReturnedString = Marshal.AllocCoTaskMem((int)MAX_BUFFER * sizeof(char));
uint bytesReturned = IniAPI.GetPrivateProfileSectionNames(pReturnedString, MAX_BUFFER, iniFile);
if (bytesReturned != 0)
{
//解析内存字符串,以\0切割
string local = Marshal.PtrToStringAuto(pReturnedString, (int)bytesReturned);
sections = local.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
}
//释放内存,防止内存泄漏
Marshal.FreeCoTaskMem(pReturnedString);
return sections;
}
2、获取指定节点所有键值对(key=value)
/// <summary>
/// 获取指定节点下所有键值对
/// </summary>
public static string[] INIGetAllItems(string iniFile, string section)
{
uint MAX_BUFFER = 32767;
string[] items = new string[0];
IntPtr pReturnedString = Marshal.AllocCoTaskMem((int)MAX_BUFFER * sizeof(char));
uint bytesReturned = IniAPI.GetPrivateProfileSection(section, pReturnedString, MAX_BUFFER, iniFile);
if (!(bytesReturned == MAX_BUFFER - 2) || (bytesReturned == 0))
{
string returnedString = Marshal.PtrToStringAuto(pReturnedString, (int)bytesReturned);
items = returnedString.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
}
Marshal.FreeCoTaskMem(pReturnedString);
return items;
}
3、获取指定节点所有Key键名
/// <summary>
/// 获取节点下所有键名
/// </summary>
public static string[] INIGetAllItemKeys(string iniFile, string section)
{
string[] value = new string[0];
const int SIZE = 1024 * 10;
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
char[] chars = new char[SIZE];
uint bytesReturned = IniAPI.GetPrivateProfileString(section, null, null, chars, SIZE, iniFile);
if (bytesReturned != 0)
{
value = new string(chars).Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
}
chars = null;
return value;
}
4、读取字符串值(带默认值)
/// <summary>
/// 读取字符串配置,找不到返回默认值
/// </summary>
public static string INIGetStringValue(string iniFile, string section, string key, string defaultValue)
{
string value = defaultValue;
const int SIZE = 1024 * 10;
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
if (string.IsNullOrEmpty(key))
throw new ArgumentException("必须指定键名称(key)", "key");
StringBuilder sb = new StringBuilder(SIZE);
uint bytesReturned = IniAPI.GetPrivateProfileString(section, key, defaultValue, sb, SIZE, iniFile);
if (bytesReturned != 0)
value = sb.ToString();
sb = null;
return value;
}
5、扩展读取:int、double、string 快捷方法
//读取int类型
public static int GetPrivateProfileInt(string lpAppName, string lpKeyName, int Default, string lpFileName)
{
StringBuilder lpReturnedString = new StringBuilder(1024);
GetPrivateProfileString(lpAppName, lpKeyName, Convert.ToString(Default), lpReturnedString, 1024, lpFileName);
return Convert.ToInt32(lpReturnedString.ToString());
}
//读取double浮点类型
public static double GetPrivateProfileDouble(string lpAppName, string lpKeyName, double Default, string lpFielName)
{
StringBuilder lpReturnedString = new StringBuilder(1024);
GetPrivateProfileString(lpAppName, lpKeyName, Convert.ToString(Default), lpReturnedString, 1024, lpFielName);
return Convert.ToDouble(lpReturnedString.ToString());
}
//简易读取字符串
public static string GetPrivateProfileString(string lpAppName, string lpKeyName, string Default, string lpFileName)
{
StringBuilder lpReturnedString = new StringBuilder(1024);
GetPrivateProfileString(lpAppName, lpKeyName, Default, lpReturnedString, 1024, lpFileName);
return lpReturnedString.ToString();
}
五、高阶封装:写入、删除系列方法
1、批量写入节点键值对
public static bool INIWriteItems(string iniFile, string section, string items)
{
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
if (string.IsNullOrEmpty(items))
throw new ArgumentException("必须指定键值对", "items");
return IniAPI.WritePrivateProfileSection(section, items, iniFile);
}
2、单键值写入/覆盖
public static bool INIWriteValue(string iniFile, string section, string key, string value)
{
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
if (string.IsNullOrEmpty(key))
throw new ArgumentException("必须指定键名称", "key");
if (value == null)
throw new ArgumentException("值不能为null", "value");
return IniAPI.WritePrivateProfileString(section, key, value, iniFile);
}
3、删除单个Key
public static bool INIDeleteKey(string iniFile, string section, string key)
{
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
if (string.IsNullOrEmpty(key))
throw new ArgumentException("必须指定键名称", "key");
//value传null = 删除该键
return IniAPI.WritePrivateProfileString(section, key, null, iniFile);
}
4、删除整个节点
public static bool INIDeleteSection(string iniFile, string section)
{
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
//key、value都传null = 删除整个节点
return IniAPI.WritePrivateProfileString(section, null, null, iniFile);
}
5、清空节点所有内容(保留节点)
public static bool INIEmptySection(string iniFile, string section)
{
if (string.IsNullOrEmpty(section))
throw new ArgumentException("必须指定节点名称", "section");
//内容传空字符串 = 清空节点数据
return IniAPI.WritePrivateProfileSection(section, string.Empty, iniFile);
}
#endregion
#endregion
}
}
六、窗体调用完整代码(实战演示)
1、读取配置文件路径
//读取App.config中配置的INI文件路径
private static string filePath = ConfigurationManager.AppSettings["filePath"].ToString();
2、批量读取节点所有键值对(Button3)
功能:读取 Address 节点下所有 key=value 数据并弹窗展示
private void button3_Click(object sender, EventArgs e)
{
//获取指定节点所有键值对数组
string[] arr = IniAPI.INIGetAllItems(filePath, "Address");
foreach (string item in arr)
{
MessageBox.Show(item);
}
}
3、新增/写入自定义节点键值(Button4)
功能:创建【相机】节点,写入曝光参数,存在则覆盖
private void button4_Click(object sender, EventArgs e)
{
//写入:节点=相机,键=曝光,值=300
IniAPI.INIWriteValue(filePath,"相机","曝光","300");
}
4、读取指定键值 + 默认值容错(Button5)
功能:读取端口值,读取失败返回默认值ss,赋值给窗体标题
private void button5_Click(object sender, EventArgs e)
{
//参数:路径、节点、键、默认值
this.Text = IniAPI.INIGetStringValue(filePath,"Address","port","ss");
//扩展:删除指定键
//IniAPI.INIDeleteKey(filePath,"Address","port");
}
七、INI工具类全部功能汇总(必背)
查询功能
1、INIGetAllSectionNames:获取全部节点
2、INIGetAllItems:获取节点全部键值对
3、INIGetAllItemKeys:获取节点全部键名
4、INIGetStringValue:读取字符串(带默认值)
5、GetPrivateProfileInt:读取整型
6、GetPrivateProfileDouble:读取浮点型
写入功能
1、INIWriteValue:单条键值写入/覆盖
2、INIWriteItems:批量写入节点数据
删除功能
1、INIDeleteKey:删除单个键
2、INIEmptySection:清空节点内容,保留节点
3、INIDeleteSection:删除整个节点
八、核心底层原理细节(必考)
1、CharSet.Auto:自动适配系统编码,读取必须搭配 Marshal.PtrToStringAuto
2、StringBuilder 无法识别 \0 截断字符,只能用于单键值读取
3、IntPtr 非托管内存,使用后必须 FreeCoTaskMem 释放,防止内存泄漏
4、写入传 null / 空字符串 具备不同删除逻辑
5、INI文件不存在时,API会自动创建空文件
九、易错点总结
1、未添加 System.Configuration 引用,读取App配置报错
2、非托管内存不释放,造成内存泄漏
3、节点、键名传空,代码主动抛异常防护
4、编码方式和读取方式不匹配,出现乱码、读取不全
5、删除逻辑混淆:清空节点、删除键、删除整节点三种逻辑不同