Често се оказвам със задачата да смесвам няколко езика за програмиране в един научен компютърен проект. Това звучи малко налудничаво, но е доста обичайно, защото понякога наследеният код е твърде дълъг за превод в рамките на крайния срок или дори по политически причини (да, човекът, който е написал кода по време на дипломната си работа през 1983 г., може да се обиди, ако актуализирате неговия код). Днес ще говоря малко за излагането на Fortran интерфейси, които да се използват в C/C++ код. Все още не владея темата, въпреки че имам известен опит в това. Ако бързате и не се занимавате с подробности, можете да отидете директно в моя GitHub и да изтеглите примерния проект.

Необходими знания

След като получим задачата да изложим Fortran API на C програма (отсега нататък ще оставя C++ имплицитно, защото формално излагаме интерфейса на C), се изисква набор от предварителни познания. Първо, изискват се минимални познания по Fortran, за да се напишат Fortran ISO-C свързванията към интерфейса. Това са файловете, които имитират (но могат също така да променят) функциите от оригиналната библиотека, които е желателно да бъдат налични в C. Това е код на Fortran, използващ стандартната библиотека iso_c_binding, както ще видим по-нататък. Трябва да е очевидно, че трябва да знаете как да компилирате код и да създавате библиотеки с приложения. Ако не го направите, продължете да четете и ние ще стигнем до там.

Работен процес на взаимодействие

Нека станем практични. Да предположим, че имате някакъв стар (или не толкова стар) код на Fortran и вашият мениджър или консултант ви е помолил да доразработите съществуващия модел. Вие сте в 2019 г. (докато пиша) и знаете, че Fortran е разлагащ се език, както можете да проверите в линка. От днес, ако търсите тагове в Stackoverflow, получавате 9237 въпроса с Fortran, 293253 резултата с C и изненадващо 599538 резултата с C++. Можете да направите подобно изживяване във вашата търсачка. Досега спорите с вашия мениджър/съветник и двамата са съгласни, че новите разработки трябва да се правят на език, който предоставя повече онлайн поддръжка.

С вашето копие на библиотеката Fortran за предоставяне на C-интерфейс в ръцете вие ​​решавате да направите чернова на диаграма на Gant за задачите, които трябва да изпълните. Започвайки с прост списък, трябва да напишете нещо близко до:

  1. Разработване на интерфейс на Fortran: осигурете C-обвързване за всяка функция/модул на Fortran, който ще се използва в проекта на C. Забележете, че не е необходимо да предоставяте интерфейс за всички библиотечни функции, а само тези, които ще използвате (подобно на предоставянето на интерфейс към C++ клас в Cython, ако някога сте правили това).
  2. Генериране на интерфейсната библиотека: тук предполагам, че вече сте компилирали вашата Fortran библиотека, за да бъде свързана с оригиналния Fortran проект. Сега трябва да компилирате обектния код на всеки от файловете за свързване на Fortran ISO-C и оригиналната библиотека и след това да архивирате като споделена библиотека (все още не съм опитвал да направя споделена библиотека в моя списък TODO!).
  3. Осигурете C заглавки: за да извикате функциите на Fortran от C, подписът на всяка функция трябва да бъде изложен, за предпочитане в заглавен файл. Този файл също ще предефинира всички Fortran типове като C struct.
  4. Включете заглавката в основната C програма: забавлявайте се! Можете да компилирате вашата C програма, извиквайки функции на Fortran. Не забравяйте да свържете библиотеката за свързване на ISO-C, създадена преди това с изпълнимия файл, в противен случай ще получите грешка при сегментиране.

Стигане до детайлите

В това, което следва, всички коментари и някои интервали бяха премахнати от файловете, за да се получи по-компактен код. Можете да намерите източниците тук и инструкциите за компилации в тук (няма да разглеждаме компилацията и специалните опции тук в текста).

Следващият кодов блок дефинира модул, наречен module_f_example, който е библиотеката, предоставена от вашия шеф за използване от C код. Забележете, че имаме тип example_type, който се предава като аргумент mstruc към подпрограма example, тази, която ще интерфейсираме тук. Тази подпрограма изчислява хипотенузата на триъгълник, чиито страни са предоставени от свойствата на example_type и след това задава двете страни на триъгълника на нула (защо? Нямам представа защо го направих). И така, трябва да свържем към C тип Fortran и функция, която получава като аргументи свързания тип и двойно число, което връща резултата от изчислението. Доста скучно. Само за да покажем от C, когато сме вътре в кода на Fortran, някои стойности преди и след изчислението се отпечатват на екрана.

Сега е време да предоставим интерфейса. Кодът тук всъщност е някакъв странен Fortran. Първо, трябва да използваме iso_c_binding по подразбиране на компилатора. Препоръчително е да го импортирате като use, intrinsic, защото в противен случай компилаторът може да има своя специфична реализация, която може да не спазва стандартите, което може да доведе до това, че вашият код може да не се компилира с различен компилатор. След това, както (може би) се очаква, импортираме модула Fortran, за да предоставим интерфейса. Типът се предефинира и преименува. Забележете използването на bind(c) след всички декларации, които искаме да можем да извикаме от C (тип и подпрограма).

В подпрограмата се наблюдават някои особености в типовете. Първо, тази функция ще получи C типове, следователно стойностите на mstruc и val трябва да отговарят на тях и да използват еквивалентните типове като тези на Fortran. Тъй като struct не е C еквивалент на Fortran type, параметър mstruc се предоставя като c_ptr. По очевидни причини този обект не може да се използва от рутината Fortran. Надяваме се, че iso_c_binding предоставя преводач на указател c_f_pointer за извличане на еквивалентния Fortran указател, който ще бъде зададен на f_mstruc, променливата, която накрая може да се използва при извикването на example! Какво объркване! Има и нещо повече: след като извикването на Fortran приключи, трябва да извлечете адреса на неговия указател чрез c_loc, така че да можете да върнете вашия обект правилно модифициран в C. Да, знам, че този параграф е доста гъст. Вероятно трябва да се върнете към началото или просто да погледнете този файл с подходящите коментари в моя акаунт в GitHub. Готово е, време е за C!

Нещата стават много по-прости в C заглавния файл. Просто осигурете struct със същото лице като неговия Fortran еквивалент type и интерфейс към функцията, както е посочено в интерфейса за свързване на ISO-C: c_example. Но почакай! Тук има една особеност. Забележете, че първият аргумент на тази функция сега е void*, този загадъчен тип! Това е необходимо, защото Fortran не може да знае за типа struct и затова трябва да приложим необработен указател (или това са моите ограничени познания по темата).

PS: след като проверих още няколко примера, мисля, че най-накрая разбрах ограничението. Всъщност можете да използвате c_example_type вътре в интерфейса ISO-C, ако типът е дефиниран като оперативно съвместим, но този тип не може да бъде предаден на необработената функция Fortran. Това е причината да се нуждаем от void*. Проверете това и това.

Само за заключение, прилагаме интерфейса. Отново, единствената особеност е, че трябва да преобразуваме препратка към нашия ptr в void* за съвместимост с интерфейса.

Надяваме се, че сте се забавлявали и намирате този код за полезен. В крайна сметка ще актуализирам източниците на GitHub с по-специфични случаи, които ми трябват в личните ми проекти. Ако имате въпроси, не се колебайте да се свържете с мен.