contents   index   previous   next



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.