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
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
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

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