Don't let yourself be intimidated by tales of horrific crashes and wildly complex code
when faced with using the Win32 API in your Visual Basic applications. It's an easy to
use and exceptionally powerful way to extend your VB projects.
The API has hundreds of available functions that perform all manner of services.
In most cases it's easy to use if you follow a few simple rules.
It's already installed on every computer your app will run on.
The only cost is a little bit of coding.
I'll repeat the last point again: The only cost is a little bit of coding
. Part of
the allure of Visual Basic for many beginning programmers is the visual
VB. You can quickly and easily create simple applications by drawing controls on forms
and binding them together with simple tidbits of code here and there. Because of VB's
visual nature, many beginners are intimidated when faced with actually having to sit down
and write non-visual
code. The reality of the situation, however, is that any
non-trivial application will require a considerable amount of coding to make it work.
There's no reason to feel overwhelmed when faced with the need to do some coding.
Any API function that can be used from VB can be setup and called with a handful of lines
of code. Taken in small chunks it's easy to understand and a great way to extend the
features of your application as well as your programming skills.
The Win32 Application Programming Interface (API) is probably the most powerful add-on
component available for Visual Basic. Hundreds of functions are available to perform
a wide variety of tasks. Unfortunately, many Visual Basic programmers do not take
advantage of these powerful functions - often because of tales they may have heard
of complexity, general protection faults, and other nasty but unfounded problems.
What is the API?
The API, quite simply, is the programming interface for Windows. Applications written for
Windows call API functions to perform common windows tasks, such as creating and destroying
windows, controls, and menus; accessing system services such as the display, keyboard and
mouse input, and printing; and many other functions. Functions in the API are provided in
several files known as Dynamic Link Libraries (DLLs). The DLLs in the Win32 API export, or
make public, many of their functions which can then be called by any Windows application.
Tools for API Programming
Armed with some information, an API reference, and a few simple rules to follow,
you can put the Win32 API to work for you in your Visual Basic applications.
Here's what you'll need:
The Win32 SDK
The Win32 Software Development Kit (SDK) is a comprehensive reference to
the functions available in the Win32 API. If you have VB4 or VB5 Professional
or Enterprise Edition, the SDK information can be found in the MSDN version
that ships with Visual Basic. MSDN (Microsoft Developer's Network) is also
on the web at http://www.microsoft.com/msdn/.
Note: You'll need to subscribe to MSDN to get a truly comprehensive reference to
Win32, but any of the other sources will be more than enough for you to do what you
need to do from VB.
The API Viewer Applet
The API Viewer applet that ships with Visual Basic will provide the Declare statements
you need to include in your modules before you can call API functions.
An Understanding of C and Hungarion Notation
While the API viewer applet will supply Declare statements that you can cut and paste
into your modules, an understanding of how the functions work and the data types they
use is helpful. The Declaring API Functions section will
provide more information on C and Hungarian notation.
A Bit of Courage and Common Sense
Fear not the API, but save your work often and follow the rules.
Calling a function in the API is a two-step process. Before making the function call from within
your Visual Basic code, you must write a Declare statement so that Visual Basic knows how to
call the external function. The Declare statement is comprised of several parts and looks much
like a VB procedure declaration. The syntax for the Declare statement is show below.
Declare template for Sub procedures:
[Public | Private] Declare Sub name Lib "libname" [Alias "aliasname"] [([arglist])]
Declare template for Function procedures:
[Public | Private] Declare Function name Lib "libname" [Alias "aliasname"] [([arglist])] [As type]
Let's look at each of the components of these statements.
Public or Private
This is a standard VB scope modifier. Declare statements must be placed in the
declarations section of a module (they cannot appear within a procedure), and
the Public or Private keyword determines if the procedure is available
throughout the application or only within the module in which it is declared.
Rather than declaring API functions as Public, you can declare them as Private
and supply a VB "wrapper" function that handles some of the setup work for calling
the API. You can then make the wrapper function public to expose the underlying API
call to the rest of your application.
Declare statements placed in form or class modules cannot be Public. Only Private
declares are allowed.
Declare Sub or Declare Function
This indicates whether or not the procedure returns a value.
Note: You can declare functions as subs and ignore the return values, but it is
This is the name of the function as it will be used in your Visual Basic code.
Note: Some API functions have illegal names in Visual Basic or for other reasons
require that a name and alias be used. However, (for reasons which will be explained
later) I recommend that you supply a name and alias for all API declares.
The Lib is the name of the library (DLL) where the function is located.
Note: The quotation marks are literals in the Declare statement. The libname
must be enclosed in quotes as shown.
The Alias is the name as it is exported by the library where it is located.
Note: Like the libname, the quotes around the aliasname are literals and are
required when an alias is used.
This is a standard Visual Basic argument list. It indicates what parameters are expected
by the procedure, the data types of the parameters, and whether they are passed ByVal or
Getting this part wrong is likely to result in a GPF at run-time.
The parenthesis are literals and required.
When declaring the arguments to an API function, you can use the special
As Any data type specification in addition to the normal VB data types. While
convenient in some situations, using As Any is somewhat dangerous because VB
performs no type checking on the variables passed.
Used only for functions, this specifies the data type of the return value.
This may be a little easier to illustrate with an example. The following
is the Declare statement for the FindWindow function. FindWindow returns
a window handle given the window's title bar text or classname.
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Let's look at each component of this declare:
The function is declared as Private to the module. This is because a
wrapper function for the API call is provided elsewhere in the module, so
the Private Declare statement ensures that only the wrapper will be
This procedure returns a value (a window handle).
Lib "user32" Alias "FindWindowA"
The procedure is exported from the user32 library, where it can be found
Note: The Win32 API often provides both ANSI and Unicode versions of
its functions that handle strings. From VB, you will normally be using
the ANSI versions, thus the "A" appended to the function name.
ByVal lpClassName As String, ByVal lpWindowName As String
The function takes two parameters, both ByVal strings.
The function returns a long integer value.
The FindWindow function also illustrates another common API feature: redundant parameters.
FindWindow will accept a window title and a window classname, but either by itself is
enough to get a window handle returned (assuming of course that such a window exists).
However, to avoid needing to provide two separate functions that serve essentially the
same purpose, the API has constructed the function so that either or both values can be
supplied. If you only want to supply one of them (and in most cases you will), simply
pass a binary zero for the other parameter. To pass binary zero (a null in C lingo, but
not to be confused with a VB Null which is something else entirely), you can either
enter ByVal 0& as the parameter value or use the intrinsic constant vbNullString.
Using the API Viewer Applet
There are hundreds of functions, constants, and structures (user defined types in VB) in
the API, but it's not necessary for you to know them all. VB ships with the API Viewer
applet, which can be used to cut and paste the Declare statements, constants, and type
definitions for the API code you need to use into your VB modules.
Using the API Viewer couldn't be easier. Simply load the appropriate library (normally
win32api.txt or win32api.mdb), select either Constants, Declares, or Types from the
combo box, and start typing the name you're searching for. When you find the desired
code, click Add to move it to the Selected Items list and when you're done click Copy
to put the code on the clipboard, ready for pasting into your VB module.
Note: When you first load win32api.txt, the API Viewer will ask you if you want to
convert it to a database file. Answer yes and from then on load win32api.mdb. If
you're feeling adventurous, you can fire up the registry editor and go the the key
HKEY_CURRENT_USER\Software\VB and VBA Program Settings\API Viewer\File List\ and
remove or modify the entries so that win32api.txt no longer appears on the file menu.
Unfortunately, I haven't yet found a way to simply have the viewer always load
win32api.mdb. You need to go to the File menu each time you start the viewer.
Roll Your Own Declares
The API Viewer, while convenient, is not always complete and in some cases may not be
correct. Additionally, you may at times need to work with functions exported from DLLs
where there is no library available to load into the API viewer. However, if you
understand a little bit of the Hungarian notation used by the Win32 SDK in documenting
its functions, you can easily convert most of them yourself.
Converting the function name itself is fairly straightforward. If the function takes a
ByVal string as one or more of it's parameters, append an "A" to the name, otherwise use
the name as it appears in the SDK. This is admittedly something of a guess, but in
most cases will be correct.
Converting the parameter list can be a bit more difficult. However, keep in mind that
nearly all API procedures are going to be expecting 32-bit long integer values for
every parameter - including those listed as strings. You may be asking yourself:
"How can this be?"
First, with the move to 32-bit windows, nearly all parameters that
were 16-bit values have been changed to 32-bit values (VB long integers).
Second, VB performs a little behind the scenes wizardry with string parameters.
The API doesn't work with strings in the same manner as Visual Basic. API
functions expect (in nearly all cases) to recieve null-terminated C strings
instead of VB strings. However, you don't pass a string value to the API
directly through the parameter list. Instead, you pass the address of a
null-terminated string. VB handles all of this for you if you declare a
parameter as a ByVal string. Rather than passing the actual string value
"by value", it converts it to a null-terminated string and passes the
address of that converted string. This address is, not surprisinly, a
32-bit long integer value.
The API will be expecting to receive one of two things in its parameters: a
value it can work with or the address of a variable containing a value it
can work with. The key to knowing if the API expects a value or an address can
often be found in the Hungarian notation for the parameter name in the function
template listed in the SDK. If the parameter name is prefixed with "lp", it's
likely that the API is going to be expecting an address. In C Hungarian notation,
"lp" is shorthand for a long pointer - in other words an address in memory.
The most common use is probably "lpsz" which is a long pointer to a null terminated
string (a VB ByVal As String declaration), but you may also need to pass pointers
to longs or user-defined types. If you need to pass an "lp" to the API for anything
but a string, pass ByRef to send the address of your variable to the API.
In nearly all cases, however, you will be passing values to the API - not addresses
or pointers. There are a few exceptions to this:
Don't let all this tech-talk get you down - it's not nearly as difficult as it seems.
In the vast majority of cases, you can simply cut and paste the API declares, constants,
and type definitions you need from the API Viewer and move on to coding the calls
without a second thought. All of the above is provided more to help you gain an
understanding of what's happening. You'll rarely, if ever, actually need to hand
roll an API declare (unless you're just one of those hard-core tpes and like to
roll your own).
As noted above, strings are nearly always passed to the API using ByVal As String,
which sends the address of a null-terminated string to the API.
Any variable which will be modified by the function is passed ByRef.
Structures (VB types) are always passed as addresses.
Declaring the API functions you need is only half the battle, and in most cases is the
easy half. Calling API functions can range from being as easy as a straight VB procedure
call to requiring several or even dozens of lines of pre-call and post-call work to
extract the data you need.
Most calls are fairly straightforward, but there is one exceptionally common setup and
teardown sequence of steps that need to happen in a large number of API calls dealing
with strings. Because the API is dealing with C strings (basically just arrays of bytes or
integers and nearly always terminated with a binary zero), the API will expect a few things
of you before you call a function that will provide a string as part of the data it returns.
Unlike VB functions, API functions will never return string values directly. If a procedure
needs to return a string, it will expect you to provide a string in the parameter list and
extract your result from the string variable you provided. The function itself will typically
return either a status code or the length of the returned string. In all cases, the API will
also expect you to make the string you supply large enough to handle the value to be returned.
This is easiest to illustrate with a simple example. The following is the Declare statement
for the Win32 GetComputerName function. This function will return the machine name of the
computer via a string parameter.
Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" ( _
ByVal lpBuffer As String, _
nSize As Long) As Long
GetComputerName takes two parameters: lpBuffer and nSize. The lpBuffer parameter is a ByVal
string that will receive the computer name from the API. The nSize parameter is used to
supply the API with the size of the lpBuffer string when the function is called and also
receives the size of the result placed in lpBuffer by the function. If the function succeeds,
it returns a non-zero value and if it fails it returns zero. Note that both parameters are
being passed as pointers: the first as an "lpsz" and the second as a pointer to the long
The following code fragment shows what a typical call to GetComputerName might look like:
Dim lResult As Long
Dim sBuffer As String
Dim lSize As Long
' make room for the result
' 256 should be more than adequate for all platforms
lSize = 256
sBuffer = Space(lSize)
' make the call
lResult = GetComputerName(sBuffer, nSize)
' test the result
If lResult <> 0 Then
' sBuffer contains the name
' nSize contains the length of the name less the null
sBuffer = Left$(sBuffer, nSize)
' the function failed, handle it here
Prior to calling the function, 256 is assigned to nSize and the sBuffer string is
assigned 256 spaces. (As far as I know, 256 is way more than enough characters for
a legal computer name on any windows platform. The heavily padded buffer may be
slightly sloppy coding, but for this purpose we're not worried about wasting a
few dozen bytes in a local procedure.
Once the parameters have been setup, the API function is called and the result is
tested. If the value of lResult is non-zero (indicating success), then two things
will be true:
The computer name will have been placed into the sBuffer string.
The nSize variable will contain the length of the computer name in sBuffer,
not including the null terminator.
At this point, it's a simple matter of using the Left$() function to extract everything
to the left of the terminating null.
Functions that return a size in the parameter list in this manner will nearly
always return the required size if the buffer was too small to hold the result.
You can use this information to test for a large enough buffer by comparing the value
returned to the original value you provided. You can also use the "double-calling"
technique, where you call once with a buffer that's known to be too small, the using
the return values to allocate a buffer that's just large enough to hold the required
result. Unless the procedure is in the middle of a tight loop, there's rarely a
measurable performance penalty for using the double-calling method.
Similarly, many functions will return string sizes as the function return value.
Again, these functions will typically provide the required size if the buffer was
too small to hold the result.
Finally, some API functions are just plain lazy and tell you nothing at all about the
size of the returned string. In these cases, you can use the InStr() function to search
for the position of the first null character (use vbNullChar) and then use Left$() to
extract everything to the left of the null.
DON'T MISMATCH THE SIZE YOU SEND AS PARAMETER WITH THE ACTUAL SIZE OF THE BUFFER!
Yes, I'm shouting, and for good reason. The Win32 API expects you to do what you said
you're going to do. If you tell it you provided a buffer of a given size, it will
write up to that number of characters at the address provided. Consider what would
happen if you did this:
Dim sBuffer As String
Dim nSize As Long
Dim lResult As Long
nSize = 2048
sBuffer = Space(248)
lResult = GetComputerName(sBuffer, nSize)
If we assume for the moment that this function could return a string as long as the value
of nSize, we would be in for some serious trouble. We've told the function that we're
supplying a 2048 byte buffer, but in fact only allocated 248 bytes. If the function
found 2048 bytes of characters to return, it would write over whatever happened to be
in memory following the first 248 bytes of the string we sent. This could be other program
data, program code, anything at all.
If you're really, really lucky, Windows will abruptly terminate your application with a
General Protection Fault. Unfortunately, it may well be the case that your application
will run merrily along, having corrupted it's own code or data in some random location.
This may manifest itself as strange behavior or data elsewhere, random crashes at
unrelated locations, and all other sorts of nearly impossible to debug problems.
If you don't already have it and you plan to do much work with the Win32 API, find yourself a
copy of Dan Appleman's Visual Basic Programmer's Guide to the Win32 API
. This book is
the bible of API programming, and among the things it provides is Applman's Ten Commandments
for programming the API. I won't repeat them here because 1) that would be plagiarism and 2)
you should buy his book anyway.
However, I'll offer my own version of API programming rules, but without Appleman's more
colorful Old Testament styling.
Save your work often.
Study the SDK documentation and know what you're calling before you call it.
Use ByVal and ByRef correctly.
Match sizes of string buffers and structures to the associated size parameters.
Save, save, save.
Build a VB wrapper function around any API call that requires even a single line
of setup code so that you don't forget to do the setup somewhere in your app.
Save your work more often. You should expect to get a few crashes while debugging
anything but the most trivial of API code, so don't blame me if you lose an hours
work because you crashed VB and hadn't saved your code.
Don't confuse VB strings with C strings.
Don't confuse VB Nulls with C nulls.
Study the documentation again. Know what each parameter means, what it expects for data,
and whether it is passed ByVal or ByRef.
Test return values explicitly.
Don't confuse C's TRUE and FALSE with VB's True and False. In the API, if a function states it
will return TRUE if successful, test for a non-zero result. Don't compare the result to
VB's True keyword.
Did I mention you should save your work often?
This page, like nearly every other I've ever seen on programming the API, has a few
scare tactics in it. Before you shy away from the API, however, keep in mind
that it's not nearly as difficult as it looks and in most cases is no harder than
coding straight VB functions.
The API is a powerful tool, but is intended for use by grown-ups. By this I don't mean
veteran programmers, just programmers that are thorough and accurate in their coding.
If you have sloppy programming habits, doing things like naming your variables "Jane"
and "Spot", ignoring return values to save the line of code it takes to test them,
not paying attention to ByVal or ByRef passing, then you should clean up your act
before you inflict total instability on your users by adding API programming (compared
to the near total instability you'll have using those habits without the API).
If, however, you're willing to write a little bit of simple code and use a little
bit of good judgement, the API can be the greatest add-on component available to you -
and it's available for the cost of just a few lines of code.
By Joe Garrick