C# Growth Road 1 | INI file reading and writing and traversing all nodes Section (addition, deletion, modification and query)

1. INI file introduction

1. Background

The INI file is a configuration file format, which is the abbreviation of Initialization file, which is an initialization file, and is
usually used in applications in the Windows operating system. Although INI is a product of ancient times, it
can still stand in the age of masters (XML, YAML, JSON...).

2. Format

The INI structure is very simple. The file is a text that is easy to edit and read. It consists of a two-layer structure consisting of Section, key, value, and comments. The key-value pairs are separated by "=", the left is the key, and the right is the value. Each key-value pair must be summarized in a paragraph, and the paragraph must be written in "[]". Comments can start with a semicolon ";", as follows:

[参数1]
;记录参数1的键1值
键1=12=23=3
[参数2]1=12=23=3

3. Suffix (file extension)

.ini、.cfg、.conf、.txt

4. Features:

advantage:

  1. The structure is simple, the two-layer structure is clear at a glance, in line with the logic of human understanding of things, and easy to maintain;
  2. The reading and writing speed is fast, which is suitable as the configuration table of each module of the software system, reducing the software startup time;
  3. The interface is simple, easy to use, and friendly to novices.

shortcoming:

  1. The structure is too simple, with only two layers, which is not suitable for depicting complex types of data and multi-level data;
  2. Size limit 64kb;
  3. Improper operation, the Chinese description data becomes garbled;
  4. Only string type is supported;

2. Additions, deletions and modifications

1. API introduction ( refer to official documents )

Kernel32.dll (a dynamic library file that must exist under the Windows platform)

Operating the INI file requires calling the underlying APIs of the system, and these APIs are encapsulated in Kernel32.dll.
It is a core dynamic link library file of the Windows operating system, located in the Windows system directory, and provides a large number of API functions for reading and writing files, managing memory, and managing threads, such as: create, open, read, write, close files, Thread management, process management, debugging, error handling, time handling.

GetPrivateProfileInt (get the int value of the file through the section and key)

  • Primitive:
UINT GetPrivateProfileInt(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
INT nDefault, 
LPCTSTR lpFileName)
  • parameter:
lpAppName: 节名称,注意这个字串是不区分大小写的。
lpKeyName: 键名称,这个支持不区分大小写。
nDefault:  默认值,指定条目未找到时返回的默认值。
lpFileName:初始化文件的名字,如果没有指定完整的路径名,windows就会在Windows目录中搜索文件。
  • return value
返回值是指定初始化文件中指定键名称后面的字符串的整数等效项。 如果未找到键,则返回值为指定的默认值。
  • C# declaration method
[DllImport("kernel32.dll")]
private static extern int GetPrivateProfileInt(string lpAppName,string lpKeyName,int nDefault,string lpFileName);

GetPrivateProfileString (by section, key, get the file string)

  • Primitive:
DWORD GetPrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpDefault,
LPCTSTR  lpReturnedString,
DWORD   nSize,
LPC TSTR lpFileName
);
  • parameter:
lpAppName:       节名称,注意这个字串是不区分大小写的。如果此参数为 NULL,则读取所有节点名。
lpKeyName:       键名称,这个支持不区分大小写。如果此参数为 NULL,lpAppName不为null,则读取所有该节点的所有键值。
lpDefault:       默认值,指定条目未找到时返回的默认值。
lpReturnedString:存储返回值,指向接收检索字符串的缓冲区的指针。
nSize:           参数指向的缓冲区的大小(以字符为单位)。
lpFileName:      初始化文件的名字。如果没有指定完整的路径名,windows就会在Windows目录中搜索文件
  • return value
返回值是复制到缓冲区的字符数,不包括终止 null 字符。

如果 lpAppName 和 lpKeyName 都不是 NULL ,并且提供的目标缓冲区太小,无法保存请求的字符串,则字符串将被截断,后跟 null 字符,并且返回值等于 nSize 减一。

如果 lpAppName 或 lpKeyName 为 NULL ,并且提供的目标缓冲区太小,无法容纳所有字符串,则最后一个字符串将被截断,后跟两个 null 字符。 在这种情况下,返回值等于 nSize 减 2。

如果找不到 lpFileName 指定的初始化文件或包含无效值,则此函数会将 errorno 设置为“0x2”, (找不到文件) 。 若要检索扩展的错误信息,请调用 GetLastError。
  • C# declaration method
/// <summary>直接获得字符串类型</summary>
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

In order to facilitate subsequent traversal, declare an overloaded function to obtain the binary string

/// <summary>获取字符串类型对应的字节</summary>
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, byte[] retVal, int size, string filePath);

getPrivateProfileSection (through the section, get all the keys and values ​​​​under the section)

  • Primitive:
DWORD GetPrivateProfileSection(
  LPCTSTR lpAppName,
  LPTSTR  lpReturnedString,
  DWORD   nSize,
  LPCTSTR lpFileName
);
  • parameter:
lpAppName:       节名称,注意这个字串是不区分大小写的。
lpReturnedString:存储返回值,指向接收键名称和值的缓冲区的指针。 缓冲区用一个或多个以 null 结尾的字符串填充;最后一个字符串后跟第二个 null 字符。
nSize:           指向的缓冲区的大小(以字符为单位)。最大配置文件节大小为 32,767 个字符。
lpFileName:      初始化文件的名字。如果没有指定完整的路径名,windows就会在Windows目录中搜索文件
  • return value
返回值指定复制到缓冲区的字符数,不包括终止 null 字符。 如果缓冲区不够大,无法包含与命名节关联的所有键名称和值对,则返回值等于 nSize 减 2
  • C# declaration method
 /// <summary>获取节点下所有键</summary>
 [DllImport("kernel32.dll", EntryPoint = "GetPrivateProfileSection")]
 private static extern uint GetPrivateProfileSection(string lpAppName,sbyte[] lpReturnedString, int nSize, string lpFileName);

getPrivateProfileSectionNames (get all section names in the file)

  • Primitive:
DWORD GetPrivateProfileSectionNames(
  LPTSTR  lpszReturnBuffer,
  DWORD   nSize,
  LPCTSTR lpFileName
);
  • parameter:
lpszReturnBuffer:存储返回值,指向接收与命名文件关联的节名称的缓冲区的指针。 缓冲区用一个或多个 以 null 结尾的字符串填充;最后一个字符串后跟第二个 null 字符。
nSize:           指向的缓冲区的大小(以字符为单位)。
lpFileName:      初始化文件的名字。如果没有指定完整的路径名,windows就会在Windows目录中搜索文件
  • return value
返回值指定复制到指定缓冲区的字符数,不包括终止 null 字符。 如果缓冲区不够大,无法包含与指定初始化文件关联的所有节名称,则返回值等于 nSize 减 2 指定的大小。
  • C# declaration method
 /// <summary>获取所有节点名称</summary>
[DllImport("kernel32.dll",EntryPoint = "GetPrivateProfileSectionNames")]
private static extern uint GetPrivateProfileSectionNames(sbyte[] lpszReturnBuffer, uint nSize, string lpFileName);

writePrivateProfileStringA (pass section, key, write string value.)

  • Primitive:
BOOL WritePrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpString,
LPCTSTR lpFileName
);
  • parameter:
lpAppName: 节名称。注意这个字串是不区分大小写的
lpKeyName: 键名称。这个支持不区分大小写
lpString:  要写入文件的 以 null 结尾的字符串。 如果此参数为 NULL,则删除 lpKeyName 参数指向的键。
lpFileName:初始化文件的名称。如果文件是使用 Unicode 字符创建的,函数会将 Unicode 字符写入文件。 否则,该函数将写入 ANSI 字符。
  • return value
如果函数成功将字符串复制到初始化文件,则返回值为非零值。
如果函数失败,或者刷新最近访问的初始化文件的缓存版本,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

Other APIs are not commonly used, so they will not be listed here. Students who want to know can move to the official website to check

2. API encapsulation

  • After declaring AIPI, in order to facilitate calling, we need to encapsulate the API.
  • Read class API
 		/// <summary>
        /// 读Int数值
        /// </summary>
        /// <param name="section">节</param>
        /// <param name="name">键</param>
        /// <param name="def">默认值</param>
        /// <returns></returns>
        public int ReadInt(string section, string name, int def)
        {
    
    
            return GetPrivateProfileInt(section, name, def, this.Filepath);
        }
        
        /// <summary>
        /// 读取string字符串
        /// </summary>
        /// <param name="section">节</param>
        /// <param name="name">键</param>
        /// <param name="def">默认值</param>
        /// <returns></returns>
        public string ReadString(string section, string name, string def)
        {
    
    
            StringBuilder vRetSb = new StringBuilder(2048);
            GetPrivateProfileString(section, name, def, vRetSb, 2048, this.Filepath);
            return vRetSb.ToString();
        }

        /// <summary>
        /// 读取double
        /// </summary>
        /// <param name="section">节</param>
        /// <param name="name">键</param>
        /// <param name="def">默认值</param>
        /// <returns></returns>
        public double ReadDouble(string section, string name, double def)
        {
    
    
            StringBuilder vRetSb = new StringBuilder(2048);
            GetPrivateProfileString(section, name, "", vRetSb, 2048, this.Filepath);
            if (vRetSb.Length<1)
            {
    
    
                return def;
            }
            return Convert.ToDouble(vRetSb.ToString());
        }
  • Write class API
 /// <summary>
        /// [扩展]写入String字符串,如果不存在 节-键,则会自动创建
        /// </summary>
        /// <param name="section">节</param>
        /// <param name="name">键</param>
        /// <param name="strVal">写入值</param>
        public void WriteString(string section, string name, string strVal)
        {
    
    
            WritePrivateProfileString(section, name, strVal, this.Filepath);
        }

  • Delete class API
 /// <summary>
        /// 删除指定字段
        /// </summary>
        /// <param name="sectionName">段落</param>
        /// <param name="keyName">键</param>
        public void iniDelete(string sectionName, string keyName)
        {
    
    
            WritePrivateProfileString(sectionName, keyName, null, this.Filepath);
        }
        /// <summary>
        /// 删除字段重载
        /// </summary>
        /// <param name="sectionName">段落</param>
        public void iniDelete(string sectionName)
        {
    
    
            WritePrivateProfileString(sectionName, null, null, this.Filepath);
        }
        /// <summary>
        /// 删除ini文件下所有段落
        /// </summary>
        public void ClearAllSection()
        {
    
    
            WriteString(null, null, null);
        }
        /// <summary>
        /// 删除ini文件下personal段落下的所有键
        /// </summary>
        /// <param name="Section"></param>
        public void ClearSection(string Section)
        {
    
    
            WriteString(Section, null, null);
        }

3. Results display

  • Due to the limited space, the call result will not be displayed.

Three, traverse the INI

1. Usage scenarios

Some people may ask, since the traversal function is used, why not use other text such as xml? But in my scene here, I feel that it is more appropriate to use INI files.
Use the name of the INI configuration library to dynamically inject functions into the software through reflection. Of course, the advantage of INI is that it is fast.
insert image description here
The above INI file is to record the name of the library and whether to enable this function. When the software is started, it first traverses the INI file, extracts all dlls, decompiles the dll name to obtain its type, and creates objects dynamically.

2. Implementation method

Traversing the INI has actually provided a dedicated API,

  1. Use the getPrivateProfileSectionNames function to retrieve the names of all sections in the initialization file.
  2. Use getPrivateProfileSection to get all the keys and values ​​below the section.

I used another method:

  1. Use GetPrivateProfileString(null, null, “”, allSectionByte, 4096, this.Filepath) to get all nodes in the file.
  2. A byte[] array needs to be passed in to accept the superimposed binary of the traversal result. After using Encoding.GetEncoding() or the GetString() method under the ASCIIEncoding class to convert it into a string, '\0' is used as default between each node. Separator, if we use or print directly, we can only get the first node, because "\0" is the end character.
  3. Use the String.Replace() function to replace "\0" with "|".
  4. Use the String.Substring() function to split the replaced string into an array.
/// <summary>
        /// 读取所有段落名,写死读4096个大小字节,如果段落加起来字符过长会读不完整
        /// </summary>
        /// <returns>返回字符串数组,所有Section</returns>
        public string[] GetAllSections()
        {
    
    
            byte[] allSectionByte = new byte[4096];
            int length = GetPrivateProfileString(null, null, "", allSectionByte, 1024, this.Filepath);
            //int bufLen = GetPrivateProfileString(section, key, Default, Buffer, Buffer.GetUpperBound(0), INI_Path);
            //返回的是所有段落名称拼接起来,以“\0”为分割符
            string allSectionStr = Encoding.GetEncoding(Encoding.ASCII.CodePage).GetString(allSectionByte, 0, length);
            //用'|'代替‘\0’作为分隔符
            string allSectionStrEx = allSectionStr.Replace('\0', '|');
            //去除追后一个分割符"|",不然会有一个空数据
            allSectionStrEx = allSectionStrEx.Substring(0, length - 1);
            //以'|'为分隔符分割allSectionStrEx到字符串数组
            string[] allSections = new string[length];
            char[] separator = {
    
     '|' };
            allSections = allSectionStrEx.Split(separator);
            return allSections;
        }

  1. Similarly, you can use GetPrivateProfileString(section, null, “”, allSectionByte, 4096, this.Filepath); to get all keys of this node.
         /// <summary>
        /// 获取段落下的所有键,写死读4096个大小字节,如果段落加起来字符过长会读不完整
        /// </summary>
        /// <param name="section">段落名</param>
        /// <returns>返回字符串数组,所有Key</returns>
        public string[] GetAllKeys(string section)
        {
    
    
            byte[] allSectionByte = new byte[4096];
            int length = GetPrivateProfileString(section, null, "", allSectionByte, 4096, this.Filepath);
            string allSectionStr = Encoding.GetEncoding(Encoding.ASCII.CodePage).GetString(allSectionByte, 0, length);
            //用'|'代替‘\0’作为分隔符
            string allSectionStrEx = allSectionStr.Replace('\0', '|');
            //去除追后一个分割符"|",不然会有一个空数据
            allSectionStrEx = allSectionStrEx.Substring(0, length - 1);
            //以'|'为分隔符分割allSectionStrEx到字符串数组
            string[] allSections = new string[length];
            char[] separator = {
    
     '|' };
            allSections = allSectionStrEx.Split(separator);
            return allSections;
        }
  1. Store the result in a two-dimensional dictionary.
 /// <summary>
        /// 遍历INI文件,返回二维字典所有内容
        /// </summary>
        public Dictionary<string, Dictionary<string, string>> TraverseIni()
        {
    
    
            //二维字典
            Dictionary<string, Dictionary<string, string>> ConfPlugInfo = new Dictionary<string, Dictionary<string, string>>();
            //读取配置算子文件中所有算子段落
            string[] sections = GetAllSections();
            int sectionsLength = sections.Length;
            //遍历所有段落
            for (int i = 0; i < sectionsLength; i++)
            {
    
    
                string section = sections[i];
                //获取该段落的所有键
                string[] keys = GetAllKeys(section);
                int keysLength = keys.Length;
                //一维存键值对
                Dictionary<string, string> keyValue = new Dictionary<string, string>();
                //遍历所有的键,提取其值
                for (int j = 0; j < keysLength; j++)
                {
    
    
                    string key = keys[j];
                    string value = ReadString(section, key, "");
                    keyValue.Add(key, value);
                }
                ConfPlugInfo.Add(section, keyValue);
            }
            return ConfPlugInfo;
        }

3. Results display

insert image description here

Four. Summary

There are many open source libraries for C# to operate INI files, such as IniParser, Nini, SimpleIni, etc. Of course, if you have enough time, it is also very happy to be able to do it yourself. It is not required to make wheels by yourself, but I hope to master the ability to make wheels.

Note: Part of the function analysis of the article refers to online information! If there is any infringement, please contact to delete!
If you reprint this article, you need to indicate the source!
Gu Zipeng: [email protected]

Guess you like

Origin blog.csdn.net/a1062484747/article/details/129829383