|
||||||||||||||
![]() |
||||||||||||||
|
|
||||||||||||||
|
Calling
Fortran Dlls from Visual Basic By Marco A. Garcia Copyright
©1999 Canaima Software
Introduction Mixed language programming can be double edge knife. It allows you to obtain the best that each programming language can offer, but at the same time, it can be a frustrating, and time-consuming experience. Visual Basic and Fortran are frequently used to build applications in science and engineering, due to several reasons. The newcomer Visual Basic (VB) offers a large set of tools that allow programmers to build Windows applications fast and easily. Although Visual Basic excels for developing user interfaces, VB programs have a tendency to be slow, especially for numeric calculations. Fortran, on the other hand, has a long-standing tradition in science and engineering. Fortran is easy to learn and use, and Fortran compilers produce the fastest numerical manipulation code, with the least effort for the programmer. However, Fortran compilers are lagging behind in terms of access to new technologies, and even developing a simple GUI in Fortran can become a complex project. In this series of articles, I will deal with some specific aspects of mixed language programming with Fortran and Visual Basic. I will refrain from explaining the basic aspects of interfacing these two languages. Transferring basic data types (like integers, real, double precision, etc.) between both languages is straight forward, and is explained in the user manuals of most Fortran compilers for windows. Instead, this series will deal with interfacing Fortran and Visual Basic subroutines with more complex data types. In particular, we will see how to handle Visual Basic's Bstring, SafeArray and Variant types in Fortran. These three data types are common place in VB programs. All VB strings are Bstrings, and all VB arrays are Safe-Arrays. Bstrings, Safe-Arrays and Variants are also the basic data types used in OLE and COM. OLE automation and COM services are so easy to use from Visual Basic due, in part, to the fact that the language offers these types as intrinsic definitions. On the other hand, Fortran does not have facilities to handle any of these data types. As a direct consequence, attempts to use OLE automation or COM services from Fortran is, putting it in mild terms, a nightmare for almost any Fortran programmer. This also explains why a quick look in most Internet Fortran Newsgroups will reveal a large number of posts by frustrated programmers attempting to understand why they cannot pass a VB array or string to their Fortran subroutines. This series is broken into four articles. This first article explains the fundamental concepts related to Bstrings, Safe-arrays and Variants. It explains what these data types are, and how they are represented in the computer's memory. The second article focuses on Bstrings. The third article deals with safe-arrays. The last article introduces the use of variants in Fortran. Each of the last three articles contains an associated Fortran library. These libraries offer definitions, functions and subroutine that allow the manipulation of these data types from Fortran. The libraries were developed by Canaima Software and are freely available from their web site (http://www.canaimasoft.com). However, make sure you read their license agreement before you include these libraries in any commercial development project.
Interfacing Fortran and Visual Basic: General Issues. Interfacing Visual Basic and Fortran is in general a one way process. You can have a main Visual Basic Program that calls Fortran subroutines stored in a DLL library. The other way around, i.e. a main Fortran program calling Visual Basic subroutines, is possible but complicated, and out of the scope of these articles. This does not mean, however, that you cannot pass arguments back and forth between Fortran and Visual Basic. By default, both VB and Fortran, pass arguments by reference. This is, rather than the value of an argument, the compilers pass a pointer to the argument. So your Fortran subroutines are able to modify the value of the arguments passed to them from a Visual Basic calling program. Fortran subroutines that will be called from Visual Basic must be compiled into a DLL. The details of how this is done are compiler-dependent. With Digital Visual Fortran, for example, you declare a subroutine as:
Subroutine CallMeFromVB(iArg1,iArg2) !DEC$ATTRIBUTES DLLEXPORT:: CallMeFromVB
!DEC$ ATTRIBUTES ALIAS: "CallMeFromVB"::CallMeFromVB
integer(2)::iArg1,iArg2
:
:
:
end sub
The subroutine should be compiled and linked using the appropriate compiler options to create the DLL. To call this subroutine from Visual Basic, you must declare the subroutine and the name of the DLL where it exists. For the previous example, your Visual Basic Declaration would look like this:
Declare Sub CallMeFromVB Lib "d:\MyProjects\MyFortranSubs.dll" _
(byref A1 as integer, byref A2 as integer)
In this case, using the byref modifier is a little redundant, but in general is good programming practice to write interfaces explicitly.
Bstrings: Strings the Visual Basic way. Object Linking and Embedding (OLE) has become an integral part of the Windows operating system. OLE is a collection of higher-level functionality, mainly related to the user interface, which uses the COM (Component-Object Model) standard for communication between its components. The distinction between OLE and COM is not always clear, but this lack of distinction is not relevant here. What is important, however, is that the OLE/COM combination was designed as an OS-independent standard. As such, it makes use of some particular data types that, at least in theory, are language independent. The OLE/COM standard defines two string types; OLE Strings and Bstrings. OLE strings are 16-bit strings; each character in the string takes 2 bytes. They are usually null-terminated, but this is not an OLE requirement. From a Fortran point of view, an OLE string can be represented as vector of 2-byte integers, each element containing a character. Why use 16-bit characters when there is already an 8-bit ANSI standard? Well, let's just say that the 8-bit ANSI standard is a little ethnocentric. 8 bits are enough to represent most characters in western alphabets, but many other alphabets have more than 255 characters, so the ANSI standard is not enough. In the tradition of the way most programming languages handle string arguments, OLE string arguments are also passed as pointers to the first character in the string. This is more obvious in some languages than in others. For example, a C/C++ programmer must explicitly pass a string argument as pointer to the string. In Fortran, the programmer passes a string argument as the variable containing the string; however, the compiler still passes a pointer to the string (plus the length of the string). In Fortran, the fact that the compiler is passing a pointer, instead of the string itself is more or less hidden from the programmer point of view. Bstrings are a little bit more complex than OLE strings. A Bstring is a structure with a header, that contains information about the length of the string and the memory allocated for its storage, and an OLE string that is null-terminated. This can be seen better with a simple sketch of the way a Bstring is stored in memory (Figure 1):
When you define a Bstring in Visual Basic, what you have is a pointer to the first character of the memory area where the Bstring is stored. And as with OLE strings, when you call a subroutine with a Bstring argument, you are actually passing this pointer. In Visual Basic, this is a transparent process to the programmer. When a programmer calls a VB subroutine with Bstring argument he/she writes the name of the variable that contains the string. The Visual Basic compiler/Interpreter takes care of converting this argument to a pointer. Now that you know how Visual Basic stores Bstrings in the computer's memory, you may be tempted to start writing a Fortran subroutine that receives a Bstring as a vector of integer(2), and then converts this vector into a character string. Well, this could be a satisfactory way to do it, if your Fortran subroutine does not have to modify the content of the Bstring. I am a little bit more ambitious. I want my Fortran subroutines to be able to receive a Bstring, modify its content if necessary, and then pass back the modified Bstring to the calling Visual Basic program. To do this, you must be aware of certain rules governing the behavior of Bstrings. The general idea behind these rules is that manipulation of Bstrings should be done by the operating system, and not by your program. Letting Windows take care of this manipulations is inherently more stable than doing it yourself. In Visual Basic these processes happen behind the scene. But when you assign a value to a Bstring in Visual Basic, the compiler/interpreter is in fact calling several Windows' API functions to perform this operation. In Fortran you would need to this explicitly. In his article "Strings the OLE Way", Bruce McKinney (1996) describes the eight golden rules governing the handling of Bstrings: Rule 1: Allocate, destroy, and measure Bstrings only through the OLE API. Those who use their supposed knowledge of Bstring internals are doomed to an unknowable but horrible fate in future versions. (You have to follow the rules if you don't want bugs.) Rule 2: You may have your way with all the characters of strings you own. The last character you own is the last character reported by the OLE API function SysStringLen, not the last non-null character. You may fool functions that believe in null-terminated strings by inserting null characters in Bstrings, but don't fool yourself. Rule 3: You may change the pointers to strings you own, but only by following the rules. In other words, you can change those pointers by calling the appropriate OLE API functions. The trick with this rule (and rule 2) is determining whether you own the strings. Rule 4: You do not own any Bstring passed to you by value. The only thing you can do with such a string is copy it or pass it on to other functions that won't modify it. The caller owns the string and will dispose of it according to its whims. Rule 5: You own any Bstring passed to you by reference as an in/out parameter. You can modify the contents of the string, or you can replace the original pointer with a new one (using the appropriate OLE API functions). Rule 6: You must create any Bstring passed to you by reference as an out string. The string parameter you receive isn't really a string-it's a placeholder. The caller expects you to assign an allocated string to the unallocated pointer, and you'd better do it. Otherwise the caller will probably crash when it tries to perform string operations on the uninitialized pointer. Rule 7: You must create a Bstring in order to return it. A string returned by a function is different from any other string. You can't just take a string parameter passed to you, modify the contents, and return it. If you did, you'd have two string variables referring to the same memory location, and unpleasant things would happen when different parts of the client code tried to modify them. So if you want to return a modified string, you allocate a copy, modify the copy, and return it. Rule 8: A null pointer is the same as an empty string to a Bstring. Experienced C++ programmers will find this concept startling because it certainly isn't true of normal C++ strings. An empty Bstring is a pointer to a zero-length string. It has a single null character to the right of the address being pointed to, and a long integer containing zero to the left. A null Bstring is a null pointer pointing to nothing. There can't be any characters to the right of nothing, and there can't be any length to the left of nothing. Nevertheless, a null pointer is considered to have a length of zero. When dealing with Bstrings, you may get unexpected results if you fail to take this into account. When you receive a string parameter, keep in mind that it may be a null pointer. Visual Basic makes all uninitialized strings null pointers. Visual Basic enforces these rules automatically. The process is completely transparent to the programmer. For other programming languages, OLE offers a set of API functions to enforce the rules. For example, to create a Bstring from Fortran, you must call the API function SysAllocString. This function returns a pointer to the newly allocated string. To remove the Bstring from memory, you call another API function, SysFreeString, and so forth. The details of how this is done are explained in the second article of this series; Handling String Arguments: the Bstrings library for Fortran.
Variants: The workhorse of OLE automation. Variants are the ultimate data type. Any intrinsic type available in Visual Basic or Fortran could, in theory be stored into a variant. How can a variant do this?, well, a variant is a structure, rather than a simple data type. The structure of a variant type is as follows (in Fortran notation):
type VARIANT
sequence
integer(2)::vt
integer(2):: wReserverd1
integer(2):: wReserverd2
integer(2):: wReserverd3
union
map
integer(1):: bVal
end map
map
integer(2):: iVal
end map
map
integer(4):: lVal
end map
map
real(4):: fltVal
end map
map
real(8):: dblVal
end map
map
integer(2)::boolVal
end map
map
integer(4):: scode
end map
map
type(CURRENCY):: cyVal
end map
map
real(8):: date
end map
map
integer(4):: bstrVal
end map
map
integer(4):: punkVal
end map
map
integer(4):: pdispVal
end map
map
integer(4):: parray
end map
!a bunch of other mapped types not relevant here
end union
end type
So as you see, the idea of this structure is that it is able to contain any other possible type. The VT field should contain a type tag (and possibly a tag modifier) indicating what the union contains. Technically, that's all you really need to know. Every time you put a variable into a variant, you also insert its type tag. Every time you want extract a variable, you first check its type so that you know what to do with it. OLE provides a group of system functions that help manage these tasks. We will see them how they work in a later article (Handling Variants: the Variants library for Fortran). As you may have noticed already, the variant type uses a union modifier. If it didn't the use of variants would represent a terrible waste of memory. Because all the possible types that can stored into a variant share the same memory space, the space used by a variant is that of the larger data type. So a variant takes 16 bytes of storage (8 bytes for the fixed part of the structure, and 8 bytes for the shared portion). The main rules governing the manipulation of variants is that you must make sure the VT field contains the appropriate tag, indicating the type of value stored in the variant. Failures to do this will often result in crashed programs. Visual Basic implements variants as an intrinsic type, so you can use them almost careless in your programs. In Fortran you can manipulate variants directly, by changing the values in the structure, or you can use the more convenient subroutines provided by Canaima Software's library.
Safe-Arrays: The Safe OLE Way of Handling Arrays. Most modern programming languages can handle some form arrays. However, if you are going to make arrays available across process, machine, and operating system boundaries, your array definitions must offer a higher level of protection that those available in most languages. The OLE way of doing arrays (which is exactly the same as the Visual Basic way) is through a protected data structure called a SafeArray. The SafeArray structure does not contain the array data; instead, it has a description of the array and a pointer to where the data is located. A SafeArray definition looks something like this (using Fortran syntax):
type SAFEARRAY
integer(2)::cDims
integer(2)::fFeatures
integer(4)::cbElements
integer(4)::cLocks
integer(4)::pvData
type(SAFEARRAYBOUND)::rgsabound(1)
end type
The cDims field contains the number of dimensions of the array. The fFeatures field is a bitfield indicating attributes of a particular array. The cbElements field defines the size of each element in the array. The clocks field is a reference count that indicates how many times the array has been locked. When there is no lock, you're not supposed to access the array data. The pvData field is a pointer to the memory location where the array data is stored. The last field is vector of boundary structures. By default, there's only one of these, but if you define multiple dimensions, the appropriate system function will reallocate the array to give you as many array elements as you need. The SAFEARRAYBOUND structure looks like this (in Fortran notation):
type SAFEARRAYBOUND
integer(4)::cElements
integer(4)::lLbound
end type
Manipulation of Safe-Arrays is done in a manner similar to Bstrings. OLE defines a rich set of APIs to create, destroy and manipulate this type of data. Again, because Safe-Arrays are an intrinsic type in Visual Basic, calls to SafeArray manipulation functions in the OLE-API are hidden from the programmer. In Fortran, however, the programmer must make these calls explicitly. The SafeArray Library developed by Canaima Software, and explained in article "Handling Arrays: the SafeArrays library for Fortran", will make life easier for those of you that want to use Safe-Arrays in your Fortran programs.
What's Next? Now that we have a better understanding of these three fundamental data types, we can better explain the details of how to use them in Fortran. The next articles in this series will focus on each of the three data types, one at a time. Because handling these data types directly in Fortran can be tedious (or in some cases almost impossible), we will make heavy use of three libraries developed by Canaima Software. These libraries make working with Bstrings, Safe-Arrays and Variants a much easier proposition. The articles also show how to create Fortran subroutines that can handle arguments of these types in a friendly and elegant way. |
||||||||||||||
|
Download a PostScript copy of this article |
||||||||||||||
|
Copyright
© 1998-2000 Canaima Software
For questions regarding this site, sent an e-mail to webmaster@canaimasoft.com |
||||||||||||||