从包含动态数组的结构的VBA调用Fortran 95 dll

时间:2014-09-11 18:57:52

标签: vba fortran fortran95

我在Excel VBA中有一个包含动态数组的类型结构。我想使用Compaq Visual Fortran编译的FORTRAN .dll来填充值(我知道它很旧,但我受限于CVF和Excel2003)。

Public Type T_STRUCT_3
    COUNT As Long
    VALUE As Double
    ARR() As Double
End Type

Public Declare Sub TestCalc3 Lib "FortranLib.dll" ( _ 
                     ByVal X As Double, ByVal n As Long, ByRef a As T_STRUCT_3)
Public Sub Initialize()
    Dim a As T_STRUCT_3, n As Long
    n = 3
    ReDim a.ARR(1 To n)
    Call TestCalc3(X, n, a)
End Sub

我在Fortran中尝试过(并且失败了)使用以下内容:

module CALCTEST
    IMPLICIT NONE

    INTEGER, PARAMETER :: C_INT = selected_int_kind(9)
    INTEGER, PARAMETER :: C_REAL = selected_real_kind(6, 37)
    INTEGER, PARAMETER :: C_DOUBLE = selected_real_kind(15, 307)

    INTEGER, PARAMETER :: MAX_SIZE = 10
!-----------------------------------------------------------------------    
    type T_STRUCT_3
    SEQUENCE
        INTEGER(C_INT) :: COUNT
        REAL(C_DOUBLE) :: VALUE
        REAL(C_DOUBLE), POINTER :: ARR(:)
    end type T_STRUCT_3

contains
!--------------------------------------------------------------------
    subroutine TestCalc3(X,N,A) 
    !DEC$ATTRIBUTES ALIAS:'TestCalc3' :: TestCalc3
    !DEC$ATTRIBUTES DLLEXPORT :: TestCalc3
    !DEC$ATTRIBUTES VALUE :: N, X

    INTEGER(C_INT), INTENT(IN)      :: N
    TYPE(T_STRUCT_3), INTENT(OUT)   :: A
    REAL(C_DOUBLE), INTENT(IN)      :: X

        A%COUNT = N                 ! Value N is fine and I can assign it to A%COUNT
        A%VALUE = X                 ! Value X is fine and I can assign it to A%VALUE
        A%ARR =(/ (X*I, I=1,N) /)   ! <== how do I point A%ARR to the dynamic array?
                                    ! Here is where the error occurs

    RETURN
    end subroutine

end module

我的编辑设置是默认的

Settings

PS。使用固定长度数组我没有问题。我已经填充了这些值并将它们很好地返回给VBA。

PS2。我没有使用ISO_C_BINDINGS(在CVF中不可用)

2 个答案:

答案 0 :(得分:1)

问题在于,用于CVF结构内部数组的Fortran指针的描述符与用于VBA中等效组件的内容的描述符不同。

你需要自己动手#34;带有所谓的Cray或整数指针和安全数组的指针(我认为这是VBA用于组件的一部分 - 即Fortran端的第三个组件应该是一个INTEGER,它包含一个VBA安全数组的句柄)。我不确定CVF为后者提供的开箱即用支持。

最好在Intel forums上询问这样的问题,作为CVF的继承者。该论坛上可能存在如何执行此操作的示例。

答案 1 :(得分:1)

如果您真正希望的是按照您的示例,那么为什么还要为Fortran端的结构而烦恼呢?所示的例子实际上是一个固定长度的数组&#34;问题,因为你正在经过&#34; n&#34;明确地向Fortran s / r发送,因此Fortan将其视为固定长度(c.f. allocationatable等)。

也就是说,只要打电话给Fortran s / r,要求返回&#34;正常&#34;数组,然后在返回后将数组分配给VBA Struct,或直接通过Arg(例如,取决于你使用策略1)或2))。

在VBA和Fortran之间传递数组时,您有一些选择,例如:

1)通过&#34;正常&#34;数组,例如:

显然,示例中Fortran端所需的所有信息都是在arg列表中明确提供的,所以为什么不呢

Subroutine ForSub(X,n,Arr)
!
Real(XX), Intent(In)    :: X
Integer, Intent(In)     :: n
Real(XX), Intent(Out)   ;; Arr(n)
!
Arr(1:n) = ....
!
End Subroutine ForSub

VBA方将需要Sub的适当声明,然后还需要一个VBA sub,它将具有&#34; direct array&#34;语法,类似

Dim Arr(1 to n)
Call ForSub(X, n, Arr(1))

...等

同样在VBA方面,使用这种方法,您需要将Fortran创建的数组显式分配给VBA Struct的每个元素。

......这是一种蛮力的方法,虽然在执行方面效率不高,但它很容易且可靠(虽然VBA方面对于更复杂的设置有点痛苦)。

2)将(仅数组)传递为Variant / SafeArray

或者,您可以作为Variant / Array或Structure传递值。然而,正如IanH所建议的,这需要将Args作为指向Variant或SafeArray的指针(可以使用其中任何一个,虽然细节稍有不同......我使用Variants和Cray指针方法来传递数组,具体取决于具体问题手)。完整的解释是相当繁琐的,但Fortran方面可能(取决于采用的确切方法)看起来像(为简单起见,假设传递数组而不是结构),在此示例中传递Variant:

!
!************************************************************************
!   Basic stats: returns a vector of stats associated with a series X
!************************************************************************
!
!DEC$ ATTRIBUTES DLLEXPORT :: BASICSTATS1D_XL
!
    Subroutine BASICSTATS1D_XL(X_VARIANTARRAY, N, RESULTS, IERR, &
                                ISTATOPTIONX, &
                                lSTATOPTIONCUMX, &
                                UPPERQUANTILEX, &
                                LOWERQUANTILEX, &
                                LALLOWNONNUMERICX_K2
!
!
    ! This s/r is provided so that long length data sets can be processed, as opposed to XL 29 limit
    ! ==> BUT ALSO, to permit stats for seriest that includ #NA's etc, i.e. return stats for
    !     just the legitimate numeric values in the data set ... if so desired.
    !
    Use ARTType
    Use ARTStats, Only: BasicStats
    !
    Use ARTF90ExcelMixedLangMod, Only: ARTExcelVarArray1D
    !
    ! Necessary to define interface to SafeArrayxxx calls
    !
    Use IFCom !Use dfcom in your case
    !
    Implicit None
    !
    !
    Type(Variant), Intent(In)                   :: x_VariantArray  ! this is an "in" example, but same declaration for "out" also
!
:
:
    !
    Call ARTExcelVarArray1D(VarArray_Ptr = x_VariantArray%VU%Ptr_Val, n = n, FArray = x(:), iErr = iErr, &
                            nAllowNonNumericX = nZZ)
    !

这里arg x_VariantArray被声明为CVF / IVF Variant,并且可以直接访问VBA Variant。或者,可以只发送指针(即作为Cray指针/整数)并以这种方式访问​​数据,但细节稍有不同。

请注意,(自定义)s / r ARTExcelVarArray1D()用于将Variant中的数组转换为Fortran数组。对称s / r将Fortran阵列转换为Intent(Out)等变体。这是一个非常简化的版本(我已经删除了大部分的前后处理和大量的&#34; VT测试/ coercion&#34;等这样的s / r是(这个是针对整数(4)&#39; s):

    Subroutine ARTExcelVarArray1D_Int(VarArray_Ptr, n, FArray, iErr, iCoerceX, iFailedCoerceX, iFailedCoerceValX)
    !
    Use IFCom, Only: SafeArrayGetDim, SafeArrayGetLBound, SafeArrayGetUBound, SafeArrayAccessData, SafeArrayUnAccessData
    Use IFWinTY, Only: Variant              ! Variant appears to be in IFCom also, so could STREAMLINE
    !
    Integer, Intent(In)             :: n
    !
    ! Declare array_ptr to be a pointer to an integer.  When Cray pointers
    !  are declared, they must point to something; this DUMMY integer is
    !  simply a placeholder so we can declare the pointer.
    !
    !
    !
    Integer                         :: dummy
    !
    Pointer (VarArray_Ptr, dummy)   ! Can't have Intent() with Ptr's , but this is an (In)
    !
    Integer, Intent(In), Optional   :: iCoerceX, iFailedCoerceX, iFailedCoerceValX
    !
    Integer, Intent(Out)            :: iErr
    !
    Integer, Intent(InOut)          :: FArray(n)
    !
    !
    !
    ! Locals
    ! ------
    !
    ! Declare another pointer, which will be used as the head of the
    !  array of EmployeeInfo's.
    !
    Pointer (Data_ptr, ArrayData)   ! Can't have Intent() with Ptr's, but this Local
    !
    Type(Variant)                   :: ArrayData(n)
    !
    Integer                         :: i, j, k   ! Loop variables
    Integer                         :: iStart, iEnd, nDim
    !
    Integer                         :: Status
    !
    !
    iErr = 0
    !
    FArray(:) = 0
    !
    !
    ! Get SafeArray Shape/Extent etc
    ! ------------------------------
    !
    !
    nDim = SafeArrayGetDim(VarArray_Ptr)
    !
    !
    !
    Status = SafeArrayGetLBound(VarArray_Ptr, 1, iStart)
    Status = SafeArrayGetUBound(VarArray_Ptr, 1, iEnd)
    !
    !
    ! Use the SafeArray routine to get the address of the array of Data
    ! -----------------------------------------------------------------
    !
    Status = SafeArrayAccessData( VarArray_Ptr, Data_ptr )
    !
    If( Status /= 0 ) Then
        !
        !
        iErr = -Status
        Go To 99999
    End If
    !
    !   Return value            Meaning
    !   -------------------------------
    !   S_OK                    Success.
    !   E_INVALIDARG            The argument psa was not a valid safe array descriptor.
    !   E_UNEXPECTED
    !
    ! ... skip lots of things, for this simple illustration
    !
    !
    Call ARTExcelVarArray1D_Int_XX(ArrayData, n, FArray, iErr, iCoerceX, iFailedCoerceX, iFailedCoerceValX)
    !
    !
    !
99999   Continue
    !
    !
    ! When you are done, you must 'deaccess' the data.
    ! ------------------------------------------------
    !
    Status = SafeArrayUnaccessData(VarArray_Ptr)
    !
    If( Status /= 0 ) Then
        !
        ! perform your error handling
        !
    End If
    !
    !
    End Subroutine ARTExcelVarArray1D_Int




Pure Subroutine ARTExcelVarArray1D_Int_XX(ArrayData, n, FArray, iErr, iCoerceX, iFailedCoerceX, iFailedCoerceValX)
!
Use IFWinTY, Only: Variant              ! Variant appears to be in IFCom also, so could STREAMLINE
    !
    !
    Integer, Intent(In)             :: n
    !
    !
    !
    Type(Variant), Intent(InOut)        :: ArrayData(:)
    !
    Integer, Intent(In), Optional   :: iCoerceX, iFailedCoerceX, iFailedCoerceValX
    !
    Integer, Intent(Out)            :: iErr
    !
    Integer, Intent(InOut)          :: FArray(n)
    !
    !
    !
    ! Locals
    !
    Integer                         :: i, iFailedCoerceVal
    !
    !
    !
    !   VT_Type Verification etc
    !   ------------------------
    !
        ! Then some elements of the Array are not required type (e.g. Int, Real etc)
        !
        ! Note: requires option for coercion to other Types
        !       - e.g. if Real(SP) (ie. VT_Type = 2)  then maybe OK etc
        !       - if Int, (i.e. VT_Type = 2 or 3), then what ??
        !... skip lot's of things for this simple illustration
        !
        ! First, apply "Error" handling
        !
        !... skip lot's of things for this simple illustration
    ! Now, fill in the fields
    ! -----------------------
    !... skip lot's of things for this simple illustration
    !
    ForAll(i=1:n)
            FArray(i) = ArrayData(i)%VU%Long_Val
    End ForAll
    !
    !
    !
    End Subroutine ARTExcelVarArray1D_Int_XX

在此特定示例中,数据作为直接1-D数组传递(通过Variant)。变体类型包括指示其实际包含内容的各种信息,并且需要查询这些元素以确定变体等与Fortran变量之间的转换细节。

我写了自己的Variant / SafeArray / BString等&#34;转换器&#34; (它有相当多的工作,有很多细节),但CVF / IVF包中有一些例子,并且在线。一般来说,你需要一组这样的例程来处理Int / Real / BString 1-D / n-D等等,以及很多&#34;重载&#34;。

BString / VBString案例更加繁琐,特别是如果您的数组是B / VBStrings数组。

一旦创建了基于显式(Cray)指针的变量和/或显式变体变量,使用Watch逐步执行代码对于了解这些类型的结构及其包含的信息非常有用

您可能还希望看一下/获得Canaima的F90VB软件包,它可以为您完成所有这些(以及更多)(尽管CVF / IVF变体之间的内部结构略有不同) Canaima的F90VB变种等)。

VBA端的语法也略有不同,具体取决于您的VBA变量是否被声明为Variant,Range等

感兴趣的是,通过这种方法传递数组的速度非常快(主要是因为不需要执行VBA方面的逐元素数组赋值,以及其他许多&#34;奇怪的事情在VBA端是必需的(VBA / COM不是特别对阵列友好的),特别是对于多暗的arr,Ranges等)。

缺点是Fortran方面的编码更多,并且需要进行大量的前后处理检查才能实现强大的实施。

3)对于Typed / Structured Vars

使用Structures执行此操作只是一个扩展,但更多的是gore&#34;。有一个特殊的方法可以在dir中传递Structure的一个例子...... MsDev \ DF98 \ SAMPLES \ MIXLANG \ VB \ TYPEARRAYS,以及在线讨论。

如果您愿意,可以在没有任何ISO绑定的情况下完成所有这些操作。