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.