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
)
| 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)
| 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 :
| $ ./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.