2012-02-14 11 views
9

Esto es lo que estoy tratando de hacer:Cómo implementar un marco adaptador en C++ que funciona tanto en Linux y Windows

Estoy desarrollando un IDE multiplataforma (Linux y Windows) que soporta plug-ins. Necesito admitir la extensibilidad usando un marco de adaptadores similar al que proporciona Eclipse. Ver here para más detalles, pero básicamente necesito el siguiente:

Vamos Adaptee y Adapted haber clases que no tienen relación que ya existen y que no se nos permite cambiar de ninguna manera. Quiero crear una clase AdapterManager que tiene un método

template <class Adaptee, class Adapted> Adapted* adapt(Adaptee* object); 

que va a crear una instancia de Adapted dada una instancia de Adaptee. La forma exacta en que se crea la instancia depende de una función de adaptador que deberá registrarse con AdapterManager. Cada nuevo plug-in debería poder contribuir funciones de adaptador para tipos arbitrarios.

Éstos son mis pensamientos acerca de una posible solución y por qué no funciona:

de
  • C++ 11 funciones de RTTI y la clase type_info proporcionar un método hash_code() que devuelve un entero único para cada tipo en el programa. Ver here. Por lo tanto, AdapterManager podría contener simplemente un mapa que, dados los códigos hash para el Adaptado y las clases de Adaptador, devuelve un puntero a la función del adaptador. Esto hace que la implementación de la función adapt() anteriormente 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); 
    } 
    

    Cualquier plug-in puede extender fácilmente el marco insertando simplemente una función adicional en el mapa. También tenga en cuenta que cualquier complemento puede intentar adaptar cualquier clase a cualquier otra clase y tener éxito si existe una función de adaptador correspondiente registrada en AdapterManager independientemente de quién la haya registrado.

  • Un problema con esto es la combinación de plantillas y complementos (objetos compartidos/DLL). Dado que dos complementos pueden instanciar una clase de plantilla con los mismos parámetros, esto podría dar lugar a dos instancias separadas de las correspondientes estructuras type_info y resultados hash_code() potencialmente diferentes, lo que romperá el mecanismo anterior. Las funciones del adaptador registradas desde un complemento podrían no funcionar siempre en otro complemento.
  • En Linux, el enlazador dinámico parece ser capaz de tratar múltiples declaraciones de tipos en diferentes bibliotecas compartidas bajo ciertas condiciones de acuerdo con this (punto 4.2). Sin embargo, el problema real está en Windows, donde parece que cada DLL obtendrá su propia versión de instanciación de una plantilla, independientemente de si también está definida en otras DLL cargadas o en el ejecutable principal. El enlazador dinámico parece bastante inflexible en comparación con el utilizado en Linux.
  • He considerado el uso de instancias de plantillas explícitas que parece reducir el problema, pero aún no lo resuelve ya que dos complementos diferentes aún pueden crear la misma plantilla de la misma manera.

Preguntas:

  1. ¿Alguien sabe de una manera de lograr esto en Windows? Si se le permitiera modificar las clases existentes, ¿esto ayudaría?
  2. ¿Conoce otro enfoque para lograr esta funcionalidad en C++, conservando al mismo tiempo todas las propiedades deseadas: no hay cambio en las clases existentes, funciona con plantillas, admite complementos y es multiplataforma?

Actualización 1:
Este proyecto utiliza el marco de Qt para muchas cosas, incluyendo el plug-in de la infraestructura. Qt realmente ayuda con el desarrollo de plataformas cruzadas. Si conoce una solución específica de Qt para el problema, también es bienvenido.

Actualización 2:
comentario de n.m. me hizo comprender que yo sólo sé sobre el problema en la teoría y en realidad no han probado. Así que hice algunas pruebas en Windows y Linux a través de la siguiente definición:

template <class T> 
class TypeIdTest { 
    public: 
     virtual ~TypeIdTest() {}; 
     static int data; 
}; 
template <class T> int TypeIdTest<T>::data; 

Esta clase se instancia en dos diferentes bibliotecas compartidas/DLL con T = int. Ambas bibliotecas se cargan explícitamente en tiempo de ejecución. Aquí es lo que encontré:

En Linux todo funcione:

  • Las dos instancias usan la misma vtable.
  • El objeto devuelto por typeid estaba en la misma dirección.
  • Incluso el miembro de datos estáticos era el mismo.
  • Por lo tanto, el hecho de que la plantilla se instanciara en varias bibliotecas compartidas cargadas dinámicamente no suponía ninguna diferencia. El enlazador parece simplemente usar la primera instanciación cargada e ignorar el resto.

En de Windows los dos instanciaciones son 'algo' distinta:

  • El typeid para los diferentes casos devuelve type_info objetos a diferentes direcciones. Sin embargo, estos objetos son iguales cuando se prueban con ==. Los códigos hash correspondientes también son iguales. Parece que en Windows la igualdad entre tipos se establece usando el nombre del tipo, lo cual tiene sentido. Hasta aquí todo bien.
  • Sin embargo, vtables para las dos instancias fueron diferentes. No estoy seguro de cuán problemático es esto. En mis pruebas, pude usar dynamic_cast para enviar una instancia de TypeIdTest a un tipo derivado a través de los límites de la biblioteca compartida.
  • Lo que también es un problema es que cada instanciación usó su propia copia del campo estático data. Eso puede causar muchos problemas y básicamente no permite campos estáticos en las clases de plantillas.

En general, parece que incluso en Windows las cosas no son tan malo como pensaba, pero sigo siendo reacios a utilizar este enfoque, dado que las instanciaciones plantilla todavía utilizan distintas vtables y almacenamiento estático. ¿Alguien sabe cómo evitar este problema? No encontré ninguna solución.

+1

Creo que estás tratando de resolver un problema de tiempo de ejecución en tiempo de compilación. Creo que en este caso la herencia y las funciones virtuales serán más directas y menos propensas a errores que las plantillas (porque como notaron, hay mucha magia sucediendo en lo que intentan hacer). –

+0

Creo que la esencia del problema es que no hay forma de tener en mis manos un identificador de tipo único (incluso en tiempo de ejecución) que funcionará a través de los límites de los plug-ins. Al menos ese parece ser el caso en Windows. No estoy seguro de que la herencia y las funciones virtuales funcionen porque cada complemento puede contribuir con nuevas clases que también deberían ser arbitrariamente adaptables. ¿Tienes una idea de cómo se podría hacer? –

+0

No puede adaptar tipos arbirtrary en tiempo de compilación desde un complemento porque necesitaría el archivo de encabezado de dicho complemento. Eso frustra el propósito de los complementos, puedes compilar todo al mismo tiempo. Obviamente no se pueden adaptar los tipos arbirtrary en tiempo de ejecución en C++. Por otro lado, puede adaptar tipos arbitrarios que tienen la misma clase base. Si esto no es suficiente, cada plugin debe proporcionar una función de registro que se llame al principio del programa que hará lo necesario para registrar todos los tipos adaptables y sus respectivas funciones de adaptación. –

Respuesta

1

creo ofertas Boost de extensión con exactamente este dominio del problema:

En particular lar que estaría interesado en lo que el autor escribió en this blog post: "Resource Management Across DLL Boundaries:

RTTI no siempre funcionan como se esperaba a través de fronteras DLL. Mira las clases de type_info para ver cómo me ocupo de eso.

No estoy seguro de si su solución es realmente robusta, pero sin duda pensó lo mismo antes. De hecho, hay algunas muestras que usan extensiones de Boost que puede probar, es posible que desee usarlas.

+0

Gracias por esta sugerencia. Eché un vistazo a Boost Extension, pero parece que no proporciona una solución a mi problema. Ya estoy usando Qt (consulte la pregunta actualizada) para administrar mi infraestructura de complementos multiplataforma y funciona muy bien. Por lo que veo, Boost Extension es solo una estructura de administrador de complementos que proporciona una funcionalidad similar al sistema de complementos de Qt. El problema que tengo es muy específico y ni Qt ni Boost Extension dan ningún detalle al respecto. –

Cuestiones relacionadas