Fortran DLL в Delphi

Я пытаюсь скомпилировать некоторые очень старые процедуры Fortran в DLL, чтобы иметь возможность использовать их с Delphi. Хотя код на Фортране не очень большой (750-800 строк), его структура очень сложная с десятками команд GOTO и перевод непрост (я пытался сделать из него какой-то полезный код, но не получилось). Хотя я новичок в Фортране и не очень опытен в вызове библиотек DLL, мне постепенно удалось преодолеть все трудности, за исключением одной: возможности вызывать подпрограмму Фортрана с несколькими динамическими массивами. Вот простой пример, который я создал:

      SUBROUTINE  MYSUB1( NoEquations, INTARR1 ) 
!DEC$ ATTRIBUTES DLLEXPORT::MYSUB1
!DEC$ ATTRIBUTES C, REFERENCE, ALIAS:'MYSUB1' :: MYSUB1
C
C***************************************************************
C
         INTEGER    NoEquations, I
         INTEGER    INTARR1(*)
C
C***************************************************************
C
                 DO 100, I=1,NoEquations
                 INTARR1(I) = I 
  100    CONTINUE
      RETURN
C
      END

      SUBROUTINE  MYSUB2( NoEquations, INTARR1, INTARR2 ) 
!DEC$ ATTRIBUTES DLLEXPORT::MYSUB2
!DEC$ ATTRIBUTES C, REFERENCE, ALIAS:'MYSUB2' :: MYSUB2
C
C***************************************************************
C
         INTEGER    NoEquations, I
         INTEGER    INTARR1(*)
         INTEGER    INTARR2(*)
C
C***************************************************************
C
                 DO 100, I=1,NoEquations
                 INTARR2(I) = INTARR1(I) 
  100    CONTINUE
      RETURN
C
      END

Я компилирую код Fortran с помощью mingw-w64 с помощью следующей команды:

gfortran -shared -mrtd -fno-underscoring -o simple.dll simple.f

И я объявляю процедуру из Delphi с помощью:

  procedure mysub1(var NoEquations: integer; var INTARR1        : array of integer); stdcall; external 'simple.dll';
  procedure mysub2(var NoEquations: integer; var INTARR1,INTARR2: array of integer); stdcall; external 'simple.dll';

Программа Delphi компилируется правильно, но когда я ее запускаю, mysub1 работает правильно и обновляет INTARR1, а mysub2 выдает нарушение прав доступа. Очевидно, что второй динамический массив смущает компилятор, но я не знаю, как его понять. заранее спасибо

Не используйте параметр открытого массива. Используйте указатель на тип элемента массива. Параметр открытого массива имеет дополнительный неявный параметр для указания длины.

David Heffernan 20.12.2020 15:52

Спасибо! Вы имеете в виду один большой массив целых чисел и указатели на этот массив?

Stelios Antoniou 20.12.2020 16:17

@Stelios Antoniou Проблема не в двух параметрах массива, все объявления неверны, первая процедура работает правильно по счастливой случайности. INTARR1,INTARR2: PInteger может помочь. И что с распределением памяти?

MBo 20.12.2020 17:17

@ Дэвид Хеффернан. Есть ли пример того, как сделать вызов? Нужно ли менять код Delphi и Fortran?

Stelios Antoniou 20.12.2020 17:41

Кажется, одинаково много работы в обе стороны. 1000 строк чего угодно не так уж сложно перевести, вдвойне сложно что-то многословное вроде фортрана. Моим подавляющим предпочтением здесь было бы переписать. «Это слишком сложно» кажется слабой причиной держаться за морской якорь устаревшей сложности. Я подозреваю, что если немного подумать, эти 1000 строк, вероятно, можно было бы переписать в Delphi во что-то вдвое меньше или меньше, и в то же время они стали бы намного чище и легче для чтения. Вы, вероятно, также в конечном итоге поймете, что делает код в конце - что-то, что, как мы можем сделать вывод, вы не понимаете в настоящее время.

J... 20.12.2020 18:10

Тип параметра — PInteger и передать @arr[0]

David Heffernan 20.12.2020 18:20

Спасибо вам обоим, я попробую сам

Stelios Antoniou 20.12.2020 18:24

@ Дэвид Хеффернан, просто чтобы быть уверенным на 100%. Вы имеете в виду, что я передам серию указателей на целые числа и один большой массив целых чисел в конце, верно?

Stelios Antoniou 20.12.2020 18:26

Нет. Вы заменяете array of integer на PInteger и пропускаете @arr[0]

David Heffernan 20.12.2020 18:35

Большое спасибо @David Heffernan! Я думаю, я понял

Stelios Antoniou 20.12.2020 19:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
388
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не знаю Delphi, но вот что вы можете сделать, чтобы создать DLL, доступную из языка C. Я надеюсь, что вы найдете это полезным. Вот ваш код F77, измененный, чтобы сделать его совместимым с функциями модуля iso_c_binding Fortran:

      SUBROUTINE MYSUB1(NoEquations,INTARR1) bind(C,name = "MYSUB1")
         !DEC$ ATTRIBUTES DLLEXPORT :: MYSUB1
         use, intrinsic :: iso_c_binding, only: IK => c_int32_t
         integer(IK), intent(in), value :: NoEquations
         integer(IK), intent(out)       :: INTARR1(NoEquations)
         integer                        :: I
         DO 100, I=1,NoEquations
             INTARR1(I) = I
  100    CONTINUE
         RETURN
      END SUBROUTINE MYSUB1

C***************************************************************
C***************************************************************

      SUBROUTINE MYSUB2(NoEquations,INTARR1,INTARR2)
     +bind(C,name = "MYSUB2")
         !DEC$ ATTRIBUTES DLLEXPORT :: MYSUB2
         use, intrinsic :: iso_c_binding, only: IK => c_int32_t
         integer(IK), intent(in), value :: NoEquations
         integer(IK), intent(in)        :: INTARR1(NoEquations)
         integer(IK), intent(out)       :: INTARR2(NoEquations)
         integer                        :: I
         DO 100, I=1,NoEquations
             INTARR2(I) = INTARR1(I)
  100    CONTINUE
         RETURN
      END SUBROUTINE MYSUB2

Обратите внимание на множество тонких, но важных изменений, которые я внес в ваш код, чтобы сделать его совместимым с C:

  1. bind(C,name = "MYSUB1") исправляет имя подпрограммы, поэтому вам не нужны дополнительные вторые директивы компилятора (которые я удалил из вашего кода).
  2. Также обратите внимание на атрибут value, который указывает компилятору передавать по значению, как это делается в C.
  3. Также обратите внимание, что я определяю типы целых чисел в интерфейсах подпрограммы как c_int32_t, чтобы быть совместимыми с типами целых чисел процессора C.
  4. Также обратите внимание, что я преобразовал ваши массивы предполагаемого размера INTARR1 и INTARR2 в массивы явной формы INTARR1(NoEquations), INTARR1(NoEquations).

Поскольку в вашем коде есть директивы компилятора Intel !DEC$, я предполагаю, что вы используете компилятор Intel Fortran в Windows. Обратите внимание, что я удалил ваши вторые строки директив в обеих подпрограммах Fortran.

Теперь, предположив, что вы сохранили приведенный выше код в файле с именем mysubs.F, а затем скомпилировали этот файл с помощью компилятора Intel Fortran ifort в командной строке Intel-Windows, как в следующей команде,

ifort mysubs.F /dll /out:libsubs

создаст DLL с именем mysubs.dll в текущей папке и выведет на экран следующее сообщение,

Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.16.27027.1
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:mysubs.dll
-dll
-implib:mysubs.lib
mysubs.obj
   Creating library mysubs.lib and object mysubs.exp

Чтобы протестировать эту DLL, вы можете попробовать следующий код C, хранящийся в main.c,

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void MYSUB1(int32_t, int32_t []);
void MYSUB2(int32_t, int32_t [], int32_t []);

int main(int argc, char *argv[])
{
    const int32_t NoEquations = 5;
    int32_t INTARR1[NoEquations];
    int32_t INTARR2[NoEquations];
    int loop;

    // C rules for argument passing apply here

    MYSUB1(NoEquations,INTARR1);
    printf("\nINTARR1:\n"); for(loop = 0; loop < NoEquations; loop++) printf("%d ", INTARR1[loop]);

    MYSUB2(NoEquations,INTARR1,INTARR2);
    printf("\nINTARR2:\n"); for(loop = 0; loop < NoEquations; loop++) printf("%d ", INTARR2[loop]);

    return 0;
}

Компилируя этот код C с помощью компилятора Intel C,

icl main.c -c

печатает следующее на экране,

Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

main.c

и генерирует основной объектный файл C. Наконец, свяжите объектный файл C с библиотекой DLL Fortran, чтобы сгенерировать исполняемый файл с помощью следующей команды:

icl main.obj mysubs.lib -o main.exe

который печатает следующее на экране,

Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.16.27027.1
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:main.exe
main.obj
mysubs.lib

Проверка вызова DLL. Просто вызовите сгенерированный исполняемый файл main.exe,

main.exe

который печатает на экране,

INTARR1:
1 2 3 4 5
INTARR2:
1 2 3 4 5

Теперь, чтобы вызвать эту DLL из Delphi, просто предположим, что вы вызываете функции C с прототипами, указанными в основном коде C. Вот и все. Больше не нужно иметь дело с Fortran из Delphi.

Заключительный совет:

  1. Fortran имеет мощные стандартные функции взаимодействия, подобные тем, которые я добавил в ваш код F77, которые могут легко связать практически любой код Fortran с любым языком (через C).

  2. Держитесь подальше от FORTRAN77, которому почти полвека, даже Fortran 90 уже более 30 лет. Последний стандарт Fortran был выпущен в 2018 году, что вместе с Fortran 2008 делает Fortran чрезвычайно мощным, высокоуровневым, быстрым, изначально векторизованным, параллельным, общим и распределенным параллельным языком программирования для численных вычислений.

На самом деле проблема в вопросе в коде Delphi.

David Heffernan 22.12.2020 01:15

@DavidHeffernan достаточно честно. Но я не понимаю вашего отрицательного голоса. Это решение устраняет многие лоскутные работы компилятора ad-hoc, сделанные в исходном коде и в процессе компиляции, путем введения «надлежащего стандартного подхода» к взаимодействию Fortran, хотя и не решает проблему его подключения к Delphi. Но я также прямо заявил об этом в своем ответе.

Scientist 22.12.2020 01:34

Дело в том, что суть заключается в ошибке в коде Delphi при использовании параметра открытого массива Delphi. Полный ответ должен решить эту проблему.

David Heffernan 22.12.2020 01:36

@Король. Спасибо за подробный ответ. C-совместимая DLL полностью приемлема для Delphi.

Stelios Antoniou 23.12.2020 12:41

Другие вопросы по теме