Delphi 中的 Fortran DLL

Fortran DLL in Delphi

我正在尝试在 DLL 中编译一些非常古老的 Fortran 程序,以便能够将它们与 Delphi 一起使用。虽然 Fortran 代码不是很大(750-800 行),但它的结构非常复杂,有几十个 GOTO 命令,而且翻译起来并不容易(我试图用它制作一些有用的代码,但我失败了)。 虽然我是Fortran新手,在调用DLL方面经验不多,但我逐渐克服了所有困难,但有一个困难,那就是能够调用具有多个动态数组的Fortran Subroutine。这是我创建的一个简单示例:

      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

我使用以下命令使用 mingw-w64 编译 Fortran 代码:

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 proram 编译正确,但当我 运行 它时,mysub1 正常工作并更新 INTARR1,但 mysub2 给我一个访问冲突。显然,第二个动态数组混淆了编译器,但我不知道如何让它理解。 提前致谢

我不知道 Delphi,但您可以按照以下步骤创建可从 C 语言访问的 DLL。我希望你觉得这对你有帮助。这是您的 F77 代码经过修改后可通过 Fortran 的 iso_c_binding 模块功能进行互操作:

      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. 此外,请注意,我已将您的 assumed-size INTARR1INTARR2 数组转换为显式数组 INTARR1(NoEquations)INTARR1(NoEquations).

由于您的代码中有英特尔 !DEC$ 编译器指令,我假设您在 Windows 上使用英特尔 Fortran 编译器。请注意,我删除了两个 Fortran 子例程中的第二行指令。

现在,假设您将上述代码存储在名为 mysubs.F 的文件中,然后在 Intel-Windows 命令提示符下通过 Intel Fortran 编译器 ifort 编译此文件在以下命令中,

ifort mysubs.F /dll /out:libsubs

会在当前文件夹中生成一个名为mysubs.dll的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,您可以尝试存储在 main.c

中的以下 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 编译器编译此 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 主目标文件。最后,link用Fortran DLL库生成可执行文件的C目标文件,

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

现在,要从 Delphi 调用此 DLL,只需假设您正在使用 C 主代码中指定的原型调用 C 函数。就这样。不再从 Delphi.

内部处理 Fortran

最终建议:

  1. Fortran 具有强大的标准互操作性功能,就像我添加到您的 F77 代码中的功能一样,可以轻松地将几乎所有 Fortran 代码连接到任何语言(通过 C)。

  2. 远离快半个世纪的FORTRAN77,Fortran 90也已经有3年多的历史了。最新的 Fortran 标准于 2018 年发布,与 Fortran 2008 一起使 Fortran 成为一种极其强大、高级、快速、原生矢量化、并发、共享和分布式的数值计算并行编程语言。