CanaimaSoft
f90SQL
Search
Contents
f90ADO
Links
f90VB
 
 
 
 

Beat Visual Basic sluggish math, create a Safe Array multiplication DLL using Fortran (Example 4.4)

In this example, we 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. This subroutine does not include the additional compiler-dependent code necessary to indicate calling convention, modify name-mangling or to create a DLL. These topics are explained in detail in User Manual:

 

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

Note that for simplicity, subroutine MatrixProduct is designed to work only on bi-dimensional matrices. It cannot multiply, for example, a matrix times a vector. . 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 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).

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:

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.

 

 
Copyright © 1998-2000 Canaima Software
For questions regarding this site, send an e-mail to
webmaster@canaimasoft.com