C中插件的“最适合”动态类型匹配

我有一个几乎一切都是插件的架构.该体系结构是图形用户界面的基础,其中每个插件由“表面”(即用户可以通过其与插件交互的UI控件)表示.这些表面也是插件.每当添加新插件时,瘦主机会自动确定哪个可用曲面是最佳UI匹配.如何在C中完成动态类型匹配是这个问题的主题.

目前,架构是用C#实现的,很大程度上依赖于反射,正如您将看到的那样.但是,我现在正在为C重新设计整个过程,但由于C没有反射(因为我是C的新手),我需要一些关于如何在没有它的情况下最好地复制此功能的输入.

以下是它在C#(简化和伪)中的完成方式:

所有插件都是Plugin类的后代.

每个表面类型都标有“目标插件类型”,表示它所设计的插件的最具体/最深的后代.

最通用的表面是只处理插件级别的表面(例如,只显示名称标签).这保证了所有插件至少有一个能够在屏幕上表示它们的表面,即使它没有提供任何有用的交互方式.

让我们将这个“基线”表面类型称为PluginSurface.如果 – 为了简洁起见(实际上它是通过类属性完成的) – 我们在表面上使用一个属性来表示它们的目标类型,我们有

PluginSurface.TargetType = typeof(Plugin)

除非添加更具体的表面,否则所有插件都将被赋予此通用表面,无论它们在插件继承层次结构中的何处.

现在,假设我们添加一个名为Father的新插件类型,派生自Plugin:

Father : Plugin

然后我们再添加一些,以创建一个小样本层次结构:

Mother : Plugin
Daughter : Father
Son1 : Mother
Son2 : Mother
Grandson : Son1

此时,所有这些都将通过通用PluginSurface在屏幕上显示.因此,我们创建了更多专门处理新插件功能的附加表面(所有表面都来自PluginSurface):

MotherSurface.TargetType = typeof(Mother)
GrandsonSurface.TargetType = typeof(Grandson)

由此,插件到表面的有效类型匹配应该是:

Father -> PluginSurface
Daughter -> PluginSurface
Mother -> MotherSurface
Son1 -> MotherSurface
Son2 -> MotherSurface
Grandson -> GrandsonSurface

好的,这就是我们期望最终得到的映射.本质上,匹配是作为主机中的静态函数实现的,它接受插件类型并返回最佳拟合表面的实例:

PluginSurface GetBestFitSurface(Type pluginType)

该函数遍历所有可用的表面,并根据提供的pluginType检查其TargetType属性.更具体地说,它检查TargetType IsAssignable是否来自pluginType.它会创建所有正面匹配的列表.

然后,需要将这个候选人名单缩小到最合适的范围.为此,我对可分配性进行了类似的检查,但这次是在所有候选者的TargetTypes之间进行检查,每个候选者检查每个候选者,并且对于每个候选者,我记下了其TargetType可分配给其他候选者的TargetTypes的数量.至.可以将TargetType分配(即转换)为大多数其他TargetType的候选者是最佳拟合.

后来发生的事情是,这个表面成为有问题的插件的包装器,在屏幕上安排自己以反映它“理解”的插件的功能,这取决于适合的紧密度. GrandsonSurface专门用于表示GrandSon,因此它将成为该插件的完整UI.但Son1和Son2“只”获得了MotherSurface,因此他们与Mother没有共同的任何功能都不会被包含在他们的UI表示中(直到更专业的Son接口被制作出来,这将是最好的适合等).请注意,这是预期用途 – 通常,母插件将是抽象的,并且针对它的界面将真正是Son接口,旨在包裹所有母亲的孩子.因此,单个表面为整个类似特征的插件提供UI.

如果没有反射的支持,如何才能在C中做到最好?

我认为挑战是将C模拟抽出C#的Type.IsAssignableFrom(Type),其中两个类型都是动态归属的.我正在寻找dynamic_cast和typeid的方向,但到目前为止我还没有牢牢把握如何去做.任何有关架构,模式或实现细节的提示都将不胜感激.

编辑1:我认为我已经解决了这里的主要问题(请参阅下面我自己的答案,其中包括[非常]粗略的C实现),但可能有更好的模式.随意撕开它.

编辑2:在这里找到的后续问题中提出了一个改进的模型:“Poor Man’s Reflection” (AKA bottom-up reflection) in C++.

最佳答案 一种选择是在Plugin基类中有一个函数:

virtual std::string id() const { return "Plugin"; }

哪些派生类可以覆盖,例如Grandson知道它的基类是Mother,所以可能代码:

override std::string id() const { return "Grandson " + Mother::id(); }

这样,当你在插件上调用id()时,就会得到像“Grandson Mother Plugin”这样的东西.你可以把它扔给std :: stringstream ss(id);,我们一会儿(ss>> plugin_id),并使用plugin_id作为表面容器中的键来寻求….

我不认为你会找到一种灵活使用typeid或dynamic_cast的方法,因为typeid不会告诉你基类(*),而dynamic_cast必须知道它在编译时要测试的类型.我假设您希望能够使用新插件,并更改一些文件或id->曲面映射的其他配置,而无需编辑代码….

(*)如果您准备为非标准版并且它符合您的可移植性要求,则C++: using typeinfo to test class inheritance会记录在运行时获取基类typeinfo的方法.

点赞