c++ - Llamando al código C con datos en memoria de Fortran
memory-management fortran-iso-c-binding (1)
Fortran 2003 introdujo la interoperabilidad C en el idioma Fortran. Esta función de lenguaje hace que sea mucho más fácil escribir fuentes Fortran y C (y por lo tanto C ++) que pueden trabajar juntas de una manera portátil y robusta. A menos que se le impida usar este nivel del idioma por otros motivos, debería utilizar esta característica.
Tiene un problema con la indirección del puntero, ya sea que el puntero al objeto C ++ se esté almacenando en un punto largo o un puntero en largo (el operando de los moldes en doSth_ y teardown_A_ debe tener un * delante de ellos). Depende de los compiladores C ++ y Fortran que esté utilizando, pero es posible que tenga una discrepancia de tamaño entre un C largo, un puntero C y un entero tipo Fortran predeterminado.
Un ejemplo modificado que muestra el enfoque utilizando la función de interoperabilidad C de Fortran 2003 a continuación.
// C++
struct A {
public:
void do_something()
{
// ...
}
private:
// ...
};
// Note no need for trailing underscore.
extern "C" {
// Note pointer to pointer to void.
void init_A(void** ptr_ptr_to_A) {
A* a = new A;
*ptr_ptr_to_A = reinterpret_cast<void*>(a);
}
void doSth(void* ptr_to_A) {
A* a = reinterpret_cast<A*>(ptr_to_A);
a->do_something();
}
void teardown_A(void* ptr_to_A) {
A* a = reinterpret_cast<A*>(ptr_to_A);
delete a;
}
}
! Fortran 2003
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
INTERFACE
SUBROUTINE init_A(ptr_to_A) BIND(C, NAME=''init_A'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
! This argument is a pointer passed by reference.
TYPE(C_PTR), INTENT(OUT) :: ptr_to_A
END SUBROUTINE init_A
SUBROUTINE doSth(ptr_to_A) BIND(C, NAME=''doSth'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
! This argument is a pointer passed by value.
TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
END SUBROUTINE doSth
SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME=''teardown_A'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
! This argument is a pointer passed by value.
TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
END SUBROUTINE teardown_A
END INTERFACE
TYPE(C_PTR) :: ptr_to_A
INTEGER :: i
!****
CALL init_A(ptr_to_A)
DO i = 1, 100
CALL doSth(ptr_to_A)
END DO
CALL teardown_A(ptr_to_A)
END
Tengo un objeto complicado de C ++ que me gustaría usar en mi código Fortran. En general, no hay problema para llamar al código C ++ de Fortran (solo es necesario proporcionar una interfaz adecuada con enlace C, por ejemplo).
Sin embargo, mi problema aquí es que quiero que mis llamadas de Fortran a C ++ operen en lo que yo llamaría un objeto persistente: un objeto de C ++ creado por la primera función de inicio, y operado por otras funciones de C ++.
Para ser más específico, supongamos que tengo el siguiente código C ++
struct A {
public:
void do() { // do something on complicated stuff
private:
... // complicated stuff
};
extern "C" {
void* init_A() {
A* a = new A();
return reinterpret_cast<void*>(a);
}
void doSth(void* ptr_to_A) {
A* a = reinterpret_cast<A*>(ptr_to_A);
a.do();
}
void teardown_A(void* ptr_to_A) {
A* a = reinterpret_cast<A*>(ptr_to_A);
delete a;
}
}
Y el siguiente código fortran (supongamos que es main ()):
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
INTERFACE
TYPE(C_PTR) FUNCTION init_A() BIND(C, NAME=''init_A'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
END FUNCTION init_A
SUBROUTINE doSth(ptr_to_A) BIND(C, NAME=''doSth'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
END SUBROUTINE doSth
SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME=''teardown_A'')
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
IMPLICIT NONE
TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
END SUBROUTINE teardown_A
END INTERFACE
Ahora en mi código real, esto compila, enlaza, y algunas veces funciona, pero a veces no: parece que la memoria asignada en init_A () no está garantizada para ser modificada por el código Fortran.
No pude encontrar nada al respecto en Internet:
- ¿Sabes si hay algún mecanismo estándar para garantizar que la memoria asignada por init_A_ () no se modifique y siga asignándose por el código Fortran?
- ¿Conoces algún otro mecanismo que se adapte a mi problema?
Además, ¿alguien puede explicarme por qué la memoria no se gestiona correctamente? Hasta ahora, pensé que
Fortran le pediría memoria al sistema operativo, C ++ también,
Los segmentos de memoria dados por el SO a Fortan y C ++ no estaban relacionados y se garantiza que no se superponen,
Si se solicitara una nueva memoria, el sistema operativo no permitiría que Fortran usara la memoria C ++ hasta que C ++ la liberara
La memoria C ++ se libera mediante una llamada a teardown_A () o cuando finaliza el programa (es decir, Fortran main)
Editar: actualicé mi código con la respuesta de IanH, pero esto todavía no funciona (segfaults, porciones de memoria son desasignadas al llamar a doSth () desde Fortran
El código original que publiqué es el siguiente (para comentarios referidos a él)
struct A {
public:
void do() { // do something on complicated stuff
private:
... // complicated stuff
};
extern "C" {
void init_A_(long* ptr_to_A) { // ptr_to_A is an output parameter
A* a = new A();
*ptr_to_A = reinterpret_cast<long>(a);
}
void doSth_(long* ptr_to_A) {
A* a = reinterpret_cast<A*>(*ptr_to_A);
a.do();
}
void teardown_A_(long* ptr_to_A) {
A* a = reinterpret_cast<A*>(*ptr_to_A);
delete a;
}
}
Y el código Fortran:
integer :: ptr_to_A
call init_A(ptr_to_A)
do i=1,10000
call doSth(ptr_to_A)
enddo
call teardown_A(ptr_to_A)