C中的reflection支持
我知道这不被支持,但我想知道是否有任何技巧。 任何提示?
总的来说,反思是程序分析某些代码结构的手段。 此分析用于更改代码的有效行为。
反思作为分析一般是非常薄弱的; 通常它只能提供对函数和字段名称的访问。 这个缺点来自语言实现者,实际上不希望在运行时使完整的源代码可用,以及适当的分析例程来从源代码中提取出想要的东西。
另一种方法是通过使用强大的程序分析工具来解决程序分析问题,例如,可以按照编译器的方式精确地parsing源文本。 (通常人们build议滥用编译器本身来做到这一点,但通常这是行不通的,编译器机器想成为一个编译器,很难将其用于其他目的)。
需要的是一个工具:
- parsing语言源文本
- 构build代表程序的每个细节的抽象语法树。 (如果AST保留源代码布局的注释和其他细节(如列号,文字基数值等)
- 构build符号表,显示每个标识符的范围和含义
- 可以从函数中提取控制stream
- 可以从代码中提取数据stream
- 可以为系统构build一个调用图
- 可以确定每个指针指向什么
- 使用上述事实可以构build自定义分析器
- 可以根据这种自定义分析来转换代码(通常通过修改代表parsing代码的AST)
- 可以从修订后的AST中重新生成源文本(包括布局和注释)。
使用这样的机制,可以在需要的任何细节层次上实现分析,然后转换代码以达到运行时reflection所能达到的效果。 有几个主要的好处:
- 分析的详细程度或数量是一个野心的问题(例如,它不受运行时reflection只能做什么的限制)
- 没有任何运行时间开销来实现反映的行为变化
- 涉及的机制可以是通用的,并且可以跨越多种语言应用,而不仅限于特定语言实现提供的内容。
- 这与C / C ++的想法是兼容的,你不支付你不使用的东西。 如果你不需要反思,你不需要这个机器。 而你的语言不需要有内置的弱思考的智力包袱。
请参阅我们的DMS Software Reengineering Toolkit ,了解可用于C,Java和COBOL以及C ++的大部分function的系统。
围绕它的任何技巧? 任何提示?
编译器可能会有select地生成“debugging符号文件”,debugging器可以用它来帮助debugging代码。 链接器也可以生成一个“映射文件”。
技巧/技巧可能是生成并读取这些文件。
基于对如何将reflection添加到C ++应用程序的响应? (堆栈溢出)和C ++被认为是C的“超集”的事实,我会说你倒霉了。
还有一个关于为什么C ++没有reflection(Stack Overflow)的很好的答案。
我需要在C ++项目中的一堆struct
进行reflection。
我用所有这些结构的描述创build了一个xml文件 – 幸运的是这些字段types是原始types。
我使用了一个模板 (不是C ++ template
)来自动生成每个struct
的class
以及setter / getter方法。
在每个class
我使用一个映射关联string名称和类成员(指向成员)。
我并不后悔使用反思,因为它开辟了新的方法来devise我的核心function,而这些function是我无法想象的。
(顺便说一句,这是一个使用原始数据库的程序的外部报告生成器)
所以,我使用代码生成,函数指针和地图来模拟reflection。
你需要从头开始实施。 在C语言中,结构和复合types没有任何运行时信息。 元数据根本不存在于标准中。
- 为C实现reflection会简单得多…因为C是简单的语言。
- 有一些基本的选项可用于alazing程序,如通过调用dlopen / dlsym来检测函数是否存在 – 取决于您的需要。
- 有创build代码的工具,可以使用tcc修改/扩展自己的代码。
- 您可以使用上述工具来创build您自己的代码分析器。
技巧和窍门总是存在。 看看Metaresc图书馆https://github.com/alexanderchuranov/Metaresc
它提供了types声明的接口,也将为types生成元数据。 基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。 开箱即用,您可以序列化/反序列化XML,JSON,XDR,Lisp-like符号,C-init符号。
这是一个简单的例子:
#include <stdio.h> #include <stdlib.h> #include <math.h> #include "metaresc.h" TYPEDEF_STRUCT (point_t, double x, double y ); int main (int argc, char * argv[]) { point_t point = { .x = M_PI, .y = M_E, }; char * str = MR_SAVE_XML (point_t, &point); if (str) { printf ("%s\n", str); free (str); } return (EXIT_SUCCESS); }
这个程序将输出
$ ./point <?xml version="1.0"?> <point> <x>3.1415926535897931</x> <y>2.7182818284590451</y> </point>
图书馆工作正常最新的海湾合作委员会和铛。
我知道以下选项,但都是成本和很多限制:
- 使用
libdl
(#include <dfcln.h>
) - 调用像
objdump
或nm
的工具 - parsing对象文件(使用相应的库)
- 包含parsing器并在编译时生成必要的信息。
- “滥用”链接器来生成符号数组。
我将使用一些unit testing框架作为示例,因为unit testing框架的自动testing发现是reflection非常方便的一个典型示例,也是大多数C的unit testing框架所不具备的。
使用libdl
( #include <dfcln.h>
)(POSIX)
如果你在POSIX环境中,可以使用libdl
来做一点反思。 插件是以这种方式开发的。
使用
#include <dfcln.h>
在您的源代码中,并链接到-ldl
。
然后,您可以访问函数dlopen()
, dlerror()
, dlsym()
和dlclose()
,您可以使用它们在运行时加载和访问/运行共享对象。 但是,它不能让您轻松访问符号表。
这种方法的另一个缺点是,你基本上限制reflection到加载为dynamic库的对象(在运行时通过dlopen()
加载的共享对象)。
运行nm
或objdump
您可以运行nm
或objdump
来显示符号表并parsing输出。 对我来说, nm -P --defined-only -g xyz.o
给出了很好的结果,parsing输出也很简单。 你只会对每一行的第一个单词感兴趣,它们是符号名称,也许是第二个单词,即单元types。
如果你不知道对象的名称,也就是说对象实际上是一个共享对象,那么至less在Linux上你可能会忽略以'_'开始的符号名称。
objdump
, nm
或类似的工具也经常在POSIX环境之外使用。
自己parsing对象文件
你可以自己parsing对象文件。 你可能不想从头开始实现,但使用现有的库。 这是nm
, objdump
甚至libdl
如何实现的。 你可以看看nm
, objdump
和libdl
的源代码以及它们使用的库,以了解它们是如何做的。
涉及parsing器
您可以编写一个parsing器和代码生成器,它在编译时生成必要的reflection信息,并将其存储在目标文件中。 那么你有很大的自由,甚至可以实现原始forms的注释。 这就是像AceUnit这样的unit testing框架。
我发现编写一个涵盖直接C语法的parsing器是相当简单的。 编写一个真正理解C并能处理所有情况的parsing器并不是微不足道的。 所以,这有一些限制,取决于你想要反思的C语法多么奇特。
“滥用”链接器来生成符号数组
你可以把你想要reflection的符号引用到一个特殊的部分,并使用链接器configuration来发出节的边界,这样你就可以用C来访问它们。
我在这里描述了C中的N-dependency injection – 比链接器定义的数组更好的方法? 这是如何工作的。
但要小心,这取决于很多事情,不是很便携。 我只用GCC
/ ld
试过,我知道它不适用于所有的编译器/链接器。 此外,几乎可以保证死代码消除不会检测到你如何调用这个东西,所以如果你使用死代码消除,你将不得不添加所有reflection的符号作为入口点。
陷阱
对于某些机制, 死代码消除可能是一个问题,特别是当您“滥用”链接器来生成符号数组时。 可以通过将reflection的符号作为链接器的入口点来解决,根据符号的数量,这可能既不好也不方便。
结论
结合nm
和libdl
实际上可以给出相当好的结果。 这个组合可以和Java中的JUnit 3.x使用的Reflection的级别几乎一样强大。 给出的reflection级别足以为C实现一个JUnit 3.x风格的unit testing框架,包括通过命名约定进行testing用例发现。
涉及parsing器是更多的工作,并限于你自己编译的对象,但给你最大的权力和自由。 给定的reflection级别足以为C实现JUnit 4.x风格的unit testing框架,包括通过注释进行testing用例发现。 AceUnit是C的一个unit testing框架,完全是这样的。
结合parsing和链接器来生成符号数组可以给出非常好的结果 – 如果你的环境在你的控制之下,你可以确保使用这种方式工作的链接器。
当然,你可以结合所有的方法来缝合零件,直到它们适合你的需求。
只要制作任何types的键控表,树,链表或任何你喜欢pipe理或发现有效的集合。 添加一个键是否string,types/ ID组合或什么都不指定,并将地址指向一个函数或结构。 一个超级天真版本的reflection可以是以下的一个集合:
struct reflectable{ size_t size,id,type; // describes payload char* name; void* payload; }
用一个大的ol开关的情况下,你做每个types或名称或macros附加相同的处理程序。 或者,总是附加函数,这些函数是您的可reflection结构中的任何东西的接收者,它们与处理程序相同,但有更多的调度模型直接位于您的容器上。