如何用 C++ 实现适配器框架适用于 Linux 和 Windows
这就是我想做的事情:
我正在开发一个支持插件的跨平台 IDE(Linux 和 Windows)。我需要使用类似于 Eclipse 提供的适配器框架来支持可扩展性。有关更多详细信息,请参阅此处,但基本上我需要以下内容:
让 Adaptee
和 Adapted
成为完全不相关的类,它们已经存在,并且我们不允许以任何方式更改。我想创建一个 AdapterManager
类,它有一个方法
template <class Adaptee, class Adapted> Adapted* adapt( Adaptee* object);
,该方法将在给定 Adaptee
实例的情况下创建 Adapted
实例。实例的具体创建方式取决于适配器函数,该函数必须向 AdapterManager 注册。每个新插件应该能够为任意类型提供适配器功能。
以下是我对可能的解决方案以及它不起作用的原因的想法:
C++11 的 RTTI 函数和
type_info
类提供了一个hash_code()
方法,该方法返回程序中每种类型的唯一整数。请参阅此处。因此,AdapterManager 可以简单地包含一个映射,给定 Adaptee 和 Adapter 类的哈希码,返回一个指向适配器函数的函数指针。这使得上面的adapt()
函数的实现变得简单:模板<类Adaptee,类Adapted>已适配* AdapterManager::adapt( 适配者* 对象) { AdapterMapKey mk( typeid(Adapted).hash_code(), typeid(Adaptee).hash_code()); AdapterFunction af = adapterMap.get(mk); if (!af) 返回 nullptr; 返回(改编*)af(对象); }
任何插件都可以通过简单地将附加功能插入到地图中来轻松扩展框架。另请注意,如果存在向
AdapterManager
注册的相应适配器函数,则任何插件都可以尝试将任何类适配到任何其他类,并且无论是谁注册的,都会成功。- 这样做的一个问题是模板和插件(共享对象/DLL)的组合。由于两个插件可以使用相同的参数实例化模板类,因此这可能会导致相应
type_info
结构的两个单独实例以及可能不同的hash_code()
结果,这将打破上述机制。从一个插件注册的适配器函数可能并不总是在另一插件中工作。 - 在Linux中,动态链接器似乎能够根据这个(第 4.2 点)。然而,真正的问题是在 Windows 中,似乎每个 DLL 都会获得自己的模板实例化版本,无论它是否也在其他加载的 DLL 或主可执行文件中定义。与 Linux 中使用的动态链接器相比,动态链接器似乎相当不灵活。
- 我考虑过使用显式模板实例化,这似乎可以减少问题,但仍然无法解决问题,因为两个不同的插件可能仍然以相同的方式实例化相同的模板。
问题:
- 有谁知道在 Windows 中实现此目的的方法吗?如果允许您修改现有的类,这会有帮助吗?
- 您是否知道另一种在 C++ 中实现此功能的方法,同时仍然保留所有所需的属性:不更改现有类、使用模板、支持插件并且是跨平台的?
更新1:
该项目使用 Qt 框架来完成许多事情,包括插件基础设施。 Qt 确实有助于跨平台开发。如果您知道该问题的 Qt 特定解决方案,也很受欢迎。
更新2:
nm的评论让我意识到我只知道理论上的问题,并没有实际测试过。因此,我使用以下定义在 Windows 和 Linux 中进行了一些测试:
template <class T>
class TypeIdTest {
public:
virtual ~TypeIdTest() {};
static int data;
};
template <class T> int TypeIdTest<T>::data;
此类在两个不同的共享库/DLL 中使用 T=int 进行实例化。这两个库都是在运行时显式加载的。以下是我的发现:
在Linux中,一切正常:
- 两个实例化使用相同的vtable。
typeid
返回的对象位于同一地址。- 甚至静态数据成员也是相同的。
- 因此,模板在多个动态加载的共享库中实例化的事实绝对没有区别。链接器似乎只是使用第一个加载的实例化并忽略其余的实例化。
在Windows中,这两个实例化“有些”不同:
- 不同实例的
typeid
返回不同位置的type_info
对象地址。然而,当使用==
进行测试时,这些对象是相等的。相应的哈希码也相等。在 Windows 上,类型之间的相等性似乎是使用类型名称建立的 - 这是有道理的。到目前为止,一切都很好。 - 然而,两个实例的vtables是不同的。我不确定这有多大问题。在我的测试中,我能够使用
dynamic_cast
将TypeIdTest
实例向下转换为跨共享库边界的派生类型。 - 还有一个问题是每个实例化都使用自己的静态字段
data
副本。这可能会导致很多问题,并且基本上不允许模板类中存在静态字段。
总的来说,即使在 Windows 中,情况似乎也没有我想象的那么糟糕,但考虑到模板实例化仍然使用不同的 vtable 和静态存储,我仍然不愿意使用这种方法。有谁知道如何避免这个问题?我没有找到任何解决方案。
Here is what I am trying to do:
I am developing a cross-platform IDE (Linux and Windows) that supports plug-ins. I need to support extensibility using an adapter framework similar to the one that Eclipse provides. See here for more details, but basically I need the following:
Let Adaptee
and Adapted
be completely unrelated classes which already exist and which we are not allowed to change in any way. I want to create an AdapterManager
class which has a method
template <class Adaptee, class Adapted> Adapted* adapt( Adaptee* object);
which will create an instance of Adapted
given an instance of Adaptee
. How exactly the instance is created depends on an adapter function which will have to be registered with AdapterManager
. Each new plug-in should be able to contribute adapter functions for arbitrary types.
Here are my thoughts about a possible solution and why it does not work:
C++11's RTTI functions and the
type_info
class provide ahash_code()
method which returns a unique integer for each type in the program. See here. ThusAdapterManager
could simply contain a map that given the hash codes for the Adaptee and Adapter classes returns a function pointer to the adapter function. This makes the implementation of theadapt()
function above trivial:template <class Adaptee, class Adapted> Adapted* AdapterManager::adapt( Adaptee* object) { AdapterMapKey mk( typeid(Adapted).hash_code(), typeid(Adaptee).hash_code()); AdapterFunction af = adapterMap.get(mk); if (!af) return nullptr; return (Adapted*) af(object); }
Any plug-in can easily extend the framework by simply inserting an additional function into the map. Also note that any plug-in can try to adapt any class to any other class and succeed if there exists a corresponding adapter function registered with
AdapterManager
regardless of who registered it.- A problem with this is the combination of templates and plug-ins (shared objects / DLLs). Since two plug-ins can instantiate a template class with the same parameters, this could potentially lead to two separate instances of the corresponding
type_info
structures and potentially differenthash_code()
results, which will break the mechanism above. Adapter functions registered from one plug-in might not always work in another plug-in. - In Linux, the dynamic linker seems to be able to deal with multiple declarations of types in different shared libraries under some conditions according to this (point 4.2). However the real problem is in Windows, where it seems that each DLL will get its own version of a template instantiation regardless of whether it is also defined in other loaded DLLs or the main executable. The dynamic linker seems quite inflexible compared to the one used in Linux.
- I have considered using explicit template instantiations which seems to reduce the problem, but still does not solve it as two different plug-ins might still instantiate the same template in the same way.
Questions:
- Does anyone know of a way to achieve this in Windows? If you were allowed to modify existing classes, would this help?
- Do you know of another approach to achieve this functionality in C++, while still preserving all the desired properties: no change to existing classes, works with templates, supports plug-ins and is cross-platform?
Update 1:
This project uses the Qt framework for many things including the plug-in infrastructure. Qt really helps with cross platform development. If you know of a Qt specific solution to the problem, that's also welcome.
Update 2:
n.m.'s comment made me realize that I only know about the problem in theory and have not actually tested it. So I did some testing in both Windows and Linux using the following definition:
template <class T>
class TypeIdTest {
public:
virtual ~TypeIdTest() {};
static int data;
};
template <class T> int TypeIdTest<T>::data;
This class is instantiated in two different shared libraries/DLLs with T=int. Both libraries are explicitly loaded at run-time. Here is what I found:
In Linux everything just works:
- The two instantiations used the same vtable.
- The object returned by
typeid
was at the same address. - Even the static data member was the same.
- So the fact that the template was instantiated in multiple dynamically loaded shared libraries made absolutely no difference. The linker seems to simply use the first loaded instantiation and ignore the rest.
In Windows the two instantiations are 'somewhat' distinct:
- The
typeid
for the different instances returnstype_info
objects at different addresses. These objects however are equal when tested with==
. The corresponding hash codes are also equal. It seems like on Windows equality between types is established using the type's name - which makes sense. So far so good. - However the vtables for the two instances were different. I'm not sure how much of a problem this is. In my tests I was able to use
dynamic_cast
to downcast an instance ofTypeIdTest
to a derived type across shared library boundaries. - What's also a problem is that each instantiation used its own copy of the static field
data
. That can cause a lot of problems and basically disallows static fields in template classes.
Overall, it seems that even in Windows things are not as bad as I thought, but I'm still reluctant to use this approach given that template instantiations still use distinct vtables and static storage. Does anyone know how to avoid this problem? I did not find any solution.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为 Boost Extension 正是解决这个问题域:
http:// /boost-extension.redshoelace.com/docs/boost/extension/index.html
<块引用>
(准备将该库提交给 Boost 进行审核)
您尤其会对作者在 此博文:“跨 DLL 边界的资源管理:
我不确定他的解决方案是否真的可靠,但他之前确实有过这个想法。事实上,有一些使用 Boost Extensions 的示例,您可以尝试一下,您可能想使用它。
I think Boost Extension deals with exactly this problem domain:
http://boost-extension.redshoelace.com/docs/boost/extension/index.html
In particular you'd be interested in what the author wrote in this blog post: "Resource Management Across DLL Boundaries:
I'm not sure whether his solution is actually robust, but he sure gave this thought, before. In fact, there are some samples using Boost Extensions that you can give a go, you might want to use it.