2009-11-21 6 views
30

Je suis en train de concevoir une API pour une bibliothèque C++ qui sera distribuée dans un objet DLL/partagé. La bibliothèque contient des classes polymoriques avec des fonctions virtuelles. Je crains que si j'expose ces fonctions virtuelles sur l'API DLL, je me suis coupé de la possibilité d'étendre les mêmes classes avec plus de fonctions virtuelles sans rompre la compatibilité binaire avec les applications construites pour la version précédente de la bibliothèque. Une option consisterait à utiliser l'idiome PImpl pour masquer toutes les classes ayant des fonctions virtuelles, mais qui semblent également avoir ses limites: les applications perdent ainsi la possibilité de sous-classer les classes de la bibliothèque et de surcharger les méthodes virtuelles.Comment concevoir une API C++ pour une extensibilité compatible binaire

Comment concevez-vous une classe API qui peut être sous-classée dans une application, sans perdre la possibilité d'étendre l'API avec des méthodes virtuelles (non abstraites) dans une nouvelle version de la DLL tout en restant binaire rétrograde? Mise à jour: les plates-formes cibles de la bibliothèque sont windows/msvc et linux/gcc.

Répondre

29

Il ya plusieurs mois, j'ai écrit un article intitulé "Compatibilité binaire des bibliothèques partagées implémentées en C++ sur les systèmes GNU/Linux" [pdf]. Bien que les concepts soient similaires sur le système Windows, je suis sûr qu'ils ne sont pas exactement les mêmes. Mais après avoir lu l'article, vous pouvez avoir une idée de ce qui se passe au niveau binaire C++ qui a quelque chose à voir avec la compatibilité. Par ailleurs, l'interface binaire d'application GCC est résumée dans un document standard «Itanium ABI», ce qui vous permet d'avoir une base formelle pour une norme de codage que vous choisissez. Juste pour un exemple rapide: dans GCC, vous pouvez étendre une classe avec plus de fonctions virtuelles, si aucune autre classe ne l'hérite. Lisez l'article pour un meilleur ensemble de règles.

Mais de toute façon, les règles sont parfois trop complexes à comprendre. Vous pourriez donc être intéressé par un outil qui vérifie la compatibilité de deux versions: abi-compliance-checker pour Linux.

+0

L'hôte du fichier PDF que vous avez posté semble avoir été fait. Pourriez-vous le rediffuser, s'il vous plaît? –

+0

@ MichałGórny il semble être de retour sur, mais je l'ai réhosté [ici] (http://static.coldattic.info/restricted/science/syrcose09/cppbincomp.pdf) juste au cas où. –

1

Le compat binaire C++ est généralement difficile, même sans héritage. Regardez GCC par exemple. Au cours des 10 dernières années, je ne suis pas certain du nombre de changements d'ABI qu'ils ont subis. Alors MSVC a un ensemble différent de conventions, donc la liaison avec GCC et vice versa ne peut pas être faite ... Si vous comparez cela au monde C, l'interopérabilité du compilateur vous semble un peu meilleure.

Si vous êtes sur Windows, vous devriez regarder COM. Lorsque vous introduisez de nouvelles fonctionnalités, vous pouvez ajouter des interfaces. Ensuite, les appelants peuvent QueryInterface() pour le nouveau pour exposer cette nouvelle fonctionnalité, et même si vous finissez par changer beaucoup de choses, vous pouvez soit laisser l'ancienne implémentation là ou vous pouvez écrire des shims pour les anciennes interfaces.

+4

"Au cours des 10 dernières années, je ne suis pas certain du nombre de changements d'ABI qu'ils ont subis". Laissez-moi vous dire combien. ** ONE. ** ABI actuel est formalisé et décrit dans un document standard. –

+1

Je sais qu'il y a eu une rupture majeure entre 2.95 et 3.0 (ce qui a été un problème sérieux sur BeOS et Haiku), mais je me souviens d'une autre rupture assez importante entre 3.2 et 3.3 (qui a causé quelques problèmes sur Gentoo) . Est-ce incorrect? – greyfade

+1

Oh, je pensais que 3.0 avait plus de 10 ans. Oui, deux. Un en Juin 2001, avec la sortie de 3.0. Depuis lors, ils ont travaillé pour produire un bon design de longue durée ABI et l'ont adopté avec la version 3.2 en août 2002. Il y a sept ans, c'était le dernier. –

11

Il y a un article intéressant sur la base de connaissances KDE qui décrit et les Don'ts de la faire quand visant à la compatibilité binaire lors de l'écriture d'une bibliothèque: Policies/Binary Compatibility Issues With C++

1

Je pense que vous comprenez mal le problème de la sous-classement.

Voici votre Pimpl:

// .h 
class Derived 
{ 
public: 
    virtual void test1(); 
    virtual void test2(); 
private; 
    Impl* m_impl; 
}; 

// .cpp 
struct Impl: public Base 
{ 
    virtual void test1(); // override Base::test1() 
    virtual void test2(); // override Base::test2() 

    // data members 
}; 

void Derived::test1() { m_impl->test1(); } 
void Derived::test2() { m_impl->test2(); } 

Voir?Pas de problème pour surcharger les méthodes virtuelles de Base, il vous suffit de vous assurer de les redéclarer virtual en Derived afin que ceux qui dérivent de Derived sachent qu'ils peuvent les réécrire aussi (seulement si vous le souhaitez, qui est d'ailleurs un excellent moyen de fournir un final pour ceux qui n'en ont pas), et vous pouvez toujours le redéfinir pour vous-même dans Impl qui peut même appeler la version Base.

Il n'y a pas de problème avec Pimpl là. D'autre part, vous perdez le polymorphisme, ce qui peut être gênant. C'est à vous de décider si vous voulez du polymorphisme ou simplement de la composition.

+0

La classe wrapper de Pimpl doit avoir des méthodes non virtuelles, car dans ce cas, elle est utilisée exactement pour masquer les méthodes virtuelles des classes de bibliothèques. Si des méthodes virtuelles étaient présentes sur l'interface de la bibliothèque, il serait impossible d'étendre l'interface de la bibliothèque dans les nouvelles versions avec plus de méthodes virtuelles tout en conservant la compatibilité binaire. Mais si l'inteface publiée n'est pas virtuelle, comment les clients vont-ils la sous-classer? D'où le post. – shojtsy

+1

D'accord, alors je comprends votre point de vue. Mais ce n'est pas vraiment un problème de Pimpl à ce stade. Plus un problème sur l'utilisation des méthodes 'virtual' dans l'interface. –

+0

"vous devez juste vous assurer de les redéclairer virtuellement dans Derived afin que ceux qui dérivent de Derived puissent les réécrire aussi". Non, les méthodes virtuelles surchargées sont aussi implicitement virtuelles. –

0

Si vous exposez la classe PImpl dans un fichier d'en-tête, vous pouvez en hériter. Vous pouvez toujours conserver la portabilité arrière car les classes externes contiennent un pointeur sur l'objet PImpl. Bien sûr, si le code client de la bibliothèque n'est pas très judicieux, il pourrait mal utiliser cet objet PImpl exposé, et ruiner la compatibilité descendante binaire. Vous pouvez ajouter des notes pour avertir l'utilisateur dans le fichier d'en-tête de PImpl.

Questions connexes