从要在 Excel VBA 中使用的 Fortran 子例程创建 DLL

Creating DLL from a Fortran subroutine to be used in Excel VBA

我的目标是找到一种方法调用ExcelVBA中的Fortran子程序(可以在Prof Alan Genz上找到。程序是MVNPACK)来计算多元正态分布的CDF分配。理想情况下,我希望将来也能够在 C# 项目中使用从该源代码编译的 DLL 版本。但是,我不确定如何进行故障排除和进一步处理。我通常使用 Python 编写代码,接触过 C、Java 等语言,但从不使用 Fortran,也不太熟悉在 DLL 中调用函数时发生的情况。据我所知,这种计算并没有那么广泛,编译 Fortran 源代码是我最好的选择。

我一直在密切关注 here about creating the DLL, and here 关于在 Excel VBA 中使用它的示例,并一直在尝试模仿结果。从上面提到的 MVNPACK 源代码开始,我想我需要的是将输入传递给子程序 MVNDST,并通过将指针作为参数传递给子程序来取回结果。所以我做的第一件事就是尝试根据示例所做的修改代码。我修改后的版本 MVNDSTC 是这样的。

    SUBROUTINE MVNDSTC( N, LOWERC, UPPERC, INFINC, CORRELC, MAXPTS,
     &                    ABSEPS, RELEPS, ERRORC, VALUEC, INFORMC)
     & bind(c)
      use ISO_C_BINDING
      implicit none
cGCC$ ATTRIBUTES STDCALL, DLLEXPORT :: MVNDSTC
      EXTERNAL MVNDFN
      
      integer(kind=c_long), value:: N, MAXPTS
      real(kind=c_double), value:: ABSEPS, RELEPS
      
      type(c_ptr), value:: LOWERC, UPPERC, INFINC, CORRELC
      type(c_ptr), value:: ERRORC, VALUEC, INFORMC
      
      real(kind=c_double), dimension(:), pointer:: LOWER, UPPER, CORREL
      integer(kind=c_long), dimension(:), pointer:: INFIN
      real(kind=c_double), dimension(:), pointer:: ERROR_OUT, VALUE_OUT
      integer(kind=c_int), dimension(:), pointer:: INFORM_OUT
      
      INTEGER NN
      INTEGER INFORM, INFIS, IVLS
      DOUBLE PRECISION ERROR, VALUE, E, D, MVNDNT, MVNDFN
      COMMON /DKBLCK/IVLS
      
      NN = (N - 1) * N / 2
      call C_F_POINTER(LOWERC, LOWER, [N])
      call C_F_POINTER(UPPERC, UPPER, [N])
      call C_F_POINTER(INFINC, INFIN, [N])
      call C_F_POINTER(CORRELC, CORREL, [NN])
      call C_F_POINTER(ERRORC, ERROR_OUT, [1])
      call C_F_POINTER(VALUEC, VALUE_OUT, [1])
      call C_F_POINTER(INFORMC, INFORM_OUT, [1])
      
      IF ( N .GT. 500 .OR. N .LT. 1 ) THEN
         INFORM = 2
         VALUE = 0
         ERROR = 1
      ELSE
         INFORM = MVNDNT(N, CORREL, LOWER, UPPER, INFIN, INFIS, D, E)
         IF ( N-INFIS .EQ. 0 ) THEN
            VALUE = 1
            ERROR = 0
         ELSE IF ( N-INFIS .EQ. 1 ) THEN
            VALUE = E - D
            ERROR = 2D-16
         ELSE
*
*        Call the lattice rule integration subroutine
*
            IVLS = 0
            CALL DKBVRC( N-INFIS-1, IVLS, MAXPTS, MVNDFN, 
     &                   ABSEPS, RELEPS, ERROR, VALUE, INFORM )
         ENDIF
      ENDIF
      VALUE_OUT(0) = VALUE
      ERROR_OUT(0) = ERROR
      INFORM_OUT(0) = INFORM
      END

然后我创建了一个小子例程,上面有 mvndstc 声明。 VBA代码如下

Private Declare PtrSafe Sub mvndstc Lib "C:\Users\poopa\Desktop\mvn\mvn_project\fortran-library.dll" _
    (ByVal N As Integer, _
     ByRef LOWER As Single, _
     ByRef UPPER As Single, _
     ByRef INFIN As Single, _
     ByRef CORREL As Single, _
     ByVal MAXPTS As Integer, _
     ByVal ABSEPS As Double, _
     ByVal RELEPS As Double, _
     ByRef ERROR As Single, _
     ByRef VALUE As Single, _
     ByRef INFORM As Single)

Sub mvn_test()
    Dim value_1(1 To 1) As Single  ' Result of the function
    Dim inform_1(1 To 1) As Single  ' Information
    Dim error_1(1 To 1) As Single  ' Error estimate
    Dim upper_1() As Single
    Dim lower_1() As Single
    Dim infin_1() As Single
    Dim correl_1() As Single
    Dim n_1 As Long, n_1_2 As Long, max_pts_1 As Long

    n_1 = 5
    ReDim lower_1(1 To n_1)
    ReDim upper_1(1 To n_1)
    ReDim infin_1(1 To n_1)
    lower_1(1) = 0#
    lower_1(2) = 0#
    lower_1(3) = 1.7817
    lower_1(4) = 0.14755
    lower_1(5) = 0#
    
    upper_1(1) = 0#
    upper_1(2) = 1.5198
    upper_1(3) = 0#
    upper_1(4) = 0#
    upper_1(5) = 1.5949
    
    infin_1(1) = 1
    infin_1(2) = 2
    infin_1(3) = 1
    infin_1(4) = 1
    infin_1(5) = 0

    n_1_2 = Int(n_1 / 2 * (n_1 - 1))
    ReDim correl_1(1 To n_1_2)
    correl_1(1) = -0.707107  ' 12
    correl_1(2) = 0#  ' 13
    correl_1(3) = 0.5 ' 14
    correl_1(4) = 0#  ' 15
    correl_1(5) = 0.5 ' 23
    correl_1(6) = 0.5 ' 24
    correl_1(7) = 0#  ' 25
    correl_1(8) = 0.5 ' 34
    correl_1(9) = 0.5 ' 35
    correl_1(10) = 0.5 ' 45
    
    max_pts_1 = 625000
    mvndstc n_1, lower_1(1), upper_1(1), infin_1(1), correl_1(1), max_pts_1, 0.00005, 0, error_1(1), value_1(1), inform_1(1)
    Debug.Print "Value = " & (value_1(1))
    Debug.Print "Error Est = " & (error_1(1))
    Debug.Print "Inform = " & inform_1(1)
End Sub

现在我的第一次尝试我根本没有修改 ERROR、VALUE、INFORM 参数,然后简单地在 Fortran 中声明为它们各自的原始类型。我实际上可以 运行 VBA 子例程,但我得到的结果全为零。所以我推测程序 运行s 但也许我没有正确返回结果,我应该将这三个输出视为大小为 1 的指针。这样我就可以在 Fortran 中保持任何过程完全相同然后如果我把 VALUE_OUT(0) = VALUE 等等,在函数结束之前我应该​​得到结果就好了。现在使用我在此处发布的代码,我实际上可以看到 VBA 中打印出的结果,仍然全为零,但在那之后 Excel 会立即崩溃。

所以我想问一下我该如何从这里开始?我在这里做错了什么?有什么值得研究的资源吗?

提前致谢。

我昨天解决了这个问题,确实是数据类型的问题。当我阅读教程时,我假设 Single 是 VBA 中的某种对象类型。殊不知 Single 实际上是 Double 的单精度!我尝试通过让 DLL 将函数内的所有值打印到一个文件来调试所有这些。