Conversion of a Static Fortran Library to a
Digital Visual Fortran
DLL
Callable from Visual Basic
Matthew Nicoll, Cypher Consulting
www.CypherConsulting.com
September 26, 1997
Note: This document was created for a conversion project for a client called "IOS Header".. I have chopped out much of the project-specific material, but there are still some project-specific references left.
Creating the DLL
Source Code modifications
DLLEXPORT statements
Each routine in the library which is to be callable from VB must have the following line inserted after the SUBROUTINE or FUNCTION statement:
!DEC$ATTRIBUTES DLLEXPORT :: <subroutine_name>
(!MS$ may be substituted for !DEC$)
For example, for routine HRC_PUT_ITEM_I:
SUBROUTINE HRC_PUT_ITEM_I(SECTION,LABEL,IIN, ITEMTYPE,STAT)
!MS$ATTRIBUTES DLLEXPORT :: HRC_PUT_ITEM_I
These DLLEXPORT statements must NOT be present for the static library compilations.
Replaced all WRITE(*,*) Statements
WRITE(*,*) statements were used for reporting unexpected conditions, often before an abort STOP statement. In a DLL, standard output goes nowhere, so I replaced all WRITE(*,*) statements with a call to a new routine called HR_SPLAT, which writes the message to the message file, and also to the standard output.
Compiling
The compile command for compiling routines for the DLL is the same as for the static library. (Pre-compile the source code if necessary to include the DLLEXPORT statements.)
Linking
The DLL is created with the following link command:
link /DLL /OUT:%headpath%\headdll.dll *.obj ioslib.lib /map:headdll.map
where environment variable headpath points to wherever you want the output files to end up.
In addition to the DLL file, an import library called headdll.lib, and an export library called headdll.exp are created. The import library must be linked with by Fortran programs which are going to use the DLL. (I am not clear on when the export library is used - something to do with when two DLL's are calling each other!)
Linking the DLL is more like linking an executable than creating an object library, so to add, replace or remove routines, you just re-link the whole thing.
VB to Fortran Calling Mechanisms and Type Matching
Declaring DLL routines in VB
In Visual Basic, DLL routines to be called must be declared - at the Module level. If declared private, the declaration applies only to that module. If declared public, any procedure in the application can call the declared DLL routine. Thus it would make sense if the IOS Header generation procedure created a VB module containing public declarations of all the callable Header routines.
Example:
Public Declare Sub HRC_GET_ITEM_I Lib "HEADLIB.DLL" _
Alias "_HRC_GET_ITEM_I@32" _
(ByVal section As String, ByVal slen As Long, _
ByVal label As String, ByVal llen As Long, _
ByRef iout As Long, _
ByVal itemtype As String, ByVal itlen As Long, _
ByRef stat As Long)
If no path is on the library name, the library is looked for first in the same directory as the EXE file of the currently executing application, then in the current directory, then in the various Windows system directories, then in the directories specified in the PATH.
The Alias name is the subroutine name, preceded by an underscore, and followed by @n, where n is the number of bytes in the argument list: 4 for each argument. Note that there is an extra length argument at the VB end for each String argument. (Passing character strings is discussed in more detail below.)
ByRef is the default, and is thus optional. Both Fortran and VB call by reference. ByVal is necessary for character strings and their lengths, as discussed below.
Passing Arguments:
The following table matches Fortran types used in Header routines to their Visual Basic equivalents.
Fortran |
Visual Basic |
* See notes |
LOGICAL*1 |
Byte |
* |
INTEGER*2 |
Integer |
|
INTEGER*4 |
Long |
|
REAL*4 |
Single |
|
REAL*8 |
Double |
|
Structure |
User-defined Type |
* |
CHARACTER*n |
String * n |
* |
All types except character strings are called by reference.
Character strings.
VB strings by default are stored as a reference to a pointer to the string, so by passing by Value, the result is passing by reference to the string! (See p 649 of VB V4.0 Programmer's Guide)
Fortran expects a hidden argument after each character string argument: the length of the string, as a 4-byte value.
Visual Basic fixed and variable length strings are passed to Fortran in the same manner. Where the Fortran routine declares the Character length explicitly (as opposed to CHARACTER*(*)), declare a string of the same length in VB
Example:
Fortran subroutine:
SUBROUTINE FSUB(C)
!MS$ATTRIBUTES DLLEXPORT :: FSUB
CHARACTER*(*) C
WRITE(*,*)C
END
Visual Basic code to call FSUB:
Public Declare Sub FSUB Lib "EXAMPLE.DLL" _
Alias "_FSUB@8" _
(ByVal c As String, ByVal clen As Long)
Sub Test
Dim C As String
C = "Hello from Visual Basic"
CALL FSUB(C, Len(C))
End Sub
Character arrays
String arrays in Visual Basic are stored as arrays of pointers (thus allowing a different length for each element of the array). To create a memory data layout in VB which is the same as how character arrays are stored in Fortran, declare an array of fixed-length strings inside a VB user-defined type (structure). To correspond to the Fortran character array name In an argument list, send the VB structure by reference, then the length of one of the elements, by value.
Fortran:
SUBROUTINE FSUBSTRINGARRAY(C)
!MS$ATTRIBUTES DLLEXPORT :: FSUBSTRINGARRAY
CHARACTER*6 C(4)
...
END
Visual Basic:
Type StringArray
s(1 To 4) As String * 6
End Type
Public Declare Sub FSUBSTRINGARRAY Lib "EXAMPLE.DLL" _
Alias "_FSUBSTRINGARRAY@8" _
(ByRef sa As StringArray, ByVal clen As Long)
Sub TestStringArray()
Dim sa As StringArray
Dim clen As Long
clen = Len(sa.s(1))
Call FSUBSTRINGARRAY(sa, clen)
End Sub
Non-Character Arrays
Declare the array in VB, and code the first element of the array in the argument list. In the VB interface declaration, just declare the type of a single element of the array. Coding the first element sends the address of the start of the array, which is all Fortran wants. It is up to the programmer to make sure the array is declared big enough.
Example:
Dim Data (1 to 20) As Single
...
Call HRC_READ_DATA_R4(uin, rn, Data(1), stat)
LOGICAL vs Byte and Boolean
Fortran LOGICAL variables may be declared size 1, 2 or 4. In all cases, only the first byte is used, and is set to 0 for false, and1 for true (which is minus one in two's complement form).
A Visual Basic Boolean variable occupies 2 bytes. All 8 bits are set 1 for true, and all 0 for false.
In the IOS Header library, LOGICAL*1 is used for logical variables.
The Visual Basic Byte type corresponds well to Fortran LOGICAL*1. A Byte can be used just like a Boolean in VB: it can be set to True (255) or False (0) and can be used in place of Boolean in If statements. When passed to Fortran, Fortran will set the byte to 1 for True, which also tests true in VB.
Structures
Structures (User-Defined Types) are passed by reference (to the first byte in the structure) in both Fortran and Visual Basic. As long as they are defined identically (using the type correspondences defined above) in both languages, and are padded identically, they can be passed with no trouble.
The default padding in Digital Fortran and in Visual Basic are the same except for REAL*8 = Double.
PADDING
Integer*2 |
padded to start on 2byte boundary |
Integer*4 |
start on 4 byte boundary |
Real*4 |
start on 4 byte boundary |
Real*8 |
DVF: /align:dcommons -- start on 8 byte boundary |
DVF: /align:commons – start on 4-byte boundary |
|
VB: start on 4 byte boundary |
|
Strings |
start immediately after previous item, no padding |
Arrays |
packed end to end, no padding |
Adjacent Strings |
also packed without padding |
Logical*1 |
no padding |