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

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

Juste une remarque pour Java.

Ce qu'il faut voir si tu parviens à produire du bytecode c'est que tu vises la JVM et non Java. Et que, du coup, tu vises beaucoup plus de langages que Java (Scala, Groovy, Ceylon, Javascript et en tirant par les cheveux : Python, Ruby).

C'est pas dit du tout que ce soit intéressant pour ton domaine d'application, mais ça reste une remarque à prendre en considération je pense.

+0 -0

@Bibibye
Inutile de passer par un typedef vers void. Une déclaration anticipée fera tout aussi bien le boulot, si ce n'est mieux => pas besoin de cast à la C (à bannir en C++).

Par contre, la couche d'interfaçage n'est que partielle. Il faut gérer les erreurs de type exception qui peuvent remonter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Foo.hpp
#ifndef FOO_HPP
#define FOO_HPP
namespace bar {
    struct Foo {
        Foo();
        ~Foo();
        void hello() const;
        set_x(int x);
    private:
        ...
    };
} // bar namespaces
#endif


// Foo.h
#ifndef FOO_WRAP_H
#define FOO_WRAP_H

#ifdef __cplusplus
extern "C" {

    namespace bar {
        struct Foo;
    } // bar namespaces
    typedef bar::Foo bar_Foo;
#else
    typedef struct bar_Foo bar_Foo;
#endif

    bar_Foo *barfoo_create(void);
    void barfoo_hello(bar_Foo const* foo);
    void barfoo_set_x(bar_Foo *foo, int x);
    void barfoo_release(bar_Foo *foo);

#ifdef __cplusplus
}
#endif

#endif

// Foo.capi.cpp
#include "Foo.h"
#include "Foo.hpp"
#include "error_reporting.hpp"

bar_Foo* barfoo_create() {
    try {
        return new bar::Foo();
    } catch (...) {
        bar::report_error("while creating a bar::Foo", __FILE__, __LINE__);
    }
    return 0;
}

int barfoo_hello(bar_Foo const* foo) {
    try {
        assert(foo);
        foo->hello();
        return EXIT_SUCCESS;
    } catch (...) {
        return bar::report_error("while bar::Foo::hello", __FILE__, __LINE__);
    }
}

void barfoo_release(bar_Foo * foo) {
    delete foo;
}

// error_reporting.cpp
#include "error_reporting.hpp"
#include <stdexcept>
int bar::report_error(char const* ctxt, char const* file, std::size_t line) {
    try {
        throw;
    } catch (bar::exception const& e) {
        return do_report_error(ctxt, e.what(), e.file(), e.line());
    } catch (std::exception const& e) {
        return do_report_error(ctxt, e.what(), file, line);
    } catch (...) {
        return do_report_error(ctxt, "Unexpected error", file, line);
    }
}

int bar::do_report_error(char const* ctxt, char const* whar, char const* file, std::size_t line) {
    // Et là, utilisation d'un handler réglable par l'utilisateur
    // cf p.ex. GDAL qui fournit une telle possibilité
}

Enfin, la plus grosse difficulté sera liée à certaines choses comme les std::string que l'on n'a pas nécessairement envie de dupliquer dans tous les sens vu que cela n'est pas efficace.

EDIT: le dispatching d'erreur ne permet pas de filtrer simplement les erreurs de logique pour générer des cores -> code retiré.

Juste une remarque pour Java.

Ce qu'il faut voir si tu parviens à produire du bytecode c'est que tu vises la JVM et non Java. Et que, du coup, tu vises beaucoup plus de langages que Java (Scala, Groovy, Ceylon, Javascript et en tirant par les cheveux : Python, Ruby).

Comment on pourrais faire ça ? J'ai juste trouvé LLJVM, qui compile de l'IR LLVM en bytecode JVM, mais le projet a l'air un peu mort. Et en plus il faut lier les bibliothèques à la bibliothèque standard C++, la JVM sait faire ça toute seule ?

C'est pas dit du tout que ce soit intéressant pour ton domaine d'application, mais ça reste une remarque à prendre en considération je pense.

Javier

Pas de problème, j'espère bien que ce fil sera utile à d'autres personnes que moi !

@imghs : merci beaucoup pour cet exemple !

De toute manière, je reviendrai poster mon code une fois que je l'aurai commencé, pour profiter de tous vos bon conseils !

+0 -0

Bon, j'ai commencé à écrire mes fichiers .hpp. Déjà 400 lignes de code et 400 lignes de commentaires, et toujours rien d'implémenté ! Mais j'ai appris plein de trucs sur des design pattern funky ^^

Du coup, dans ma réflexion sur l'organisation du tout, j'ai encore une question pour vous : Comment faire pour associer de manière globale des informations à des classes ? Je m'explique, j'ai une classe BaseFormat dont je vais dériver plein de classes FormatA, FormatB, FormatC, …

J'aimerai associer des extensions à chaque format, afin qu'une fonction get_format(std::string filename) puisse me renvoyer le bon format en fonction de l'extension. En Python, j'aurai fait ça avec un dictionnaire, donc en C++ je cherche du coté des std::map. Si possible, cette map devrait être initialisée lors de la compilation, mais pas des fichiers différents ?

Comment puis-je faire pour avoir ce type de variables globales (dans un namespace quand même ^^) ? Ma tentative naïve :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <map>
#include <string>
class BaseFormat {};
class SimpleFormat : public BaseFormat {};

// Je trouverai bien autre chose que des pointeurs nus je pense.
typedef std::map<std::string, BaseFormat*> format_map_t;

format_map_t all_formats;

all_formats[".simple"] = new SimpleFormat();

Me renvoie des erreurs :

1
2
3
4
5
6
7
main.cpp:11:1: error: C++ requires a type specifier for all declarations
all_formats[".simple"] = new SimpleFormat();
^~~~~~~~~~~
main.cpp:11:13: error: size of array has non-integer type 'const char [8]'
all_formats[".simple"] = new SimpleFormat();
            ^~~~~~~~~
2 errors generated.

Si je comprends bien, ma ligne 11 est parsée comme une déclaration, et pas comme une instruction (j'avais lu des trucs sur le sujet). Mais je ne vois pas trop comment faire autrement.

J'ai aussi vu des trucs sur des Factories, mais j'ai l'impression de sortir l'arme atomique avec une classe de plus par classe de base.

EDIT: Comme quoi, il suffit de poster parfois. Que pensez-vous d'une classe singleton avec une méthode pour enregistrer une association ?

EDIT2: J'arrive à ça:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <map>
#include <string>
class BaseFormat {};
class SimpleFormat : public BaseFormat {};

class FormatMap {
public:
    static void associate(std::string name, BaseFormat format){
        instance.all_formats[name] = &format;
    }
    static BaseFormat* get(std::string name){
        // TODO: vérifier si la clef existe
        return instance.all_formats[name];
    }
private:
    typedef std::map<std::string, BaseFormat*> format_map_t;
    format_map_t all_formats;
    static FormatMap instance;
};

FormatMap FormatMap::instance = FormatMap();

FormatMap::associate(".simple", new SimpleFormat());

J'ai le même problème avec ma ligne 23 (qui est reconnue comme une déclaration). Comment je peut faire pour appeler une fonction lors de la compilation ?

+0 -0

J'avance, j'avance sur ce sujet ! Et j'apprends plein de truc, c'est assez funky !

Ma réussite du soir est la suivante: appeler du code C++ déclaré comme extern "C" depuis fortran, avec le module iso_c_binding. Pour ceux qui pourraient être intéressé, voici le code correspondant :

Le header de la la lib C++ (lib.hpp)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Foo {
public:
    Foo(int a, int b);
    ~Foo(){};

    int bar(int c);
    double baz(double d);
private:
    int a;
    int b;
};

L'implémentation (lib.cpp) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "lib.hpp"
#include <iostream>
using namespace std;


Foo::Foo(int _a, int _b): a(_a), b(_b){}

int Foo::bar(int c){
    cout << "C++ side, calling Foo::bar" << endl;
    return a + c;
}

double Foo::baz(double d){
    cout << "C++ side, calling Foo::bar" << endl;
    return d + b;
}

Le wrapper C (lib.h). Ce dernier est inutile pour le moment, mais j'aimerai bien générer le wrapper fortran automatiquement depuis ce fichier.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifdef __cplusplus
extern "C" {
    class Foo;
    typedef Foo FOO;
#else
    typedef struct FOO FOO;
#endif

FOO* create_foo(int a, int b);

void delete_foo(FOO* foo);

int foo_bar(FOO* foo, int c);

double foo_baz(FOO* foo, double d);

#ifdef __cplusplus
}
#endif

L'implémentation de l'API C (fichier lib_c_api.cpp)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include "lib.h"
#include "lib.hpp"

FOO* create_foo(int a, int b){
    return new Foo(a, b);
}

void delete_foo(FOO* foo){
    delete foo;
}

int foo_bar(FOO* foo, int c){
    return foo->bar(c);
}

double foo_baz(FOO* foo, double d){
    return foo->baz(d);
}

Et enfin l'API fortran (lib.f90) !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
module lib
    use iso_c_binding
    type foo
        type(c_ptr) :: val
    end type
    interface
        function create_foo_c(a, b) bind(C, name="create_foo")
            use iso_c_binding
            implicit none
            type(c_ptr) :: create_foo_c
            integer(c_int), value :: a
            integer(c_int), value :: b
        end function
    end interface

    interface
        subroutine delete_foo_c(foo) bind(C, name="delete_foo")
            use iso_c_binding
            implicit none
            type(c_ptr), value :: foo
        end subroutine
    end interface

    interface
        function foo_bar_c(foo, c) bind(C, name="foo_bar")
            use iso_c_binding
            implicit none
            integer(c_int) :: foo_bar_c
            type(c_ptr), value :: foo
            integer(c_int), value :: c
        end function
    end interface

    interface
        function foo_baz_c(foo, c) bind(C, name="foo_baz")
            use iso_c_binding
            implicit none
            real(c_double) :: foo_baz_c
            type(c_ptr), value :: foo
            real(c_double), value :: c
        end function
    end interface

contains
    function create_foo(a, b)
        implicit none
        type(foo) :: create_foo
        integer, intent(in) :: a, b
        create_foo%val = create_foo_c(a, b)
    end function

    subroutine delete_foo(f)
        implicit none
        type(foo), intent(inout) :: f
        call delete_foo_c(f%val)
    end subroutine

    function foo_bar(f, c)
        implicit none
        integer :: foo_bar
        type(foo), intent(in) :: f
        integer, intent(in) :: c
        foo_bar = foo_bar_c(f%val, c)
    end function

    function foo_baz(f, c)
        implicit none
        double precision :: foo_baz
        type(foo), intent(in) :: f
        double precision, intent(in) :: c
        foo_baz = foo_baz_c(f%val, c)
    end function
end module

Et enfin le programme de test (test.f90) !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
program test
    use lib
    implicit none
    type(foo) :: f

    f = create_foo(3, 4)

    write(*,*) foo_bar(f, 60), " should be ", 63
    write(*,*) foo_baz(f, 10d0), " should be ", 14.0d0

    call delete_foo(f)
end program

On peut à présent compiler tout ça, le code C++ en premier (dans la vrai vie sous forme d'une bibliothèque statique ou partagée, ici sous forme de fichiers objets)

1
2
3
g++ -c lib.cpp
g++ -c lib_c_api.cpp
gfortran lib.f90 test.f90 lib.o lib_c_api.o -lstdc++

On n'oublie pas de lier à la lib standard C++ (-lstdc++ avec gcc, -lc++ avec clang++), et on peut enfin tester ! Le résultat est bon :

1
2
3
4
5
$ ./a.out
C++ side, calling Foo::bar
          63  should be           63
C++ side, calling Foo::bar
   14.000000000000000       should be    14.000000000000000

Comme vous le voyez, le fichier lib.f90 est assez long même pour une interface aussi simple que ça … Je pense que je vais donc essayer de le générer automatiquement. C'est ce que fait Gtk (oui, on peut faire du Gtk depuis fortran ^^). Il y a quelques subtilités, comme l'utilisation de l'attribut value de partout, le fortran utilisant par défaut un passage par référence (constante ou non selon l'attribut intent). Bref, j'espère que ce bout de code pourra aider quelqu'un. Je me demande si je ne devrais pas lancer un blog pour mettre de genre de truc dedans.

+1 -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