Création d'une bibliothèque générique en C++

Le problème exposé dans ce sujet a été résolu.

Je réfléchi en ce moment à la réalisation d'une bibliothèque scientifique et j'ai quelques problèmes de conception. J'aimerai l'écrire en C (voir mes raisons plus bas), et en même temps avoir quelque chose de générique. C'est mon premier code C un peu important, et j'ai sans doutes du mal avec sa conception aussi parce que j'ai fait trop de Python/C++ et que le modèle objet m'est devenu familier.

EDIT: Après les conseils de tout un tas de gens, la lib sera écrite en C++, avec une interface C.

Buts

Cette bibliothèque serait une abstraction au dessus de plusieurs formats de fichiers, stockant tous les mêmes informations. L'idée est de proposer une structure de donnée interne générique, et de lire/écrire ces données dans un fichier en supportant plusieurs formats.

Pourquoi le C

J'aimerai écrire ce code sous la forme d'une bibliothèque qui soit utilisable depuis plusieurs langages. Le C me semble donc être un bon candidat pour faire ça ! Les cibles sont :

  • Le C (ça c'est bon)
  • Fortran 90 et plus (via iso_c_binding)
  • Python (interface CPython)
  • Julia (pas de problème avec ccall)
  • Pourquoi pas Java un jour, mais rien n'est sûr.

Pourquoi le C me pose des problèmes

Principalement parce qu'il manque de généricité : je ne voit pas comment faire pour avoir un unique point d'entrée dans ma bibliothèque. Le C++ me parait assez intéressant pour résoudre des problèmes du C, mais en a d'autres :

  • Flux de lecture/écriture réputés être lents, et j'aimerai avoir une bibliothèque efficace.
  • Comment faire pour appeler du code C++ depuis le C ou le Fortran ?

Ce que je veux faire

Avoir un point d'entrée unique quelque soit le format de fichier : L'utilisation devrait être aussi simple que :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
MyFile *file = open("filename.ext");
MyData data;

read(file, &data);

// Change data here

write(file, &data);

close(file);

Seulement, l'implémentation d'une fonction read et write dépends du type de fichier (déterminé dynamiquement à partir de l'extension). Et c'est là qu'est tout le problème …

En gros je pensais avoir une table de hash qui associe les extensions à une structure FILE, cette structure contenant des pointeurs vers les fonctions à utiliser. Mais je ne suis pas sûr que ce soit la meilleur manière de faire ça, ni que ça convienne totalement. Comment feriez-vous pour résoudre ce problème en C ?

+0 -0

Cela ne répond pas à tes questions immédiates, mais je ne peux que t'en recommander l'écoute: http://channel9.msdn.com/events/TechDays/Techdays-2014-the-Netherlands/Modernizing-Legacy-C-Code (a priori une conf similaire a été donnée lors de la CppCon14)

En résumé, l'implémentation de la bibliothèque standard C qui vient avec VC++ est en cours de réécriture en .... C++. Et si. J'avais lu qu'ils auraient de meilleures performances, mais via un post sur dvpz. Donc info à vérifier. EDIT: vérifié: cf les slides 76 à 78 du lien précédent)

Qu'est-ce que cela veut dire pour toi ? Que tu peux écrire les interfaces en C, mais implémenter le coeur en C++. Ainsi, ton type Data peut être un type opaque, à l'image de FILE, mais qui en interne agrège un pointeur vers un type racine (puisque tu sembles vouloir prendre la direction d'un polymorphisme dynamique). Et encore … tu n'as peut-être même pas besoin de recourir à cette sur-couche artificielle.

PS: cherche "extern C" pour l'interfaçage C et C++. Ceci dit, autant faire une lib C++ avec une API d'exploitation en C pour ceux qui se refusent aux unique_ptr<>, ou pour ceux qui n'ont pas le choix (exploitation depuis un autre langage). Ceci dit, pour le binding python, il existe très certainement des voies plus intéressantes que celle des API en C.

Ceci dit, pour le binding python, il existe très certainement des voies plus intéressantes que celle des API en C.

Pour C++ <-> Python il y en a un paquet, ça va de swing à boost::python en passant par l'interface CPython classique, ctype qui est relativement lent mais entièrement en Python (dépend du code à interfacer) jusqu'à ce que j'utiliserai aujourd'hui pour de nouveaux bindings, Cython. Bref en python, tu as le choix.

cherche "extern C" pour l'interfaçage C et C++

Je connaissais =) Mais je me demandais si je pouvais avoir les mêmes fonctionnalités et la même facilité d'utilisation avec ce type de fonctions.

ou pour ceux qui n'ont pas le choix (exploitation depuis un autre langage). Ceci dit, pour le binding python, il existe très certainement des voies plus intéressantes que celle des API en C.

Oui, le binding Python est plus simple depuis le C++, je connaissait Swing, et Kje en a cité plein d'autres. Mais depuis Fortran par exemple, seul le C est dispo, et depuis Julia, le ccall est vraiment pratique à utiliser.

Quel est l'avantage de unique_ptr<> ici ?

(puisque tu sembles vouloir prendre la direction d'un polymorphisme dynamique). Et encore … tu n'as peut-être même pas besoin de recourir à cette sur-couche artificielle.

Dans quel sens ? Je me suis dit que vu que le nombre de formats est connu à la compilation, il devrait être possible d'utiliser une enum pour les répertorier, et un gros switch pour appeler la bonne. Au passage, a part la solution enum/switch (statique, pratique si moins de 10 éléments) et la solution table de hash (dynamique, pratique pour 100 éléments), il y a quoi comme solution pour associer des données à des fonctions ?

+0 -0

J'avais fais des benchs dans le passé. Le switch s’effondrait en faveur du polymorphisme du C++ à partir d'une dizaine d'états si mes souvenirs sont bons. Mais bon, c'est à remesurer pour chaque machine. Dans tous les cas, c'est vraiment se compliquer la vie. Surtout s'il doit y avoir plusieurs switch sur le même type dans le code. Cf le Open Close Principle.

L'avantage d'unique_ptr sur un pointeur nu est … Comment dire ? C'est un peu la révolution du C++ Moderne. Je t'invite vraiment à suivre la conf pour voir ce que cela a pu apporter, même avec une interface finale en C, et même sans exceptions. Après, cela peut aussi servir si tu fournis une interface C++ à ta lib.

je t'invite vraiment à suivre la conf pour voir ce que cela a pu apporter, même avec une interface finale en C, et même sans exceptions.

Pas vu de références à unique_ptr dans la première conf, je regarderai la seconde plus tard.

Je vais continuer à regarder un peu, mais je risque de partir sur une lib en C++, avec des bindings funky :

1
2
3
4
5
Lib C++
   \_ Python & numpy
   \_ Julia
   \_ extern "C"
         \_ Fortran 90

Je galèrerai peut-être un poil plus sur les bindings, mais la lib sera beaucoup plus simple à écrire =).

+0 -0

Si tu souhaites que ta lib soit interfaçAble avec plein de langages différents, mieux vaut prévoir une API C.

En C++, le simple fait de changer de compilateur empêche d'utiliser ta lib de manière dynamique. Par exemple, l'interface C++ de la bibliothèque audio FMODEx n'est utilisable que depuis Visual Studio; avec GCC ça ne marche pas et ça fait plein d'erreurs de link bizarres si on essaie.

Par contre, en interne, +1 pour utiliser le C++ ! Beaucoup font comme ça de toute façon. En général, les fonctions C de l'interface retournent uniquement des handles (des void* ou des int/long) et on accède à tout le reste via ces handles. Du côté bibliothèque, un handle, ça peut soit être un pointeur plus ou moins déguisé, soit un identifiant qui fait référence à un objet dans un pool. Plein de bibliothèques dans plein de domaines fonctionnent comme ça, que ce soit du son, du graphisme, de la crypto, du réseau, des lecteurs/enregistreurs/convertisseurs de format de fichier, etc.

Pour ton problème plus spécifique de comment faire pour choisir le bon format, normalement on se base plutôt sur le contenu des X premiers octets du fichier plutôt que seulement sur l'extension. En général on suppose que ne se baser que sur l'extension est une garantie plutôt peu fiable (ça amrche bien sous windows mais ailleurs beaucoup moins). Beaucoup de formats usuels stockent un identifiant qui indique sans équivoque le format des données qui suivent dans les 4 premiers octets du fichier, parfois 8 ou un peu plus mais très souvent 4. Essaie d'ouvrir des fichiers PDF, word, des sons, des musiques (ou même des vidéos si tu es patient) dans un éditeur de texte et tu verras. Bien souvent ces identifiants sont lisibles en ASCII et en plus d'inqieur le format, indiquent aussi très souvent la version. Par exemple pour PDF, on voit que ça commence toujours par "PDF-x.x" avec "x.x" représentant la version utilisée (1.3, 1.4, 1.5, 1.6, 1.7 ou 1.8 dans ce cas)

+0 -0

Pour ton problème plus spécifique de comment faire pour choisir le bon format, normalement on se base plutôt sur le contenu des X premiers octets du fichier plutôt que seulement sur l'extension.

Dans ce cas ça ne fonctionnera pas, beaucoup de ces formats sont de simples fichiers textes avec juste une mise en force spécifique. On parle de formats créés par des scientifiques (qui ont en général autre chose à faire), dans les années 70-80 (vive les majuscules et les identifiants de 6 lettres). Ce qui explique aussi la profusion (30 de communs, plus de 200 existants). Mais je retiens l'info !

Mon problème est plus sur comment associer des informations à une classe depuis l'extérieur de cette classe (pour savoir quoi instancier).

En C++, le simple fait de changer de compilateur empêche d'utiliser ta lib de manière dynamique.

D'utiliser la lib depuis le C++ ou depuis un binding ? Dans le premier cas cas ce n'est pas très grave, mais si ça empêche d'utiliser depuis le C ou le Fortran ça va me gêner un peu plus. Dans le pire des cas, ça sera en open source, donc il suffirait de recompiler avec le bon compilo, mais ce n'est pas très sympa.

+0 -0

D'utiliser la lib depuis le C++ ou depuis un binding ? Dans le premier cas cas ce n'est pas très grave, mais si ça empêche d'utiliser depuis le C ou le Fortran ça va me gêner un peu plus.

Il faut surtout garder à l'esprit que certains langages ne peuvent pas facilement utiliser une API C++, alors qu'une API C en général si. Par exemple si tu vises Java, tu vas vite être coincé pour créer et manipuler tes objets C++; tu vas devoir faire une surcouche C, car ce qu'on utilise habituellement pour faire des bindings pour Java, JNI ou JNA, ne supportent que le C et les langages dont l'ABI est totalement compatibles avec le C.

En fait c'est une question de point de vue: préfères-tu faire autant de bindings différents que de langages que tu veux supporter, ou bien tu préfères faire une seule API C à laquelle tous les langages capables de faire ce qu'on appelle de la FFI peuvent importer ?

Faire un binding par langage à supporter avec un truc genre JNI pour Java, API python ou boost python, fonctions similaires de la CLR pour le C#, etc. , c'est particulièrement chiant et pas si simple que ça en a l'air, il y a plein de subtilités à la con genre avec le format des chaînes de caractères.

Bon, il y a quand même une question de performance: faire un binding par langage est évidemment plus rapide à l'exécution…

+0 -0

Dans ce cas ça ne fonctionnera pas, beaucoup de ces formats sont de simples fichiers textes avec juste une mise en force spécifique. On parle de formats créés par des scientifiques (qui ont en général autre chose à faire), dans les années 70-80 (vive les majuscules et les identifiants de 6 lettres). Ce qui explique aussi la profusion (30 de communs, plus de 200 existants).

Luthaf

Par curiosité, on peut avoir des références sur ces formats ?

+0 -0

Il faut surtout garder à l'esprit que certains langages ne peuvent pas facilement utiliser une API C++, alors qu'une API C en général si.

C'était pour ça que je partais sur du C à la base.

En fait c'est une question de point de vue: préfères-tu faire autant de bindings différents que de langages que tu veux supporter, ou bien tu préfères faire une seule API C à laquelle tous les langages capables de faire ce qu'on appelle de la FFI peuvent importer ?

Vu que je n'ai que 4 langages principaux à supporter, je pense que je vais les faire à la main, et laisser les gens motivés faire le reste s'ils le veulent.

Par curiosité, on peut avoir des références sur ces formats ?

Ce sont les formats de stockage de trajectoire, où on a des truc variés : - Format texte : - Minimaliste : http://www.wikiwand.com/en/XYZ_file_format - Un peu plus complet : http://www.wwpdb.org/documentation/format33/v3.3.html - Basé sur XML : http://www.wikiwand.com/fr/Chemical_Markup_Language - Spécifique à un logiciel : http://lammps.sandia.gov/doc/read_data.html - Format binaire basé sur une lib externe : http://ambermd.org/netcdf/nctraj.xhtml - Format binaire avec une lib particulière : http://www.ncbi.nlm.nih.gov/pubmed/24258850

Tous les formats texte pouvant être gzippé, car des fichiers de 40-50Go sont communs.

D'ailleurs, je me demandais quelle seraient les performances d'un parseur en mode Flex pour lire les fichiers textes, comparé à une itération sur les lignes du fichier.

+0 -0

Je n'ai pas lu l'ensemble des réponses, mais le mot-clef "swig" n'y apparait pas. Je me permet donc d'ajouter mon grain de sel.

Au boulot, on bosse sur une application écrite en C++ qui est totalement interfacée par du Python. La connexion se fait avec SWIG. Ca marche assez bien. Je n'ai jamais testé vers d'autres langages que Python, mais SWIG permet également d'exporter vers PHP, Java, Perl, Octave, Javascript, R, et d'autres.

Je pense que c'est une solution suffisante pour ne pas se restreindre au C pour le développement.

+0 -0

En effet mais a un petit bémol pret. Dans le monde Python, swig c'est un peu l'interfaçage du pauvre à l'ancienne en ce sens que c'est une solution assez peu efficace et peu élégante vis a vis des autres techniques de bindings qui existent. Son usage est considéré comme désuet globalement et peu de libs récentes l'utilisent, ce sont plus les libs historiques. La solution hype étant Cython en ce moment mais du coup totalement limité au monde python.

Oui, nous l'utilisons clairement pour des raisons historiques. Tout changer serait maintenant une belle galère.

Mais je pense qu'une force de swig est l'aspect multi-langages, surtout vers PHP et Java ce qui permet des solutions commerciales et/ou en lignes assez sympa à moindre coût.

+0 -0

Mais je pense qu'une force de swig est l'aspect multi-langages, surtout vers PHP et Java ce qui permet des solutions commerciales et/ou en lignes assez sympa à moindre coût.

Oui, en effet. Après dans les languages que je vise il y a surtout des vieux de la vieille, sans modèle objet ou avec un modèle peut utilisé : C, Fortran. Mes autres cibles sont surtout Python (donc pourquoi pas Swig), et Julia (pas encore supporté par Swig, a sa propre FFI pour le C++ et le C).

Parmi les langages qui pourraient être supportés un jour, je mettrai par ordre de priorité :

  • Matlab, Octave, Mathematica.
  • Java
  • Perl

Et sans doutes jamais vers PHP, Javascript ou Go, tout simplement parce que ces langages ne sont jamais utilisé en contexte de calcul intensif. Donc Swig peut être intéressant, mais dans mon cas je ne crois pas que ça vaille le coup d'explorer cette voie.

+0 -0

Il faut surtout garder à l'esprit que certains langages ne peuvent pas facilement utiliser une API C++, alors qu'une API C en général si.

C'était pour ça que je partais sur du C à la base.

Luthaf

comme ça a été dit, il ne faut pas mélanger API et implémentation. Tu peux avoir une API C avec une implémentation C++.

1
2
3
4
5
6
7
extern "C" { // j'omets volontairement les #ifdef __cplusplus

struct T; // pas sûr de cette syntaxe

T* t_create();

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct T
{
    T() {}
    ~T() {}
};

T* t_create()
{
    return new T();
}

comme ça a été dit, il ne faut pas mélanger API et implémentation. Tu peux avoir une API C avec une implémentation C++.

C'est ce que je pensais faire finalement. Tu parle de #ifdef __cplusplus, à quoi servent-ils ici ?

Je pensais avoir un fichier mylib.h, qui ne contienne que les déclaration des fonctions externes, fonctions dont les implémentations sont dispersées sur plusieurs fichiers .cpp.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte