Ira*_*ter 19
反射通常是程序分析某些代码结构的手段.此分析用于更改代码的有效行为.
反思作为分析通常很弱; 通常它只能提供对函数和字段名称的访问.这个弱点来自语言实现者本质上不希望在运行时提供完整的源代码,以及适当的分析例程来从源代码中提取所需的内容.
另一种方法是通过使用强大的程序分析工具来解决程序分析,例如,可以完全按照编译器的方式解析源文本.(通常人们会建议滥用编译器本身来执行此操作,但这通常不起作用;编译器机制希望成为编译器,并且很难将其弯曲到其他目的).
我们需要的是一种工具:
使用这样的机器,可以在任何需要的细节级别上实现分析,然后转换代码以实现运行时反射将实现的效果.有几个主要的好处:
请参阅我们的DMS软件重新设计工具包,了解可以为C,Java和COBOL执行上述所有操作的系统,以及大部分用于C++的系统.
基于对如何向C++应用程序添加反射的响应?(Stack Overflow)以及C++被认为是C的"超集"的事实,我会说你运气不好.
关于为什么C++没有反射(Stack Overflow)也有一个很好的长篇答案.
struct我需要在 C++ 项目中的一堆 s 中进行反射。
我创建了一个 xml 文件,其中包含所有这些结构的描述 - 幸运的是,字段类型是原始类型。
我使用模板(不是 C++ template)来自动生成class每个模板struct以及 setter/getter 方法。
在每个中class,我都使用映射来关联字符串名称和类成员(指向成员的指针)。
我并不后悔使用反射,因为它开辟了设计核心功能的新方法,如果没有反射,我什至无法想象。
(顺便说一句,它是使用原始数据库的程序的外部报告生成器)
因此,我使用代码生成、函数指针和映射来模拟反射。
我知道以下选项,但所有这些选项都是有代价的并且有很多限制:
libdl(#include <dfcln.h>)objdump调用类似或这样的工具nm下面我将使用一些单元测试框架作为示例,因为单元测试框架的自动测试发现是反射非常方便的典型示例,而这是大多数 C 单元测试框架所缺乏的。
libdl( #include <dfcln.h>) (POSIX)如果您在 POSIX 环境中,可以使用libdl. 插件就是这样开发的。
使用
#include <dfcln.h>
Run Code Online (Sandbox Code Playgroud)
在您的源代码中并链接到-ldl.
然后您可以访问函数dlopen()、 、dlerror(),dlsym()并dlclose()使用它们在运行时加载和访问/运行共享对象。但是,它并不能让您轻松访问符号表。
这种方法的另一个缺点是,您基本上将反射限制为作为动态库加载的对象(通过在运行时加载的共享对象dlopen())。
nm或objdump您可以运行nmorobjdump显示符号表并解析输出。对我来说,nm -P --defined-only -g xyz.o给出了很好的结果,并且解析输出是微不足道的。您只对每行的第一个单词(即符号名称)感兴趣,也许对第二个单词(即节类型)感兴趣。
如果您不以某种静态方式知道对象名称,即该对象实际上是一个共享对象,至少在 Linux 上您可能希望跳过以“_”开头的符号名称。
objdump或nm类似的工具通常也可以在 POSIX 环境之外使用。
您可以自己解析目标文件。您可能不想从头开始实现它,而是使用现有的库。nm, ,objdump甚至是这样libdl实现的。您可以查看和的源代码nm以及它们使用的库,以了解它们是如何做的。objdumplibdl
您可以编写一个解析器和代码生成器,在编译时生成必要的反射信息并将其存储在目标文件中。然后你就有很大的自由,甚至可以实现原始形式的注释。这就是一些单元测试框架(例如AceUnit)所做的事情。
我发现编写一个涵盖直接 C 语法的解析器相当简单。编写一个真正理解 C 并可以处理所有情况的解析器并不是一件容易的事。因此,这有一些限制,具体取决于您想要反思的 C 语法的奇异程度。
您可以将对要反映的符号的引用放在特殊节中,并使用链接器配置来发出节边界,以便您可以在 C 中访问它们。
我在这里描述了C 中的 N 依赖注入 - 比链接器定义的数组更好的方法?这是如何运作的。
但请注意,这取决于很多因素并且不太便携。我只尝试过GCC/ ld,并且我知道它不适用于所有编译器/链接器。另外,几乎可以保证死代码消除不会检测您如何调用这些东西,因此如果您使用死代码消除,则必须添加所有反射符号作为入口点。
对于某些机制,死代码消除可能是一个问题,特别是当您“滥用”链接器来生成符号数组时。可以通过将反射符号作为链接器的入口点来解决这个问题,并且根据符号的数量,这可能既不好也不方便。
结合nm和libdl实际上可以给出相当不错的结果。该组合几乎与 Java 中 JUnit 3.x 使用的反射级别一样强大。给出的反射级别足以实现 C 的 JUnit 3.x 风格的单元测试框架,包括通过命名约定发现测试用例。
涉及解析器需要更多工作,并且仅限于您自己编译的对象,但为您提供了最大的权力和自由。给出的反射级别足以实现 C 的 JUnit 4.x 风格的单元测试框架,包括通过注释发现测试用例。AceUnit是一个 C 单元测试框架,它正是这样做的。
结合解析和链接器来生成符号数组可以给出非常好的结果 - 如果您的环境在您的控制之下,您可以确保以这种方式使用链接器适合您。
当然,您可以结合所有方法将零碎的内容拼接在一起,直到它们满足您的需求。
提示和技巧始终存在。看看Metaresc库https://github.com/alexanderchuranov/Metaresc
它提供了用于类型声明的接口,该接口还将生成该类型的元数据。基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。开箱即用,您可以序列化/反序列化XML,JSON,XDR,类似Lisp的表示法,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);
}
Run Code Online (Sandbox Code Playgroud)
该程序将输出
$ ./point
<?xml version="1.0"?>
<point>
<x>3.1415926535897931</x>
<y>2.7182818284590451</y>
</point>
Run Code Online (Sandbox Code Playgroud)
库对于最新的gcc和clang正常工作。