从要在 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 将函数内的所有值打印到一个文件来调试所有这些。
我的目标是找到一种方法调用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 将函数内的所有值打印到一个文件来调试所有这些。