Так уж вышло, что у меня возникла необходимость прикрутить к дотнетовскому фронт-энду математический модуль, писанный на Фортране. Задача для меня не особо тривиальная, потому как раньше с Фортраном я не сталкивался.
В целом, задача моя состояла в следующем: взять код нескольких готовых функций на Фортране, несколько написать самому, скомпилировать получившееся в DLL, нежно обернуть библиотеку управляемым кодом, и вызвать из своей гуишки. Будем исходить из того, что получить необходимо 32-разрядное приложение.
Попытаюсь проследить весь процесс от начала и до конца :)
Писать/править код можно, в принципе, и в блокноте, а можно использовать неплохие бесплатные текстовые редакторы, подсвечивающие фортрановский синтаксис: к примеру, PSPad или Crimson Editor (первый мне понравился больше). Хотя, честно говоря, до UltraEdit’a им далековато. Он не бесплатен, и подсветка синтаксиса для Фортрана по умолчанию вместе с ним не идет – правила подсветки можно скачать с официального сайта программы. Но удобство редактора и набор возможностей позволяют закрыть глаза на эти мелочи.
Следующий шаг – выбор компилятора. Первым делом я ухватился за опенсорсовый вариант – G95. Мал, шустер и прост. Впрочем, на этом этапе у меня к компилятору было только одно требование – компилировать.
Итак, забрасываю все исходники в одну папку, решаю назвать свою библиотеку test.dll и вызываю команду:
g95 -s -shared -mrtd -o test.dll test.def *.f
Если верить FAQ’у, то здесь параметр -mrtd отвечает за соглашение передачи параметров STDCALL. В файле test.def разбираемся с декорированием имен: пусть экспортируем функцию testfcn, тогда, чтобы избавиться от знака подчеркивания при экспорте, пишем в нем:
EXPORTS testfcn=testfcn_
Итак, библиотека получена, теперь задача – обернуть ее в какой-нибудь класс на C#.
Пусть наша функция принимает параметрами xr, xi – вещественные массивы двойной точности, целые числа n и ord, и еще один вещественный массив y. Тогда метод, импортирующий функцию из нашей библиотеки, следует объявить следующим образом:
[DllImport( "test.dll", EntryPoint = "testfcn", SetLastError = true,
CharSet = CharSet.Ansi, ExactSpelling = true,
CallingConvention = CallingConvention.Stdcall )]
public static extern void testfcn( ref double xr,
ref double xi,
ref int n,
ref int ord,
ref double y );
При вызове функции, передавая массивы, даем ссылку на их первый элемент:
testfcn( ref xr[ 0 ], ref xi[ 0 ], ref n, ref o, ref y[ 0 ] );
Если работа выполняется на 64-разрядной машине, не забываем синхронизировать разрядность в настройках дотнетовского проекта.
В целом, на этом изыскания можно было бы и закончить, если бы не одно «но»: быстродействие полученного модуля меня абсолютно не устраивало. Поэтому мой взгляд упал на Intel Fortran Compiler, а именно – десятую его версию.
Он вполне неплохо проинтегрировался в Visual Studio 2005, поэтому нужда во внешних редакторах сразу же отпала. Порадовал широкий выбор опций, связанных с оптимизацией.
Вместо def-файла ему достаточно подсунуть такую директиву:
!DEC$ ATTRIBUTES DLLEXPORT, ALIAS: 'testfcn' :: testfcn
По советам Гугла пытался впихнуть туда же атрибут STDCALL, равно как и проставить соответствующую опцию в параметрах компилятора, но он упорно выдавал библиотеку с соглашением CDECL, поэтому C#-обвертку пришлось немного изменить, выставив для DLLImport’a CallingConvention = CallingConvention.Cdecl.
Еще одна проблема возникла с массивами – по умолчанию они передавались через стек, и даже при небольших объемах данных сыпались исключения переполнения стека. Лечится это добавлением ключика /heap-arrays в параметры командной строки компилятора.
Итак, выставив настройки оптимизации по своему вкусу (включая специфические для процессоров Intel), компилируюсь, запускаю программу на тест, и – получаю прирост производительности на 50% (!!!).
Единственный минус – при развертывании приложения придется тащить за собой целый ворох вспомогательных библиотек, от которых теперь, оказывается, зависит наш модуль. Их не так уж и много, они не такие уж и большие, но все равно вполне неприятно. При использовании G95 такой необходимости не было. Впрочем, в таких случаях я сразу вспоминаю псевдокомпилятор MATLAB’a со 150-мегабайтным установочником рантаймов, и на душе сразу становится легче :)
