среда, 28 июля 2010 г.

Как прикрутить вызов фортрановской функции из интерпретатора CINT

CERN ROOT - программа для анализа больших объемов данных, ориентированная прежде всего на использование в научной среде. Программа обладает широкими возможностями по визуализации данных и имеет встроенный интерпретатор языка C++ CINT. Как известно, в физике до сих пор имеет широкое применение язык программирования FORTRAN, на котором написано значительное количество интересных библиотек. Однажды мой коллега-физик попросил меня помочь разобраться в этом вопросе - ему нужно было вызывать функции фортрановской библиотеки MINUIT (разработанной тоже в CERN) непосредственно из интерпретатора CINT. Благодаря поискам решения этой задачи родился этот пример.
Итак, прежде всего создадим функцию на фортране, которая будет вызываться из CINT. Пусть это будет функция, складывающая два числа, также создадим в ней COMMON блок для того, чтобы иметь возможность поэкспериментировать с изменением глобальных переменных:
       INTEGER FUNCTION SUM( A, B )
       DOUBLE PRECISION GL1
       DOUBLE PRECISION GL2
       INTEGER GL3
       COMMON /GL/ GL1( 1, 2 ), GL2, GL3

       INTEGER A, B
       SUM = A + B
       GL1( 1, 1 ) = 0.1
       GL1( 1, 2 ) = 0.2
       GL2 = 10.89
       GL3 = 8
       RETURN
       END
Разместим этот код в файле Sum.f. Теперь нам нужно создать код на C++, который, во-первых, будет вызывать функцию SUM(), а во-вторых, сможет быть вызван из интерпретатора CINT. Пусть вызов SUM() будет организован в виде функтора (т.е. класса с определенным оператором ()) - это упростит семантику вызова из CINT. Разместим наш код в файлах Sum.h и Sum.c. Это содержимое Sum.h:
#include <iostream>
#include "TObject.h"


extern "C" { extern int  sum_( int*, int* ); }

extern struct  fort_struct { double  a[ 2 ][ 1 ]; double  b; int  c; }  gl_;


class  Sum : public  TObject
{
    public:
        int  operator()( int  a, int  b )
        {
            return sum_( &a, &b );
        }

        void  accessGl( void ) const
        {
            printGl();
            gl_.a[ 0 ][ 0 ] = 34.9;
            gl_.a[ 1 ][ 0 ] = 4.1;
            gl_.b = 0.88;
            gl_.c = 70;
            printGl();
        }

    private:
        void  printGl( void ) const
        {
            std::cout << "gl_ = " << gl_.a[ 0 ][ 0 ] << ", " <<
                         gl_.a[ 1 ][ 0 ] << ", " << gl_.b << ", " << gl_.c <<
                         std::endl;
        }

        ClassDef( Sum, 1 )
};

Это содержимое Sum.c:
#include "Sum.h"

ClassImp( Sum )

Сразу отметим наличие определений ClassDef() и ClassImp() - это то, что позволит сделать из нашего Sum объект, с которым интерпретатору CINT будет комфортно работать.
Теперь замечания по использованию фортрановского кода в коде C++:
  1. Фортрановские функции должны связываться с C++ кодом как extern "C", при этом название функции записывается в нижнем регистре (несмотря на то, что в исходном коде оно может быть в верхнем регистре), а в конце добавляется символ _ - именно так компилятор фортрана записывает названия функций в объектном коде. В нашем случае функция SUM() трансформировалась в sum_().
  2. Аргументы передаются в фортрановскую функцию через указатели, поэтому функция двух целочисленных аргументов SUM() превратилась в sum_( int*, int* ).
  3. Глобальные переменные, объявленные в блоке COMMON, также записываются в нижнем регистре с добавлением символа _. Также обратите пристальное внимание на то, что столбцы и строки многомерных массивов в C++ и фортране меняются местами, так, переменная GL1( 1, 2 ) стала double a[ 2 ][ 1 ] в коде C++.
Исходный код готов, теперь нам нужно создать из него подключаемую библиотеку, ниже приводим содержимое Makefile:
all:
 rootcint -f dict_sum.cxx -c Sum.h
 g++ -fPIC -I$(ROOTSYS)/include/root -c dict_sum.cxx Sum.c
 g77 -fPIC -c Sum.f -o Sum_f.o
 g++ -shared -o Sum.so dict_sum.o Sum.o Sum_f.o

clean:
 rm *.o *.so dict_sum.cxx dict_sum.h
Обратите внимание на использование rootcint. Эта утилита создает вспомогательный код, необходимый для использования Sum в CINT, название файла dict_sum.cxx выбрано произвольно. Фортрановский код компилируется с помощью компилятора g77, но ничто не мешает заменить его на gfortran, если это необходимо. После запуска make создается объект Sum.so, который мы будем загружать в CINT, а затем использовать определенные в нем объекты. Пример сессии ROOT:
root [0] .L Sum.so
root [1] Sum s
root [2] int res = s(2,4)
root [3] .p res
(int)6
root [4] s.accessGl()
gl_ = 0.1, 0.2, 10.89, 8
gl_ = 34.9, 4.1, 0.88, 70
root [5]

Комментариев нет:

Отправить комментарий