contents   index   previous   next



Passing Arrays

 

As indicated earlier, Visual Basic arrays are always passed by reference to called subroutines. This means the calling subroutine is always able to modify the data in the array, and in some cases to reallocate the array (i.e. to change its dimensions or to replace it with a new one). There isn’t much to the process of passing Visual Basic arrays to Fortran subroutines; Visual Basic passes a handle to a Safe Array, and your Fortran subroutine can manipulate the Safe Array using the appropriate subroutines and functions provided by f90VB’s SafeArrays library. The most important consideration you should keep in mind is that, as opposed to Fortran, array arguments in Visual Basic do not declare the dimensions of the array. If your subroutine requires arrays with a given number of dimensions (for example a bi-dimensional matrix) it should check that the passed Safe Array is declared with the adequate number of dimensions.

 

Again, the mechanics and salient points of passing Safe Arrays between Visual Basic and Fortran are better explained through an example.

 

 

 

 

 

Example 4.4

 

In this example, you will create a Fortran subroutine (MatrixProduct) that receives two matrices or vectors and multiplies them, returning the result in a third matrix. Fortran includes an intrinsic function called matmul that is highly optimized to perform matrix multiplications. The ability to use such functionality from a Visual Basic program would be very advantageous, in particular because Visual Basic has never been known as a speed demon when it comes to numerical analysis.

 

This is the Fortran code of subroutine MatrixProduct [21]:

 

subroutine MatrixProduct(SAHndlA, SAHndlB, SAHndlR, iError)

 

!This subroutine receives two arrays (SAHndlA and SAHndlB)

!multiplies them and returns the result in SAHndlR

!If the operation is successful, iError contains 0, 

!otherwise it contains a negative value

 

use f90VBDefs

use f90VBSafeArrays

implicit none

 

!subroutine arguments

integer(SAFEARRAY_KIND),intent(in)::SAHndlA, SAHndlB

integer(SAFEARRAY_KIND),intent(inout)::SAHndlR

integer(SHORT_KIND),intent(inout)::iError

 

!Accessory variables

 

!Fortran arrays to map Safe Arrays

real(SINGLE_KIND),pointer::MtrxA(:,:),MtrxB(:,:),MtrxR(:,:)

 

integer(VARTYPE_KIND)::vt_type

integer(LONG_KIND)::nDimsA,nDimsB,nElementsA,nElementsB

integer(HRESULT_KIND):: iRet

 

!Check that input arrays conform in type

call SafeArrayGetVarType(SAHndlA,vt_type,iRet)

if (iRet.ne.S_OK .or. vt_type.ne. VT_R4) then

    iError=-1

    return

endif

 

call SafeArrayGetVarType(SAHndlB,vt_type,iRet)

if (iRet.ne.S_OK .or. vt_type.ne.VT_R4) then

    iError=-1

    return

endif

 

!Get dimensions of input matrices

nDimsA = SafeArrayGetDim(SAHndlA)

nDimsB = SafeArrayGetDim(SAHndlB)

 

!Check that array dimensions conform to multiplication

if (nDimsA.ne.2 .or. nDimsB.ne.2) then

    iError = -1

    return

endif

 

!check that size of arrays conform to multiplication

call SafeArrayGetNElements(SAHndlA,nDimsA,nElementsA,iRet)

call SafeArrayGetNElements(SAHndlB,1,nElementsB,iRet)

if (nElementsA.ne.nElementsB) then

    iError = -1

    return

endif

 

!Create the Safe Array for the result matrix

call SafeArrayGetNElements(SAHndlA,1,nElementsA,iRet)

call SafeArrayGetNElements(SAHndlB,nDimsB,nElementsB,iRet)

call SafeArrayCreate(SAHndlR, VT_I4, 1,nElementsA,1,nElementsB)

 

!Check that output safe array was created

if (SAHndlR.eq.f90VB_NULL_PTR) then

    iError=-1

    return

endif

 

!map safe arrays into Fortran arrays for fast access

call SafeArrayAccessData(SAHndlA,MtrxA,iRet)

call SafeArrayAccessData(SAHndlB,MtrxB,iRet)

call SafeArrayAccessData(SAHndlR,MtrxR,iRet)

 

!perform the multiplication

MtrxR = MATMUL(MtrxA,MtrxB)

iError=0

 

!un-map Fortran arrays (and unlock safe arrays)

call SafeArrayUnAccessData(SAHndlR)

call SafeArrayUnAccessData(SAHndlA)

call SafeArrayUnAccessData(SAHndlB)

 

end subroutine

 

 

Subroutine MatrixProduct has four arguments. The first two (SAHndlA and SAHndlB) are input arguments that contain handles to the Safe Arrays with the two matrices to multiply. The third argument (SAHndlR) is an output argument; the subroutine creates a Safe Array with the product of the two input matrices and returns its handle in SAHndlR. The fourth argument (iError) is also an output argument and is used to return error flags to the calling program. Note that most of the code in subroutine MatrixProduct is dedicated to check the characteristics of the input Safe Arrays, and to ensure that they conform to the requirements of matrix multiplication [22]. The subroutine, however, only performs the most basic error checking to keep the example as simple as possible. A Safe Array can have any number of dimensions and any type, so you must verify that the content and dimensions of passed Safe Arrays are adequate to the task performed by the subroutine. In addition to the verifications included in the example above, the subroutine should also check that handle SAHndlR is null, or that the array represented by this handle in the calling program can be reallocated.

 

The matrix multiplication is performed through a call to Fortran intrinsic function matmul using Fortran matrices that have been mapped (using SafeArrayAccessData) to the data-block of the involved Safe Arrays.

 

Using your Fortran compiler, you can export the subroutine into Example44.dll. You may have to add some compiler-dependent directives to the subroutine to do this (see Chapter 5 for details regarding the compiler you are using).

 

To call MatrixProduct from Visual Basic or Visual Basic for Applications, you need to declare the subroutine as an external procedure, and provide the location of the DLL file that contains the procedure. Start by creating a new Visual Basic project, and add a module file with the following declaration:

 

Declare Sub MatrixProduct Lib "Example44.dll" (a() As Single, b() As Single, c() As Single, iError As Integer)

 

As in the previous example, we assume that Example44.dll and the visual basic executable reside in the same directory. You can now add a simple form to drive the subroutine (Figure 4.6).

 

The code associated to Form1 is listed below:

 

'Matrices dimensions

Dim m As Long

Dim n As Long

Dim q As Long

 

'Matrices to multiply

Dim a() As Single 'Matrix a(mxn)

Dim b() As Single 'matrix b(nxq)

 

'Utility variables

Dim i As Long, j As Long

 

    

Private Sub Form_Load()

    Randomize

End Sub

 

 

Private Sub cmdGenerate_Click()

    

    Dim TmpText As String

    

    'Generate random sizes for matrices a and b

    m = Int((50 - 2 + 1) * Rnd + 2)

    n = Int((50 - 2 + 1) * Rnd + 2)

    q = Int((50 - 2 + 1) * Rnd + 2)

 

    'Dimension matrices a and b

    ReDim a(1 To m, 1 To n)

    ReDim b(1 To n, 1 To q)

    

    'Fill matrix a with random values

    For i = 1 To n

        For j = 1 To m

            a(j, i) = Rnd

        Next j

        For j = 1 To q

            b(i, j) = Rnd

        Next j

    Next i

 

    'Show matrices in text boxes

    Label1.Caption = "Matrix A (" & m & "," & n & ")"

    TmpText = ""

    For i = 1 To m

        For j = 1 To n

             TmpText = TmpText & Format(a(i, j), "0.000")

            If j < n Then

                TmpText = TmpText & vbTab

            End If

        Next j

        TmpText = TmpText & vbCrLf

    Next i

    txtArrayA.Text = TmpText

    

    Label2.Caption = "Matrix B (" & n & "," & q & ")"

    TmpText = ""

    For i = 1 To n

        For j = 1 To q

           TmpText = TmpText & Format(b(i, j), "0.000")

            If j < q Then

                TmpText = TmpText & vbTab

            End If

        Next j

        TmpText = TmpText & vbCrLf

    Next i

    txtArrayB.Text = TmpText

    

    Label3.Caption = "Result (mxq)"

    

End Sub

 

 

Private Sub cmdMultiply_Click()

    

    Dim MatrixR() As Single

    Dim m As Long

    Dim q As Long

    Dim iError As Integer

    Dim TmpText As String

    

    'call Fortran subroutine

    Call MatrixProduct(a, b, MatrixR, iError)

    

    If iError < 0 Then

        MsgBox "Error in MatrixProduct. Try generating another set " & _

               "of input matrices", vbOKOnly

    Else

        'get dimensions from resulting matrix

        m = UBound(MatrixR, 1)

        q = UBound(MatrixR, 2)

        

        'Show resulting matrix in text box

        Label3.Caption = "Result (" & m & "," & q & ")"

        TmpText = ""

        For i = 1 To m

            For j = 1 To q

                TmpText = TmpText & Format(MatrixR(i, j), "0.000")

                If j < q Then

                    TmpText = TmpText & vbTab

                End If

            Next j

            TmpText = TmpText & vbCrLf

        Next i

        txtArrayR.Text = TmpText

    End If

 

End Sub

 

 

When you run this program and click button cmdGenerate, the program executes subroutine cmdGenerate_Click, which creates two random matrices and shows them in the corresponding text controls (i.e. txtArrayA and txtArrayB). Note that the cmdGenerate_Click chooses the dimensions of the matrices randomly, but makes sure they conform to the multiplication operation (i.e. that the second dimensions of the first matrix is equal to the first dimension of the second matrix).

 

Clicking button cmdMultiply, executes subroutine cmdMultiply_Click, which calls Fortran subroutine MatrixProduct. You should note that MatrixR, which contains the result after calling MatrixProduct, has been declared as a dimensionless array. MatrixProduct takes care of creating variable MatrixR and returning it to the Visual Basic program.

 

Passing Variants