ini_parse配置解析功能移植

1 ini_parse移植#

1.1 下载ini解析源码#

源码的github地址https://github.com/benhoyt/ini

1
git clone https://github.com/benhoyt/inih.git

img

1.2 使用ini_parse功能#

img

img

img

可以看到核心就是一个ini_parse函数。用户自定义一个callback函数去解析自己的配置ini。测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include "ini.h"
#include <string.h>
typedef struct _SAMPLE_INI_CFG_S {
char name[100];
int bus_id;
int age;
char name2[100];
int bus_id2;
int age2;
} SAMPLE_INI_CFG_S;
static int parse_handler(void *user, const char *section, const char *name, const char *value) {
SAMPLE_INI_CFG_S *cfg = (SAMPLE_INI_CFG_S *)user;
if (strcmp(section, "person1") == 0) {
if (strcmp(name, "name") == 0) {
strcpy(cfg->name, value);
} else if (strcmp(name, "bus_id") == 0) {
cfg->bus_id = atoi(value);
} else if (strcmp(name, "age") == 0) {
cfg->age = atoi(value);
} else {
/* unknown section/name */
}
} else if (strcmp(section, "person2") == 0) {
if (strcmp(name, "name") == 0) {
strcpy(cfg->name2, value);
} else if (strcmp(name, "bus_id") == 0) {
cfg->bus_id2 = atoi(value);
} else if (strcmp(name, "age") == 0) {
cfg->age2 = atoi(value);
} else {
/* unknown section/name */
}
} else {
/* unknown section/name */
}
return 1;
}
int main(int argc, char **argv) {
SAMPLE_INI_CFG_S ini_cfg;
int ret = ini_parse("./sensor_cfg.ini", parse_handler, &ini_cfg);
if (ret > 0) {
printf("Parse err in %d line.\n", ret);
return ret;
}
if (ret == 0) {
printf("Parse incomplete, use default cfg ./sensor_cfg.ini\n");
printf("%s, %d, %d\n", ini_cfg.name, ini_cfg.bus_id, ini_cfg.age);
printf("%s, %d, %d\n", ini_cfg.name2, ini_cfg.bus_id2, ini_cfg.age2);
}
return 0;
}

1.2.1 自定义ini文件#

ini配置文件sensor_cfg.ini如下:

img

gcc test.c ini.c。我的callback定义是parse_handler,从ini中解析section,每个section会调用一次callback,解析出所有的section。

运行代码:

img

1.2.2 支持语法检测#

ini_parse还支持语法检测。但ini写的不和语法规范会报错。手工制造ini语法错误,测试结果如下:

img

img

2 minIni移植使用#

MiniINI 是一个用来解析 INI/CFG 配置文件的 C++ 库,主要特点是可移植性、性能和小体积。支持上千种 INI 格式配置,易用简单。

2.1 下载minIni#

GitHub - compuphase/minIni: A small and portable INI file library with read/write support

2.2 特征#

  • minIni 支持读取senction外部的key,因此它支持不使用section的配置文件(但在其他方面与 INI 文件兼容)。
  • 可以使用冒号分隔键和值;冒号等价于等号。也就是说,字符串“Name: Value”和“Name=Value”具有相同的含义。
  • minIni 不需要标准 C/C++ 库中的 文件 I/O 函数,且允许通过宏配置要选择文件 I/O 接口。
  • 哈希字符 (“#”) 是分号开始注释的替代方法。允许尾随注释(即在一行上的键/值对后面)。
  • key名称和val周围的前导和尾随空格将被忽略。
  • 当写入包含注释字符(“;”或“#”)的值时,该值将自动放在双引号之间;读取值时,将删除这些引号。当设置中出现双引号本身时,这些字符将被转义。
  • 支持section和key枚举。
  • 您可以选择设置 minIni 将使用的行终止符(对于文本文件)。(这是编译时设置,而不是运行时设置)。
  • 由于写入速度远低于闪存(SD/MMC 卡、U 盘)中的读取速度,因此 minIni 以双倍“文件读取”为代价将“文件写入”降至最低。
  • 内存占用是确定性的。没有动态内存分配。

2.3 INI 文件语法#

1
2
3
4
[Network]  #section
hostname=My Computer #key = val
address=dhcp
dns = 192.168.1.1

2.4 minIni支持文件系统#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

#define INI_FILETYPE FILE*
#define ini_openread(filename,file) ((*(file) = fopen((filename),"r")) != NULL)
#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"w")) != NULL)
#define ini_close(file) (fclose(*(file)) == 0)
#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL)
#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0)
#define ini_rename(source,dest) (rename((source), (dest)) == 0)
#define ini_remove(filename) (remove(filename) == 0)

#define INI_FILEPOS fpos_t
#define ini_tell(file,pos) (fgetpos(*(file), (pos)) == 0)
#define ini_seek(file,pos) (fsetpos(*(file), (pos)) == 0)

2.5 minIni的API介绍#

image-20240602144015881

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "minIni.h"
#define sizearray(a) (sizeof(a) / sizeof((a)[0]))
const char inifile[] = "example.ini";
int main(void){
char str[100];
char section[50];
long n;

n = ini_gets("Network", "address", "dummy", str, sizearray(str), inifile);
if (n >= 0) printf("Network/address=%s", str);

n = ini_getl("Network", "timeout", -1, inifile);
printf("Network/timeout=%ld\n", n);
}

example.ini如下:

1
2
3
4
5
[Network]
hostname=My Computer
address=dhcp
dns=192.168.1.1
timeout=10

运行结果:

1
2
Network/address=dhcp
Network/timeout=10

2.5.1 ini_gets()#

获取字符串类型的值.

1
2
3
4
5
6
7
8
9
10
int   ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
/*
参数 1 是 Section;
参数 2 是 Key;
参数 3 是获取不到值时的默认值;
参数 4 是用于保存目标键值的 Buffer;
参数 5 是 Buffer 的长度;
参数 6 是 INI 文件的路径;
*/

image-20240602144722018

先打开文件,然后用 getkeystring() 找到目标键值,最后拷贝给调用者。

2.5.1.1 getkeystring#

image-20240602150058567

image-20240602145619533

  1. 用 fgets 进行逐行读取,用 strrchr 找到包含 ‘[‘ 和 ‘]’ 的行,然后再用 strncasecmp 找到目标 Section 所在的行。
  2. 继续用 fgets 进行逐行读取,用 strrchr 找到包含 ‘=’ 的行,然后再用 strncasecmp 找到目标 Key 所在的行。
  3. 用 strncpy 将目标 Key 的值拷贝给调用者。

大致就是这3个关键步骤,当然还有很多其他异常处理,语法检测和边界判断的逻辑,这里不做展示。

2.5.2 ini_getl()#

ini_getl() 用于获取整型类型的值,也是间接调用int_gets, 最后将字符串转换成数字。

2.5.3 ini_puts()#

写出参数到ini,保存到ini。

1
2
3
4
5
6
7
8
9
10
11
12
/** ini_puts()
* \param Section the name of the section to write the string in
* \param Key the name of the entry to write, or NULL to erase all keys in the section
* \param Value a pointer to the buffer the string, or NULL to erase the key
* \param Filename the name and full path of the .ini file to write to
*
* \return 1 if successful, otherwise 0
*/
int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename)
//eg:
ini_putl("second", "age", 20, inifile);
n = ini_puts("first", "alt", NULL, inifile);//当val等于NULL表示删除该key

2.5.4 ini_putl()#

ini_putl() 用于写出整型类型的值,也是间接调用int_puts, 将数字转换成字符串,然后保存字符串到ini。

image-20240602171648913

2.5.5 section/key enumeration#

1
2
3
4
5
6
7
8
printf("4. Section/key enumeration, file structure follows\n");

for (s = 0; ini_getsection(s, section, sizearray(section), inifile) > 0; s++) {
printf(" [%s]\n", section);
for (k = 0; ini_getkey(section, k, str, sizearray(str), inifile) > 0; k++) {
printf("\t%s\n", str);
}
}//对section和key进行枚举

2.5.6 section/key存在性检查#

1
2
3
4
5
6
/* section/key presence check */
assert(ini_hassection("first", inifile));//检查是否有first 段
assert(!ini_hassection("fourth", inifile));
assert(ini_haskey("first", "val", inifile));//检查first段是否有val这个key
assert(!ini_haskey("first", "test", inifile));
printf("5. checking presence of sections and keys passed\n");

2.5.7 配置文件阅读打印#

ini_browse用来打印出每个段的每个key的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
int Callback(const char *section, const char *key, const char *value, void *userdata) {
(void)userdata; /* this parameter is not used in this example */
printf(" [%s]\t%s=%s\n", section, key, value);
return 1;
}

/* browsing through the file */
printf("6. browse through all settings, file field list follows\n");
ini_browse(Callback, NULL, inifile);

if (access(filename, F_OK) != 0) {
perror("open %s fail", filename);
}

image-20240602173647362