Get a little more control: Calling a C DLL from Visual Basic

VB is a good general-purpose language, but sometimes--like when you're working directly with low-level hardware--you need a little C code to get the job done. This article explains how to write a function in C that's usable via the VB Declare statement.

Visual Basic is a comfortably high-level language, keeping the programmer nicely isolated from the vagaries of the platform, hardware, and sometimes even VB's own type system. That makes it a good language for general-purpose development. But sometimes, you need a little more control than VB can easily give you, so you are forced to turn to another language, like C.

There are a variety of reasons why you'd want to put parts of an application's functionality in a C DLL. Here are some examples:
  • ·        Working with code that's inherently incompatible with VB: VB can link only to libraries written to conform to a certain calling convention. On the other hand, C can link to libraries using any number of calling conventions.
  • ·        Converting complex variable types into VB-compatible types: Some variable types, such as memory pointers and unsigned numeric variables, are difficult, if not impossible, to use from VB.
  • ·        Implementing direct hardware access: Using VB to work directly with hardware such as serial ports is well-nigh impossible, but C has no intervening runtime layer and can easily work directly with hardware.
  • ·        Reusing code: A lot of MSDN sample code is written in C, and it's generally easier to use code someone else has written in C than to try rewriting it into VB.
  • ·        Maximizing speed and efficiency: C code is typically faster and more efficient than equivalent VB code, especially for memory-intensive operations.

A (very) simple example
I've put the code for a simple C DLL in Listing A. Calling the CallMe function results in the display of a message box informing you that the code you called lives in a C DLL that you called from VB. I'll leave more advanced applications of this technique up to you.

Even if you don't know C at all, after staring at the code long enough, you should notice a few things: CallMe returns an integer value that's always zero, it accepts no arguments, and it's declared with a funny-looking identifier you may not have seen before, __stdcall.

Holiday decorations for calling conventions
Function calling conventions define a standard way for a called function to behave. For everything to work correctly, both the calling and called code have to agree on the same calling convention. With VB, you don't really have a choice: It supports only functions that use the StdCall convention. If you attempt to call code that doesn't use that convention, you'll wind up with a Bad DLL Calling Convention error.

StdCall says that function parameters should be pushed onto the stack in reverse order (from rightmost to leftmost in the function's declaration), specifies that by default, arguments be passed by value, and makes the called function responsible for getting arguments out of the stack. This is also known as the FORTRAN calling convention.

The C compiler supports multiple calling conventions, so you must declare (or decorate, in C parlance) functions that should follow the StdCall convention with the keyword __stdcall (that's two underscores followed by the name of the convention), as I've done with the CallMe function in Listing A. Alternatively, if you are using Visual C++, you use the /Gz compiler switch to force all undecorated functions to use StdCall, in which case you don't have to mess with the decorations at all.

Exporting definitions
So far so good, but before we can actually use the CallMe function, we have to export it so that it can be seen from the outside world. For that, we need to create a definition file, which is essentially just a text file that includes some information useful for the C compiler. The definition file for our CallMe function can be found in Listing B.

Definition files follow a standard format and always contain at least two pieces of information:
  • ·        FUNCTION—the name of the DLL the definition file defines
  • ·        An EXPORTS section—lists by name and ordinal number all functions in the DLL that should be callable by an application.

Once you have your properly decorated source and a definition file, you simply need to compile the DLL and you are ready to go. I'll assume that since you have Visual Basic, you also have or can get access to Visual C++. In that case, you simply create a new, blank project workspace, name it whatever you want your DLL file to be named (which should be the same as the LIBRARY section of your definition file—in our case, CDLLFROMVB), add these two source files to it, and build the project. If you happen to be using another C compiler or IDE, your steps may vary slightly.

Working from the VB end
Now you just need an appropriate VB Declare statement for the CallMe function. In this example, it's simple, as you can see from the code in Listing C. The Declare statement should specify the name or ordinal of the DLL function, the name of the DLL file, an argument list, and finally, the function's return type. Unless the DLL file is located in a folder that appears in the system's path statement, you'll have to specify a path along with the file name.

Since some C variable types either don't map directly to their VB counterparts or don't have VB counterparts, getting the Declare statement into the correct format can be an aggravating experience. C-to-VB type conversion is a complicated subject, and I only have space for a few guidelines here. If you are interested in a definitive reference, I highly recommend picking up a copy of Dan Appleman's Visual Basic Programmer's Guide to the Win32 API.

Because you're likely to be trying this sort of approach for the sake of convenience, it's in your best interest to make things as simple as possible for yourself and only use easily converted types as your C functions' arguments and return types. Numeric types are the easiest to work with, but bear in mind that VB's Integer type is actually only 16 bits long and maps to C's short type. Likewise, C's int type is 32 bits long, which means you should use a Long on the VB end. For floating-point variables, C's float and double types equate to VB's Single and Double types, respectively.

Tying up some loose strings
Arrays and strings are a special case. VB arrays should be passed ByRef from VB and received on the C side as a far pointer, declared as type __far *varName, where type is the variable type and varName is the variable name, to a variable of the same type as the base type of the array. VB's String type is actually an object type (BSTR) without a direct C analog. So you should pass fixed-length strings from VB and receive them as far pointers to char variables (like char __far *theString) in your C code. Check out this MSDN article for some examples of passing strings into a C DLL. Although it was written for VB 3 and is therefore somewhat dated, most of the information in it pertaining to C function declarations still applies.

I hope I've managed to clarify some of what is generally considered to be a black art by VB programmers. At the very least, you should now have less cause for concern should someone throw some C code your way in response to a question.





Editor's Picks