Я пытаюсь скомпилировать некоторые очень старые процедуры 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 выдает нарушение прав доступа. Очевидно, что второй динамический массив смущает компилятор, но я не знаю, как его понять. заранее спасибо
Спасибо! Вы имеете в виду один большой массив целых чисел и указатели на этот массив?
@Stelios Antoniou Проблема не в двух параметрах массива, все объявления неверны, первая процедура работает правильно по счастливой случайности. INTARR1,INTARR2: PInteger
может помочь. И что с распределением памяти?
@ Дэвид Хеффернан. Есть ли пример того, как сделать вызов? Нужно ли менять код Delphi и Fortran?
Кажется, одинаково много работы в обе стороны. 1000 строк чего угодно не так уж сложно перевести, вдвойне сложно что-то многословное вроде фортрана. Моим подавляющим предпочтением здесь было бы переписать. «Это слишком сложно» кажется слабой причиной держаться за морской якорь устаревшей сложности. Я подозреваю, что если немного подумать, эти 1000 строк, вероятно, можно было бы переписать в Delphi во что-то вдвое меньше или меньше, и в то же время они стали бы намного чище и легче для чтения. Вы, вероятно, также в конечном итоге поймете, что делает код в конце - что-то, что, как мы можем сделать вывод, вы не понимаете в настоящее время.
Тип параметра — PInteger и передать @arr[0]
Спасибо вам обоим, я попробую сам
@ Дэвид Хеффернан, просто чтобы быть уверенным на 100%. Вы имеете в виду, что я передам серию указателей на целые числа и один большой массив целых чисел в конце, верно?
Нет. Вы заменяете array of integer
на PInteger
и пропускаете @arr[0]
Большое спасибо @David Heffernan! Я думаю, я понял
Я не знаю 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:
bind(C,name = "MYSUB1")
исправляет имя подпрограммы, поэтому вам не нужны дополнительные вторые директивы компилятора (которые я удалил из вашего кода).value
, который указывает компилятору передавать по значению, как это делается в C.c_int32_t
, чтобы быть совместимыми с типами целых чисел процессора C.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.
Заключительный совет:
Fortran имеет мощные стандартные функции взаимодействия, подобные тем, которые я добавил в ваш код F77, которые могут легко связать практически любой код Fortran с любым языком (через C).
Держитесь подальше от FORTRAN77, которому почти полвека, даже Fortran 90 уже более 30 лет. Последний стандарт Fortran был выпущен в 2018 году, что вместе с Fortran 2008 делает Fortran чрезвычайно мощным, высокоуровневым, быстрым, изначально векторизованным, параллельным, общим и распределенным параллельным языком программирования для численных вычислений.
На самом деле проблема в вопросе в коде Delphi.
@DavidHeffernan достаточно честно. Но я не понимаю вашего отрицательного голоса. Это решение устраняет многие лоскутные работы компилятора ad-hoc, сделанные в исходном коде и в процессе компиляции, путем введения «надлежащего стандартного подхода» к взаимодействию Fortran, хотя и не решает проблему его подключения к Delphi. Но я также прямо заявил об этом в своем ответе.
Дело в том, что суть заключается в ошибке в коде Delphi при использовании параметра открытого массива Delphi. Полный ответ должен решить эту проблему.
@Король. Спасибо за подробный ответ. C-совместимая DLL полностью приемлема для Delphi.
Не используйте параметр открытого массива. Используйте указатель на тип элемента массива. Параметр открытого массива имеет дополнительный неявный параметр для указания длины.