我在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
我的编辑设置是默认的
PS。使用固定长度数组我没有问题。我已经填充了这些值并将它们很好地返回给VBA。
PS2。我没有使用ISO_C_BINDINGS
(在CVF中不可用)
答案 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绑定的情况下完成所有这些操作。