CanaimaSoft
f90SQL
Search
Contents
f90ADO
Links
f90VB
ProductsServices ArticlesNewsProduct SupportOnline StoreAbout UsContact Us
Products
Services
Articles
News
Product Support
Online Store
 
About Us
Contact Us

Calling Fortran Dlls from Visual Basic
ARTICLE 2. Handling String Arguments: the BStrings library for Fortran

By Marco A. Garcia

Copyright ©1999 Canaima Software
All rights reserved

Bstrings (or BSTR) are the basic string type for Object Linking and Embedding (OLE-Automation) and Component-Object Model (COM) services. If Fortran programmers ever expect to take full advantage of these interesting new technologies, they need to get used to the idea that Bstrings are here to stay. Even if you are not interested in using OLE/COM, your Fortran programs may benefit from the use of Bstrings. Bstrings are allocated dynamically, can handle international alphabets and can store large amounts of text. Bstrings are also relatively stable because most to their handling is done through the Windows operating system.

In this article I will introduce the Bstrings Library for Fortran (Bstrings.lib), developed by Canaima Software. The aim of this library is to offer Fortran programmers a set of well-designed functions and subroutines to manipulate Bstrings, and to easily convert Fortran strings to Bstrings or back. The memory representation of Bstrings and the rules governing their manipulation were explained in a previous article. This article has a more practical approach. Here, we will see how to create and destroy Bstrings, how to exchange information with Bstrings, and how they can be used exchange data between your Fortran subroutines and your Visual Basic or C/C++ programs.


Basic Definitions


Throughout this short manual, we will be dealing with three different types of strings. Fortran strings are the strings all Fortran programmers are familiar with. These are Fortran's intrinsic strings defined using the CHARACTER(LEN=<n>). Fortran strings are, in most cases, static and padded with spaces to the right. A typical definition of a Fortran string looks like this:

character(len=10)::MyFString 

In this document, Fortran strings are frequently called Fstrings for brevity.

OLE-Strings are 2-byte strings (each character in the string takes 2 bytes of storage) and are null terminated. With Canaima Software's Bstring Library, OLE-Strings are always defined as vectors of integer(kind=2). To maintain compatibility with future changes and among compiler vendors, you should use constant OLECHAR_KIND instead of 2 for the kind. This constant is defined in the Bstrings Library. A typical definition of an OLE-String looks like this:

integer(OLECHAR_KIND)::MyOleStr(25) 

Because OLE-Strings are null terminated, you should always allocate an extra entry in the vector to account for the null terminator. For example, to define an OLE-String that will contain the string "Example", you must allocate a vector with 8 elements. The first 7 will contain the ASCII representation of the characters in the string; the last entry will contain 0 (null terminator).

Bstrings were explained in a previous article. Bstrings are dynamically allocated by the operating system. Canaima Software's Bstring Library takes care for you of doing the appropriate OS calls to handle allocation, destruction and changes in Bstrings.

Canaima Software's Bstring Library contains three components. The library file (Bstrings.lib), which contains all the subroutines necessary to handle the strings. A module with definition of constants (BstringsDefs.mod), and a module with definitions of interfaces (Bstrings.mod). Before you can use these definitions in your Fortran code, you must USE the modules. You must also link your object files against the Bstrings.lib to create the executable.


Bstring Handles


In Fortran, Bstrings are manipulated through handles. A Bstring handle is nothing else but a 4-byte integer variable that contains the memory location (i.e. a pointer) of a Bstring. With Canaima Software's Bstring library, you can create Bstrings using a variety of methods. In all cases, however, what you get is a handle to the Bstring. This handle is used to identify a given Bstring for the rest of its life. The address stored in the handle is of little interest to most programmers. In fact this address may change after calling certain library functions or subroutines.


Creating (Allocating) and Destroying (Deallocating) Bstrings


The easiest method to create a Bstring is calling function BstrAlloc. The function returns a handle to the created Bstring. In the following example, we call function BstrAlloc to create a new Bstring and to initialize it with some text.

Program Example01
!This program requests a text input from the user
!and allocates a new Bstring with the text
!then it deallocates the Bstring before closing
use f90OLEDefs
use f90BStrings
implicit none
integer(BSTRHNDL_KIND)::BSTRHndl
character(Len=100) InputStr
print *,'Enter the text to initialize the Bstring:'
read (*,*) InputStr
!Allocate a new Bstring
BSTRHndl=BstrAlloc(InputStr)
if (BSTRHndl.eq.0) then
     print *,'System could not allocate Bstring'
else
     print *,'Bstring successfully allocated. Handle Value:', &
              BSTRHndl
endif
!Destroy Bstring and deallocate used memory
call BstrFree(BSTRHndl)
print *,'Bstring deallocated. Handle Value:', BSTRHndl
stop
end


When you create a Bstring, the system reserves a chunk of memory for the string and returns its address. Because this memory is reserved by the operating system, you should always destroy Bstrings before leaving your program. Otherwise, the memory is never returned to the system. To destroy a Bstring, you call subroutine BstrFree, passing the handle of the Bstring you want to destroy as the argument.

The argument InputStr in function BstrAlloc, can be a normal Fortran-style string, another Bstring or an OLE-string (more on OLE-Strings later). The function can be called with an additional argument indicating the size of the Bstring buffer (i.e. the amount of memory the operating system reserves for the new Bstring. See the section "Changing the Contents of a Bstring", later in this manual). If the system cannot allocate the necessary space for the Bstring, the returned handle has the value zero (0).


Changing the Contents of a Bstring


Once you have allocated a Bstring, to change its contents you use subroutine BstrReAlloc. This subroutine is called with three arguments; the handle of the Bstring you want to change (BSTRHndl), the new contents (InputStr) and a status flag (iRet). BstrReAlloc performs several operations. It allocates memory for a new Bstring, then copies the contents of InputStr into it, destroys the older Bstring, and finally, sets handle BSTRHndl to point to the new Bstring. If the whole operation is successful, argument iRet is set to OLE_TRUE, otherwise it is set to OLE_FALSE. As with BstrAlloc, argument InputStr can be a normal Fortran-style string, another Bstring or an OLE-string. If the original Bstring was created (when you called BstrAlloc) with a buffer large enough to hold the new contents, chances are the new Bstring will be located in the same address. However, you should never take this for granted.

The following example shows how to use BstrReAlloc. In the example, we allocate and initialize a new Bstring, and then change its contents.

Program Example02 
!This program allocates and initializes a Bstring
!then requests a new string from the user and 
!uses BstrReAlloc to replace the contents of the 
!original Bstring. 

use f90OLEDefs
use f90BStrings
implicit none
integer(BSTRHNDL_KIND)::BSTRHndl
character(Len=500)::InputStr
integer(HRESULT_KIND):: iRet 

!Allocate a new Bstring
BSTRHndl=BstrAlloc('Original Contents') 

if (BSTRHndl.eq.0) then
     print *,'System could not allocate original Bstring'
else
     print *,'Bstring successfully allocated. Handle Value:', &
             BSTRHndl
endif 

print *,'Enter the text to initialize the Bstring:'
read (*,*) InputStr 

!Changes contents of original Bstring
call BstrReAlloc(BSTRHndl,InputStr,iRet) 
if (iRet.eq.OLE_TRUE) then
     print *,'System successfully changed the contents' 
     print *,'of the Bstring. Handle Value:',BSTRHndl
else
     print *,'System could not change the contents'
     print *,'of the original Bstring'
endif
!Destroy Bstring and deallocate used memory
call BstrFree(BSTRHndl)
print *,'Bstring deallocated. Handle Value:', BSTRHndl 

stop
end 

Using StrCopy: A Generic Subroutine to Convert String Formats.


Canaima Software's Bstrings Library offers generic subroutine StrCopy, which allows users to convert strings to any of the three available formats (i.e. Fstrings, OLE-Strings and Bstrings). Subroutine StrCopy is called with three main arguments; DestStr is the destinations string, SrcStr is the source string and the iRet, an operation status flag. The subroutine also accepts several other optional arguments (see the Bstrings Library Reference for a complete description).

Arguments DestStr and ScrStr can be of any of the supported string types (Fstrings, OLE-Strings or Bstrings). If DestStr is a Bstring handle, StrCopy will allocate a new string if the passed handle is has a null, or reallocate the Bstring if the handle has been already allocated. The following program shows several examples of how StrCopy can be used.

program Example03 
!This program shows examples of how to use subroutine StrCopy 

use f90OLEDefs
use f90BStrings
implicit none 

integer(BSTRHNDL_KIND)::BSTRHndlA,BSTRHndlB
character(Len=100)::FStrA,FStrB
integer(OLECHAR_KIND)::OLEStrA(101), OLEStrB(101)
integer(HRESULT_KIND):: iRet
integer i 

print *,'Enter a string'
read(*,*) FStrA 

!Create a Bstring, initialize to FStrA
Call StrCopy(BSTRHndlA,FStrA,iRet)
if (iRet.eq.BSTRINGS_ERR) then
     print *,'Error creating Bstring A'
     stop
else
     print *,'Bstring A created. Handle Value:',BSTRHndlA
endif 

!Convert FStrA into an OLE string stored in OLEStrA
Call StrCopy(OLEStrA,FStrA,IRet)
if (iRet.eq.BSTRINGS_ERR) then
     print *,'Error creating OLE-String A'
     stop
else
     print *,'OLE-String A created.'
     print *,'Values:',(OLEStrA(i),i=1,StrLenTrim(OLEStrA))
     print *,'Characters:',(char(OLEStrA(i)),i=1, &
             StrLenTrim(OLEStrA))
endif 

!Copy BstringA to FStringB
Call StrCopy(FStrB,BSTRHndlA,iRet)
if (iRet.eq.BSTRINGS_ERR) then
     print *,'Error copying Bstring A to FString B'
     stop
else
     print *,'FString B created. Value:',trim(FStrB)
endif 

!Error checks removed from this section for brevity 
!Copy some text to OLEStrB
Call StrCopy(OLEStrB,'Sample text stored in OLE-String B',iRet)
print *,'OLE-String B created.'
print *,'Values:',(OLEStrB(i),i=1,StrLenTrim(OLEStrB))
print *,'Characters:',(char(OLEStrB(i)),i=1,StrLenTrim(OLEStrB)) 

!Create a Bstring, initialize to text in OLEStrB
Call StrCopy(BSTRHndlB,OLEStrB,iRet)
print *,'Bstring B created. Handle Value:',BSTRHndlB 

!Changes BstringA to text in BstringB
Call StrCopy(BSTRHndlA,BSTRHndlB,iRet)
print *,'Bstring A modified. Handle Value:',BSTRHndlA 

!Copy Bstring A to FString B
Call StrCopy(FStrB,BSTRHndlA,iRet)
print *,'FString B created. Value:',trim(FStrB) 

!Free Bstrings
call BstrFree(BSTRHndlA)
call BstrFree(BSTRHndlB) 

stop
end 

Other Functions available in Canaima Software's Bstrings Library


The Bstrings Library includes several additional functions and subroutines to handle the three supported string types. As a rule of thumb, procedures with names starting with Str are generic procedures that will work with any of the three string types. Procedures with names starting with Bstr perform operations that are specific to Bstrings. For a complete description of each function and subroutine, please see Canaima Software's Bstrings Library Reference.

The following table lists all the procedures and functions available in Canaima Software's Bstring Library.

 

Name Type Description
BstrAlloc Function Creates and initializes a new Bstring
BstrReAlloc Subroutine Reallocates and/or changes the contents of a Bstring
BstrFree Subroutine Deallocates (Destroys) and frees the memory used by a Bstring.
BstrConvToVB Subroutine Converts a standard 2-byte Bstring into a 1-byte Bstring (Used only for VB interfacing). Reallocates or creates target Bstring as appropriate.
BstrConvFromVB Subroutine Converts a 1-byte Bstring into a 2-byte standard Bstring (Used only for VB interfacing). Reallocates or creates target Bstring as appropriate.
StrCopy Subroutine Convert one string format to another. If the target string is a Bstring, then StrCopy will create or reallocate the Bstring as appropriate.
StrLen Function Returns the length of a string buffer.
StrLenTrim Function Returns the length of the string stored in a string buffer.
StrByteLen Function Returns the number of bytes used by a string buffer.
StrByteLenTrim Function Returns the number of bytes used by the string stored in a string buffer.
StrIndex Function Returns the starting position of a substring in a string
StrRepeat Subroutine Concatenates several copies of a substring into a single string. If the target string is a Bstring, StrRepeat will create or reallocate the Bstring as appropriate.
StrSubStr Subroutine Extracts a substring from a source string.
StrCompare Function Compares the contents of two strings.

 

Some of the functions and subroutines included in Canaima Software's Bstring library are redundant with intrinsic Fortran subroutines that handle Fstrings. For example, function StrLen, when used with a Fstring argument, is equivalent to FORTRAN's intrinsic function Len(). This redundancy was introduced on purpose. The objective of Canaima Software's Bstring Library was to offer a homogenous set of procedures that would work in the same way, regardless of the string types passed as arguments.


Handling String arguments in Fortran subroutines called from Visual Basic


When programming with mixed languages, you are frequently forced to understand the intricacies of the internal formats used to store different data types. Internally, Visual Basic stores all its strings as BStrings. In Visual Basic, a subroutine can declared string arguments by reference (using the ByRef argument modifier), or by value (using the ByVal argument modifier). Arguments received by reference can be modified by the Visual Basic subroutine; those declared by value are treated as constants inside the subroutine. When you pass a Bstring, either by value or by reference, you are passing a pointer to the Bstring. There is really nothing that would stop you from modifying this pointer, however, the rules to handling Bstrings tell you that you do not own Bstrings passed to you by value, and as such, you should no attempt to modify it. Visual Basic enforces these rules, but once you call an external subroutine (for example one written in Fortran), there is no way the compiler can make you abide by the rules. In theory, then, you can modify a Bstring passed to your Fortran subroutine, even if it is passed by value. However, good programming practice tells you that you should not do this. In part because of this, and in part because none of the available Fortran compilers for Windows offer libraries to handle Bstrings, the documentation of these compilers indicate that Visual Basic strings can only be passed in one direction; from the calling VB program to the called Fortran subroutine. With Canaima Software's Bstring Library, this limitation does not exist anymore. You can pass VB strings by reference, change them in your Fortran subroutine, and pass the modified string back to the calling VB program.

I have said several times that VB strings and Bstrings are the same. Internally VB handles all its string as Bstrings. A point that is frequently misunderstood, however, is that when Visual Basic calls functions declared as external (i.e. using VB's DECLARE statement), all string arguments are converted to a special type of Bstring. This special type (I will call it External VB Bstring) uses only one byte per character, instead of the two bytes per character used by standard OLE Bstrings. Why would Microsoft do such a thing? Well the only reason I can think of is to allow VB programs to call Windows' API functions, most of which expect C-style ANSI strings. But the strings passed by VB to external subroutines are real Bstrings, they still have the string length on front of the string, and they still must be handled as Bstrings, with the exception of the number of bytes they use to represent each character. The following figure illustrates how a normal Bstring and VB External-Bstring look like.

The meaning of this change of formats is that procedures that expect standard OLE Bstrings as arguments, will not work properly if called from Visual Basic through a DECLARE interface. Now, you may be thinking that Microsoft's developers went nuts on this; and I thought the same for a while, until I realized that VB can use OLE and COM services as naturally as if they were part of the language. In fact, Visual Basic is OLE in and out. So there is not reason to use DECLARE interfaces to access the OLE/COM functionality. Instead, VB developers designed the DECLARE interface as a mean to interface Visual Basic with other languages and the Windows' APIs; converting Bstrings to this special format when calling Windows' API functions makes much more sense.

When you pass a Bstring by reference to an external procedure, VB first calls an internal subroutine that creates a temporary special Bstring with the passed argument. This is the Bstring passed to the external procedure. On return, VB calls another internal subroutine that converts the special Bstring into an OLE standard Bstring. You should notice, however, that the string passed to the external procedure is still a Bstring, so the external procedure can only change it by using the appropriate Bstring APIs.

Canaima Software's Bstrings Library was designed to handle standard OLE Bstrings. Many of the procedures included in the library will fail if called with External Visual Basic Bstrings. The library, however, includes two special subroutines to deal with this problem. Subroutine BstrConvFromVB takes an External Visual Basic Bstring and converts it to a standard OLE Bstring. The subroutine BstrConvToVB does the opposite.

At this point we have seen most of the details we need to know to handle Bstring arguments in Fortran. Let's now see a simple example of how a Fortran subroutine can receive a Bstring argument, change it and return the changed Bstring to the calling Visual Basic program. The following Fortran subroutine receives a Bstring, finds its length and returns a new Bstring with a text indicating the length of the input Bstring.

subroutine BStringLength(BstrHndl) 

!Subroutine changes the passed Bstring to a text
!indicating its length in number of characters 

use f90OLEDefs
use f90BStrings
implicit none 

!Uncomment next lines if you are using DVF
!!DEC$ATTRIBUTES DLLEXPORT::BStringLength
!!DEC$ATTRIBUTES ALIAS: 'BStringLength'::BStringLength 

!Uncomment the next line if you are using Lahey Fortran 95 
!or Absoft Pro Fortran
!DLL_EXPORT BStringLength 

!Create an export file if you are using Salford Fortran 95 

!subroutine arguments
integer(BSTRHNDL_KIND),intent(inout)::BSTRHndl 

!internal variables
integer::BstrLength
character(Len=5)::CharBStrLen 

!Reformat Bstring to standard format
call BstrConvFromVB(BSTRHndl) 

!write length of passed Bstring to a character variable
BstrLength=StrLenTrim(BSTRHndl)
if (BstrLength.eq.BSTRINGS_ERROR) then
     CharBStrLen='ERROR'
else
     write(CharBStrLen,'(i5)') BstrLength
endif 

!write return Bstring
call StrCopy(BstrHndl,'The length of the passed Bstring was ' // &
             trim(CharBstrLen))      

!Reformat return Bstring to VB external format
call BstrConvToVB(BstrHndl) 

end subroutine 
     

To call the previous subroutine from a VB program, you need to put the subroutine into a DLL. The way you do this is compiler dependent, and out of the scope of this article. You should consult your compiler documentation for instructions on how to create a DLL that includes the previous function. Make sure you link the DLL against Canaima Software's Bstrings library (Bstrings.lib).

Once you have your Fortran DLL, open a new Visual Basic project. Add a text Box to the project's form and name it txtBox. Add also a command button and name it Go. Add the following instructions to the form's code:

Private Sub cmdGo_Click()
   Dim MyString As String
   MyString = txtBox.Text
   Call BStringLength(MyString)
   MsgBox (MyString)
End Sub 

  

Now, add a new module to the project, and define the interface to subroutine BstringLength using a declare statement. For example:

Declare Sub BStringLength Lib "MyDLLDirectoryPath" _
             (ByRef str As String) 
     

Replace MyDLLDirectoryPath by the path to your fortran DLL. You can now run the example. This is a screenshot of the running project.


Where to find Canaima Software's Bstrings library

Canaima Software's Bstrings library is available for Digital Visual FORTRAN (ver. 5.0 and 6.0), Absoft Pro FORTRAN (ver. 5.0 and 6.0), Lahey/Fujitsu FORTRAN 95 and Salford FORTRAN 95. The library can be downloaded from Canaima Software's web site (http://www.canaimasoft.com). Make sure you read the user license agreement. The library is free for personal and academic use only. Commercial or corporate users of the library must contact Canaima Software for information about how to obtain a license.

Download a PostScript copy of this article

Article2-BStrings.zip

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