Вызов кода C с данными в памяти из Fortran

У меня есть сложный объект C++, который я хотел бы использовать в своем коде Fortran. В общем, нет проблем с вызовом кода C++ из Fortran (просто нужно предоставить подходящий интерфейс, например, с привязкой к C).

Однако моя проблема здесь в том, что я хочу, чтобы мои вызовы Fortran к C++ работали с тем, что я бы назвал постоянным объектом: объектом C++, созданным первой функцией инициализации и управляемым другими функциями C++.

Чтобы быть более конкретным, предположим, что у меня есть следующий код 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;
    }
}

И следующий код на Фортране (предположим, что это 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

Теперь в моем реальном коде это компилируется, связывается и иногда работает, но иногда нет: кажется, что память, выделенная в init_A(), не гарантируется, что код Fortran останется неизменным)

В инете ничего по этому поводу не нашел:

  • Знаете ли вы, существует ли какой-либо стандартный механизм, гарантирующий, что память, выделенная init_A_(), останется неизменной и по-прежнему будет выделена кодом fortran?
  • Знаете ли вы какой-либо другой механизм, который подойдет для моей проблемы?

Кроме того, может ли кто-нибудь объяснить мне, почему память не управляется правильно? До сих пор я думал, что

  • Fortran запросит память у ОС, C++ тоже,

  • Сегменты памяти, предоставленные ОС как Fortan, так и C++, не были связаны и гарантированно не перекрывались,

  • Если запрашивалась новая память, ОС не позволяла Fortran использовать память C++ до тех пор, пока C++ не освобождал ее.

  • Память C++ освобождается либо вызовом teardown_A(), либо когда программа (т.е. основная Fortran) завершается.

Редактировать: я обновил свой код ответом IanH, но он все еще не работает (segfaults, части памяти освобождаются при вызове doSth() из Fortran

Исходный код, который я опубликовал, следующий (для комментариев, относящихся к нему):

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;
    }
}

И код Фортрана:

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)

person Bérenger    schedule 08.07.2014    source источник
comment
Так что этот вопрос касается C++, а не C, верно?   -  person Phonon    schedule 09.07.2014
comment
Ну, за исключением его синтаксиса C++, вопрос все еще актуален в C, поскольку используются только механизмы C (замените new на malloc). Я думаю, что программисты на C чаще имеют дело с низкоуровневыми вещами, и они могли бы знать ответ   -  person Bérenger    schedule 09.07.2014
comment
*ptr_to_A = reinterpret_cast<long>(a); это неопределенное поведение, как и это A* a = reinterpret_cast<A*>(ptr_to_A); Ответ @IanH, вероятно, будет правильным, поскольку он использует правильные типы для вещей на стороне фортрана и не вставляет указатели в ints   -  person Spudd86    schedule 09.07.2014
comment
Я думаю, что это зависит от реализации, а не от неопределенности. Мне тоже не нравятся эти приведения, но я все равно не ожидаю, что интерфейс с Fortran будет приятным...   -  person Bérenger    schedule 09.07.2014
comment
Не предполагайте, что sizeof(long)==sizeof(void*). Какой размер использует fortran для целого числа? Не зная фортрана, я бы предположил, что ваш указатель не помещается внутри переменной ptr_to_A.   -  person user877329    schedule 09.07.2014
comment
Я не думаю, что это так (по крайней мере, в моей конкретной реализации), поскольку, когда я печатаю это долго как на Фортране, так и на С++, он дает мне один и тот же ответ.   -  person Bérenger    schedule 09.07.2014
comment
Но благодаря ответу IanH теперь я использую void*, передаю по значению и использую оператор return в init_A()   -  person Bérenger    schedule 14.07.2014


Ответы (1)


Fortran 2003 представил возможность взаимодействия C с языком Fortran. Эта особенность языка значительно упрощает написание исходного кода на Fortran и C (и, следовательно, на C++), которые могут работать вместе переносимым и надежным способом. Если вам не запрещено использовать этот уровень языка по другим причинам, вам следует очень активно использовать эту функцию.

У вас есть проблема с косвенным указателем - хранится ли указатель на объект C++ в длинном или в указателе на длинный (операнд для приведения в doSth_ и teardown_A_ должен иметь * перед ними). Это зависит от компиляторов C++ и Fortran, которые вы используете, но возможно, что у вас есть несоответствие размера между длинной C, указателем C и целочисленным типом Fortran по умолчанию.

Модифицированный пример, показывающий подход с использованием функции взаимодействия C Fortran 2003 ниже.

// 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
person IanH    schedule 09.07.2014
comment
Хорошо, большое спасибо за ваш подробный ответ. Я реализовал это, и теперь это работает. Для будущих читателей: есть также дополнительная информация об интерфейсе на stackoverflow.com/tags/fortran-iso-c- привязка/информация. Тем не менее, я все еще не уверен, что fortran std 2003 года гарантирует, что моя память C не повреждена с помощью такого объявления интерфейса. Для меня эти интерфейсы были в первую очередь для удобства и переносимости между примитивными типами. - person Bérenger; 09.07.2014
comment
На самом деле это не всегда работает. В более сложном случае, когда код C++ динамически выделяет больше памяти в init_A() (я использую std::vector), я получаю segfault в doSth(). Кто-нибудь знает, как в этой ситуации память управляется программой/ОС? - person Bérenger; 14.07.2014
comment
Я использовал дескрипторы C_PTR для передачи объектов C++ в Fortran в различных ситуациях. Ваша ошибка, вероятно, связана с проблемой в другом месте. Между собой стандарты C(++) и Fortran гарантируют, что описанный выше подход работает. (Вы можете заменить reinterpret_cast на static_cast.) - person IanH; 15.07.2014
comment
Виноват. У меня действительно была ссылка на удаленный объект в моем коде C++. Спасибо за ответ, вы заслуживаете награду - person Bérenger; 15.07.2014