Declaring an array of strings arguments
This section discusses the very common situation of having to pass an array of strings from a program written in Visual Basic or Visual Basic for Applications into a Fortran subroutine.
Example 5.2
This example implements the simple (and inefficient [27]) bubble-sort algorithm to sort an array of strings. The subroutine that performs the sorting is written in Fortran, and uses both f90VBSafeArrays and f90VBBstrings libraries. The subroutine has two array arguments, one containing the strings to be sorted, and the other containing the sorted strings. Here is the code for Fortran subroutine BBStrSort and auxiliary subroutine BSort:
subroutine BBStrSort(USAHndl, SSAHndl, CaseSensitive, iError)
!This subroutine uses the bubble sort algorithm
!to sort a vector of strings (USAHndl). The
!sorted vector is returned in SSAHndl
use f90VBDefs
use f90VBBstrings
use f90VBSafeArrays
implicit none
!Subroutine arguments
integer(SAFEARRAY_KIND),intent(in)::USAHndl
integer(SAFEARRAY_KIND),intent(inout)::SSAHndl
integer(VARIANT_BOOL_KIND),intent(in)::CaseSensitive
integer(SHORT_KIND),intent(inout)::iError
!Accessory variables
integer(VARTYPE_KIND)::vt_type
integer(HRESULT_KIND):: iRet
integer(LONG_KIND)::i
!Fortran arrays to map Safe Arrays
integer(BSTRHNDL_KIND),pointer::VectS(:)
!Copy unsorted SafeArray into output array
call SafeArrayCopy(USAHndl,SSAHndl,iRet)
if (iRet.ne.S_OK) then
iError=-1
return
endif
!Map output array into a Fortran array for
!faster manipulation of the BSTR handles
call SafeArrayAccessData(SSAHndl,VectS,iRet)
if (iRet.ne.S_OK) then
iError = -1
goto 100
endif
!Convert from VB's external BString format into
!standard BStrings so they can be handled by
!standard-comforming f90VB subroutines
do i=lbound(VectS,1),ubound(VectS,1)
call BstrConvFromVB(VectS(i),VectS(i))
enddo
!call BSort subroutine
if (CaseSensitive) then
call BSort(VectS,lbound(VectS,1),ubound(VectS,1),.true.)
else
call BSort(VectS,lbound(VectS,1),ubound(VectS,1),.false.)
endif
!Convert back to external VB Bstring format
do i=lbound(VectS,1),ubound(VectS,1)
call BstrConvToVB(VectS(i))
enddo
100 continue
call SafeArrayUnAccessData(SSAHndl,iRet)
end subroutine BBStrSort
subroutine BSort(Vect, min, max, CSens)
!sort a vector of BString handles
use f90VBDefs
use f90VBBstrings
implicit none
!subroutine arguments
integer, intent(in)::min, max
integer(BSTRHNDL_KIND)::Vect(min:max)
logical,intent(in)::CSens
!local variables
integer::i,j
integer(BSTRHNDL_KIND)::TmpHndl
do i=min, max-1
do j=i,max
!Compare BStrings
if (StrCompare(vect(j),'<',vect(i),CSens)) then
!swap handles
TmpHndl=vect(j)
vect(j)=vect(i)
vect(i)=TmpHndl
endif
enddo
enddo
end subroutine BSort
As in other general examples, the Fortran code above does not include compiler-dependent directives to account for name-mangling, calling conventions, etc. These directives are discussed below for each particular compiler. Also note that error and array conformance checking have been kept to a minimum.
The first operation performed by BBStrSort is to copy the input array into the output array. The subroutine could have been designed to work in a single array (i.e. the sorted array is passed back in the same array used as input). However, using two arrays has a pedagogical value, because it shows how to create Safe Arrays that can be returned to Visual Basic. To copy the input array into a new output array, you can use SafeArrayCopy.
Next the BBStrSort maps a Fortran array into the data-block of the output array. After this, the entries in the Fortran array represent BString handles. From the previous discussions on BString arguments, you may remember that Visual Basic converts strings passed to Declared procedures to a special kind of BString that is not completely compatible with the OLE standard type. f90VB procedures only work with standard BStrings, so you must convert the strings stored in the input array into standard BStrings. This is done calling BstrConvFromVB for each handle in the input array.
BSort is a fairly simple subroutine that performs the sorting of the strings. The only relevant line in the subroutine is the one that calls f90VB function StrCompare, which compares two BStrings and returns a Boolean True if the comparison is positive. Note that because the array contains handles and not the real strings, sorting is relatively fast, involving only moving the handles to different positions in the array.
Finally, BBStrSort reformats the sorted Safe Array of BStrings into the format Visual Basic expects from external subroutines (by calling BstrConvToVB).
If you are concerned about the performance hit of these operations, a test run of BBStrSort with a very large array (5000 strings) takes about 35 seconds to sort. The same algorithm written in Visual Basic takes about 80 seconds [28].
You can compile and link BBStrSort and BSort into Example52.dll following the instructions indicated below for your particular compiler.
You are now ready to create a Visual Basic front end for subroutine BBStrSort. Start with a new Visual Basic project, and add a new module file with the following declaration:
Declare Sub BBStrSort Lib "Example52.dll" (UStrArray() As String, SStrArray() As String, CaseSensitive As Boolean, iError As Integer) [29]
This gives Visual Basic the information it needs to locate the DLL file where BBStrSort resides (we are assuming the DLL and the VB executable reside in the same directory), as well as the arguments that must be passed to the subroutine. Note that all arguments are passed by reference (Visual Basic's default).
Next, create a new Form as illustrated in Figure 5.1.
Add the following code to the form:
Option Explicit
Option Base 1
'Form variables
Dim UStrVect() As String
Private Sub Form_Load()
Randomize
End Sub
Private Sub Command1_Click()
'Generate a vector with a random number of entries
'each entry with a random string
Dim TmpText As String
Dim m As Long, n As Long, k As Long
Dim i As Long, j As Long
'Generate random number of entries for the vector
m = Int((100 - 2 + 1) * Rnd + 2)
ReDim UStrVect(m)
'Generate strings
TmpText = ""
For i = 1 To m
'generate a random string size (up to 25 characters)
n = Int((25 - 1 + 1) * Rnd + 1)
For j = 1 To n
'generate a random character
Do
k = Int((122 - 65 + 1) * Rnd + 65)
Loop Until (k < 91 Or k > 96)
UStrVect(i) = UStrVect(i) & Chr(k)
Next j
TmpText = TmpText & UStrVect(i)
If i < m Then
TmpText = TmpText & vbCrLf
End If
Next i
'Refresh the control showing the unsorted strings
Text1.Text = TmpText
'Clean control showing sorted string
Text2.Text = ""
'Show the size of the vector of strings
Text3.Text = m
End Sub
Private Sub Command2_Click()
Dim SStrArray() As String
Dim CaseSens As Boolean
Dim iError As Integer
Dim TmpText As String
Dim i As Long, n As Long
If Check1.Value Then
CaseSens = True
Else
CaseSens = False
End If
'Call BBStrSort in Fortran-f90VB DLL
Call BBStrSort(UStrVect, SStrArray, CaseSens, iError)
If iError < 0 Then
MsgBox "An error occurred in sorting subroutine", vbOKOnly
Exit Sub
End If
'Refresh the control showing the sorted strings
TmpText = ""
n = UBound(SStrArray)
For i = LBound(SStrArray) To n
TmpText = TmpText & SStrArray(i)
If i < n Then
TmpText = TmpText & vbCrLf
End If
Next i
Text2.Text = TmpText
End Sub
Private Sub Command3_Click()
'This sub implements the same inefficient sorting algorithm
'as BBStrSort. It is included here only for performance
'comparison reasons
Dim SStrArray() As String
Dim CaseSens As Boolean
Dim TmpText As String
Dim i As Long, j As Long, n As Long
Dim SStrUBound As Long
If Check1.Value Then
CaseSens = True
Else
CaseSens = False
End If
SStrUBound = UBound(UStrVect)
ReDim SStrArray(1 To SStrUBound)
SStrArray = UStrVect
'Bubble Sort the array
For i = 1 To SStrUBound - 1
For j = i To SStrUBound
If CaseSens Then
If SStrArray(i) > SStrArray(j) Then
TmpText = SStrArray(j)
SStrArray(j) = SStrArray(i)
SStrArray(i) = TmpText
End If
Else
If UCase(SStrArray(i)) > UCase(SStrArray(j)) Then
TmpText = SStrArray(j)
SStrArray(j) = SStrArray(i)
SStrArray(i) = TmpText
End If
End If
Next j
Next i
'Refresh the control showing the sorted strings
TmpText = ""
For i = 1 To SStrUBound
TmpText = TmpText & SStrArray(i)
If i < SStrUBound Then
TmpText = TmpText & vbCrLf
End If
Next i
Text2.Text = TmpText
End Sub
The following subsections explain changes and additions that need to be made on subroutine BBStrSort so that you can create Example52.dll with the different Fortran compilers supported by f90VB. We will also find very basic descriptions of how to compile and link BBStrSort with these compilers.
Absoft Pro Fortran
Changes to BBStrSort
To indicate that BBStrSort should conform to the standard calling convention you need to add the qualifier STDCALL in front of the subroutine declaration.
After this change, the code for BBStrSort should be as follows (changes appear in bold):
stdcall subroutine BBStrSort(USAHndl, SSAHndl, CaseSensitive, iError)
!This subroutine uses the bubble sort algorithm
!to sort a vector of strings (USAHndl). The
!sorted vector is returned in SSAHndl
use f90VBDefs
use f90VBBstrings
use f90VBSafeArrays
implicit none
!Subroutine arguments
integer(SAFEARRAY_KIND),intent(in)::USAHndl
integer(SAFEARRAY_KIND),intent(inout)::SSAHndl
integer(VARIANT_BOOL_KIND),intent(in)::CaseSensitive
integer(SHORT_KIND),intent(inout)::iError
(the rest of the code stays the same)
end subroutine BBStrSort
Note that subroutine BSort is only called from BBStrSort, so you don’t make any changes in this subroutine.
Compiling BBStrSort
By default, Absoft Pro Fortran does not initialize declared variables. Un-initialized handles can have spurious values, which would be interpreted by Window's Safe Array services as addresses of allocated Safe Arrays. The initialization problem can be approached in two ways:
Explicitly initialize to zero all declared Safe Array handles: In BBStrSort you can do this by adding the following statements at the beginning of the source code:
SSAHndl =0
Use the –s compiler switch, which forces the compiler to treat internal variables as static and initialized to zero.
If you use the first approach you can compile BBStrSort using the following command-line instruction:
f90 –c Example52.f90 -p "%f90VBAPFModDir%"
Which produces the object file Example52.obj. The –p compiler switch is used to indicate the path of the f90VB modules. This path is stored in environment variable f90VBAPFModDir, created when you installed f90VB.
Linking BBStrSort
Absoft Pro Fortran will mangle the name of subroutines with the STDCALL qualifier. To solve this problem, you need to create an accessory file (Example52.als) for the linker containing aliases for the mangled names. In this case, the file contains a single line:
_BBStrSort@16 BBStrSort
This file is used later to tell the linker that subroutine _BBStrSort@16 will be known externally as BBStrSort.
You also need another file (Example52.xps) to indicate the name of the Fortran procedures that will be exported to the DLL. In this case, the exports file contains only one line:
BBStrSort
Use the following command-line instruction to link the library:
lnk /dll Example52.obj /exports:Example52.xps /aliases:Example52.als absRT0.lib fmath.lib f90math.lib "%f90VBAPFLibDir%\f90VBSafeArrays.lib"
The /dll switch instructs Absoft's linker to create Example52.dll. The /exports switch loads the file with the list of procedures to export into Example44.dll. The /aliases switch loads the aliases file with the external name of the subroutine. Note that you link against the f90VBSafeArrays library. The path of this library is stored in environment variable f90VBAPFLibDir, which was created by the f90VB installation program.
Compaq (Digital) Visual Fortran
Changes to BBStrSort
To indicate that BBStrSort should conform to the standard calling convention, you need to add a DEC$ATTRIBUTE STDCALL compiler directive to the declaration of the subroutine:
!DEC$ATTRIBUTES STDCALL:: BBStrSort
The STDCALL attribute also changes the default method used to pass arguments, so you need to tell the compiler that arguments to subroutine BBStrSort are passed by reference. You can do this using the DEC$ATTRIBUTE REFERENCE directive:
!DEC$ATTRIBUTES REFERENCE:: USAHndl, SSAHndl, CaseSensitive, iError
To indicate that the subroutine must be exported to the DLL as a public procedure, you add the DEC$ATTRIBUTES DLLEXPORT compiler directive to the subroutine declaration:
!DEC$ATTRIBUTES DLLEXPORT:: BBStrSort
Compaq Visual Fortran will mangle the name of subroutines with the STDCALL attribute. You can use another compiler directive to indicate an alias for the mangled name of the exported subroutine:
!DEC$ATTRIBUTES ALIAS: 'BBStrSort'::BBStrSort
The first argument is the alias (i.e. the name by which the subroutine would be available to external programs using the DLL), the second argument is the Fortran name of the subroutine.
After these changes, the code for BBStrSort should be as follows (changes appear in bold):
subroutine BBStrSort(USAHndl, SSAHndl, CaseSensitive, iError)
!This subroutine uses the bubble sort algorithm
!to sort a vector of strings (USAHndl). The
!sorted vector is returned in SSAHndl
!DEC$ATTRIBUTES STDCALL:: BBStrSort
!DEC$ATTRIBUTES DLLEXPORT:: BBStrSort
!DEC$ATTRIBUTES ALIAS: 'BBStrSort':: BBStrSort
!DEC$ATTRIBUTES REFERENCE:: USAHndl, SSAHndl, CaseSensitive, iError
use f90VBDefs
use f90VBBstrings
use f90VBSafeArrays
implicit none
!Subroutine arguments
integer(SAFEARRAY_KIND),intent(in)::USAHndl
integer(SAFEARRAY_KIND),intent(inout)::SSAHndl
integer(VARIANT_BOOL_KIND),intent(in)::CaseSensitive
integer(SHORT_KIND),intent(inout)::iError
(the rest of the code stays the same)
end subroutine BBStrSort
Compiling and Linking BBStrSort
You can compile and link BBStrSort in a single operation, using the following command-line instruction:
f90 Example52.f90 /dll /out:Example52.dll /module:"%f90VBDVFModDir%" "%f90VBDVFLibDir%\f90VBSafeArrays.lib"
The /dll switch instructs CVF's linker to create a dynamic link library whose name is provided by the /out switch. The /mod compiler switch is used to indicate the path of the f90VB modules. This path is stored in environment variable f90VBDVFModDir, created when you installed f90VB. Note that you link against the f90VBSafeArrays library. The path of this library is stored in environment variable f90VBDVFLibDir, also created by the f90VB installation program.
Lahey/Fujitsu Fortran 95
Changes to BBStrSort
The only change you need to do to compile subroutine BBStrSort with Lahey’s compiler is to add the DLL_EXPORT directive to indicate that the subroutine should be exported to a DLL. After this change, the code for BBStrSort should be as follows (changes appear in bold):
subroutine BBStrSort(USAHndl, SSAHndl, CaseSensitive, iError)
!This subroutine uses the bubble sort algorithm
!to sort a vector of strings (USAHndl). The
!sorted vector is returned in SSAHndl
DLL_EXPORT BBStrSort
use f90VBDefs
use f90VBBstrings
use f90VBSafeArrays
implicit none
!Subroutine arguments
integer(SAFEARRAY_KIND),intent(in)::USAHndl
integer(SAFEARRAY_KIND),intent(inout)::SSAHndl
integer(VARIANT_BOOL_KIND),intent(in)::CaseSensitive
integer(SHORT_KIND),intent(inout)::iError
(the rest of the code stays the same)
end subroutine
The compiler directive DLL_EXPORT also indicates the external name of the procedure, so the subroutine becomes publicly available with the name that follows DLL_EXPORT.
Compiling and Linking BBStrSort
To compile BBStrSort with Lahey/Fujitsu Fortran 95 you need to use the special switch –ml msvb to indicate to the compiler/linker the appropriate name-mangling and calling convention. The command
lf95 Example52.f90 -dll -win -ml msvb -mod "%f90VBLF95ModDir%" -lib "%f90VBLF95LibDir%\f90VBSafeArrays.lib"
creates Example52.dll containing subroutine BBStrSort, which is ready to be called from Visual Basic. The -mod compiler switch is used to indicate the path of the f90VB modules. This path is stored in environment variable f90VBLF95ModDir, created when you installed f90VB. Note that you link against the f90VBSafeArrays library. The path of this library is stored in environment variable f90VBLF95LibDir, also created by the f90VB installation program.