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

Требуемые знания

После того, как поставлена ​​задача представить Fortran API программе на C (с этого момента я позволю C++ неявным, потому что формально мы открываем интерфейс для C), требуется набор предварительных знаний. Во-первых, для записи привязок Fortran ISO-C к интерфейсу требуется минимальное знание Fortran. Это файлы, которые имитируют (но также могут модифицировать) функции исходной библиотеки, которые должны быть доступны в C. Это код Фортрана, использующий стандартную библиотеку iso_c_binding, как мы увидим далее. Должно быть очевидно, что вы должны уметь компилировать некоторый код и создавать библиотеки приложений. Если нет, продолжайте читать, и мы доберемся до этого.

Рабочий процесс сопряжения

Приступим к практике. Предположим, у вас есть старый (или не очень старый) код на Фортране, и ваш руководитель или научный руководитель попросил вас доработать существующую модель. Вы находитесь в 2019 году (когда я пишу) и знаете, что Fortran — это умирающий язык, как вы можете проверить по ссылке. На сегодняшний день, если вы выполняете поиск по тегам в Stackoverflow, вы получаете 9237 вопросов по Fortran, 293253 результата по C и, что удивительно, 599538 результатов по C++. Вы можете сделать аналогичный опыт в своей поисковой системе. К настоящему моменту вы спорите со своим менеджером/консультантом, и оба согласны с тем, что новые разработки должны быть сделаны на языке, обеспечивающем большую онлайн-поддержку.

Имея в руках свою копию библиотеки Fortran для обеспечения C-интерфейса, вы решаете сделать черновик диаграммы Ганта для выполнения задач. Начав с простого списка, вы должны написать что-то близкое к:

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

Знакомство с деталями

В дальнейшем все комментарии и некоторые пробелы были удалены из файлов, чтобы получить более компактный код. Исходники вы можете найти здесь, а инструкции по компиляции — здесь (мы не будем рассматривать компиляцию и специальные опции здесь по тексту).

Следующий блок кода определяет модуль с именем module_f_example, который представляет собой библиотеку, предоставленную вам вашим начальником для использования из кода C. Обратите внимание, что у нас есть тип example_type, который передается в качестве аргумента mstruc подпрограмме example, с которой мы будем здесь работать. Эта подпрограмма вычисляет гипотенузу треугольника, стороны которого заданы свойствами example_type , а затем устанавливает обе стороны треугольника равными нулю (почему? Я понятия не имею, почему я это сделал). Итак, нам нужно привязать к C фортрановский тип и функцию, которая получает в качестве аргументов интерфейсный тип и двойное число, возвращающее результат вычисления. Довольно скучно. Просто чтобы показать из C, когда мы находимся внутри кода Fortran, некоторые значения до и после вычисления выводятся на экран.

Теперь пришло время предоставить интерфейс. Код здесь на самом деле какой-то странный Fortran. Во-первых, нам нужно использовать компилятор по умолчанию iso_c_binding. Рекомендуется импортировать его как use, intrinsic, потому что в противном случае компилятор может иметь свою конкретную реализацию, которая может не соответствовать стандартам, что может привести к тому, что ваш код не будет компилироваться другим компилятором. Затем, как (возможно) и ожидалось, мы импортируем модуль Fortran для предоставления интерфейса. Тип переопределен и переименован. Обратите внимание на использование bind(c) после всех объявлений, которые мы хотим вызывать из C (тип и подпрограмма).

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

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

PS: проверив еще несколько примеров, я думаю, что наконец понял ограничение. На самом деле вы можете использовать c_example_type внутри интерфейса ISO-C, если тип определен как интероперабельный, но этот тип не может быть передан в необработанную функцию Fortran. Вот почему нам нужны void*. Отметки это и это.

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

Надеюсь, вам понравился и вы найдете этот код полезным. В конечном итоге я буду обновлять исходники GitHub более конкретными случаями, которые мне понадобятся, а затем в моих личных проектах. Любые вопросы, не стесняйтесь обращаться ко мне.