在C语言中枚举结构体字段

7
我有几个C语言结构体,我想编写以下三个函数:
get_field_list(...)
get_value_by_name(...)
set_value_by_name(...)

第一个应该返回在结构体中定义的字段列表。第二个和第三个应该通过字段名获取和设置相应的字段。
我正在编写结构体。如果需要,我愿意使用任何宏魔法。如果每个结构体有三个函数三元组,那也可以。但是通用结构更好。函数指针也可以......
基本上,我想要一些关于结构体的基本反射。

Relevent: https://natecraun.net/articles/struct-iteration-through-abuse-of-the-c-preprocessor.html

动机

我正在尝试为使用C语言编写的本地应用程序构建数据访问层(DAL)。我使用SQLite作为数据库。我需要存储各种结构,并能够插入\更新\获取(按键选择)\搜索(按查询选择),并且还需要创建\删除所需的表。

基本上,我想要像Hibernate一样的东西来处理C语言...

目前为止,我最好的想法是使用宏、代码生成工具或脚本来创建我的结构以及元数据,我可以使用它们来动态构建所有我的SQL命令。并且有一个小的“通用”模块来实现我需要的所有基本程序...

欢迎提出解决我的实际问题的不同或更好的想法!


1
C语言没有这个功能。 - Random832
1
@Random832 当然,从C语言的角度来看是没有反射的...但我相信有一种方法可以获得类似的行为。 - AK_
如果您的结构体包含一个字符数组、一个结构体和一个整数,那么get_value_by_name()函数的返回类型是什么?或者您的结构体成员都具有相同的类型吗? - Jerry Jeremiah
1
请参考以下问题,了解有关C语言反射的讨论:https://dev59.com/3HM_5IYBdhLWcg3wgjS2 - Code Different
1
通常情况下,这可以通过使用 offsetof 来访问相对于结构指针的字段,以及使用 x-macros 和 # 字段名称字符串化来生成结构声明和字段数组描述符。虽然不是非常美观,但如果能节省大量手动输入的工作量,那么它就值得一试。 - doynax
显示剩余10条评论
3个回答

6
可以采用您提出的“宏魔法”来完成:
对于每个结构体,创建一个头文件(mystruct-fields.h),内容如下:
FIELD(int,   field1)
FIELD(int*,  field2)
FIELD(char*, string1)

然后,在另一个头文件 (mystruct.h) 中,您可以根据需要包含多次:

#define FIELD(T,N) T N;

struct mystruct {
#include "mystruct-fields.h"
};

#undef FIELD

#define FIELD(T,N) { STRINGIFY(T), STRINGIFY(N), offsetof(mystruct, N) },
#define STRINGIFY1(S) #S
#define STRINGIFY(S) STRINGIFY1(S)

struct mystruct_table {
  struct {
    const char *type, *name;
    size_t offset;
  } field[];
} table = {
#include "mystruct-fields.h"
  {NULL, NULL, 0}
};

#undef FIELD

你可以根据需要使用表格来实现反射函数。
通过另一个头文件的包含,可能可以重用上述代码而无需重写,因此你的高层代码可能只需要像这样说:
#define STRUCT_NAME mystruct
#include "reflectable-struct.h"
#undef STRUCT_NAME

坦白说,如果您只是按照常规编写结构,然后手动编写表格,对于后来的人来说会更加容易;这样做更易于阅读,您的集成开发环境将能够自动完成类型,注释中的重要警告应该有助于防止人们在未来破坏它(无论如何,您肯定已经为此进行测试了吧?)

虽然这可能回答了问题,但应该注意到这样的宏是极其不好的实践。特别是因为没有办法避免硬编码所有内容。@AK_ 你需要考虑你实际上要解决什么问题,而不是想出一个不存在匹配问题的解决方案。 - Lundin
@ams,你能看一下我在问题中添加的“动机”部分吗? - AK_
这是一个不同于最初提出的问题,并且不适合在StackOverflow上讨论。基本上,您想要可序列化类型,而C语言不会自动执行该操作。我建议为每个结构编写一个函数。您可以直接将字节转储到数据库字符串中,这对除指针以外的所有内容都有效。对于指针,您需要使用某种索引。 - ams
@ams 我不想要序列化...我需要将C数据类型转换为SQL数据类型...并且我需要它们在SQLite文件中正确地排列,所以我不能只将它们作为字符串/流进行转储... - AK_
我的答案仍然是一样的:我会为每个结构编写一个函数,就像在C++中你会为每个类编写一个方法。 - ams
这里的问题是C语言真正需要结构体内省,这可以通过memberof()运算符非常干净和直接地实现,并且它不会与C范式冲突(事实上,像C11 _Generic表达式这样的新语言添加比memberof()运算符更与C范式冲突 - 我真的很喜欢_Generic,我只是把它作为C语言中可接受或不可接受的例子)。当纯粹主义者听到“反射”时,他们会感到害怕,但我们并不是在谈论反射,而是类型内省,这是完全不同的事情。 - cesss

0
做这件事的方法是将您的结构以数据库格式、XML或文本文件等您熟悉的格式保存。然后使用C程序为每个结构编写.h文件。该.h文件包含结构体、字段枚举和包含每个字段名称的char数组。从那里,您可以构建任何需要的东西。最好使用程序生成器。

0

看一下Metaresc库。它提供了纯C语言的反射机制。类型定义元数据可以从自定义宏语言中获得,该语言替代了标准C类型定义语义,或者从编译器调试信息中获得。示例应用程序在README.md中提供。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接