Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement an adapter framework in C++ that works in both Linux and Windows

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 a hash_code() method which returns a unique integer for each type in the program. See here. Thus AdapterManager 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 the adapt() 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 different hash_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:

  1. Does anyone know of a way to achieve this in Windows? If you were allowed to modify existing classes, would this help?
  2. 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 returns type_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 of TypeIdTest 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.

like image 474
Dimitar Asenov Avatar asked Feb 14 '12 10:02

Dimitar Asenov


1 Answers

I think Boost Extension deals with exactly this problem domain:

  • http://boost-extension.redshoelace.com/docs/boost/extension/index.html

    (in preparation for this library's submission to Boost for review)

In particular you'd be interested in what the author wrote in this blog post: "Resource Management Across DLL Boundaries:

RTTI does not always function as expected across DLL boundaries. Check out the type_info classes to see how I deal with that.

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.

like image 54
sehe Avatar answered Sep 18 '22 12:09

sehe