Using The Win32 API


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 nature of 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
    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.

Declaring API Functions

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 not recommended.
  • name
    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.
  • Lib "libname"
    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.
  • Alias "aliasname"
    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.
  • (arglist)
    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 ByRef.
    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.
  • As type
    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:
  • Private
    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 called directly.
  • Function FindWindow
    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 as FindWindowA.
    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.
  • As Long
    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:

  • 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.
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).

Calling API Functions

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 integer nSize.

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
    ' success
    ' sBuffer contains the name
    ' nSize contains the length of the name less the null
    sBuffer = Left$(sBuffer, nSize)
    ' the function failed, handle it here
  End If
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:

  1. The computer name will have been placed into the sBuffer string.
  2. 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.



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.

Rules for API Programming

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.

  1. Save your work often.
  2. Study the SDK documentation and know what you're calling before you call it.
  3. Use ByVal and ByRef correctly.
  4. Match sizes of string buffers and structures to the associated size parameters.
  5. Save, save, save.
  6. 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.
  7. 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.
  8. Don't confuse VB strings with C strings.
  9. Don't confuse VB Nulls with C nulls.
  10. Study the documentation again. Know what each parameter means, what it expects for data, and whether it is passed ByVal or ByRef.
  11. Test return values explicitly.
  12. 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.
  13. 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

If you enjoyed this post, subscribe for updates (it's free)

Error in code listing

There is an error in the GetComputerName code. it uses "nSize" where in fact "lSize" is necessary. These lines are wrong:
lResult = GetComputerName(sBuffer, nSize)
sBuffer = Left$(sBuffer, nSize)

Should be:
lResult = GetComputerName(sBuffer, lSize)
sBuffer = Left$(sBuffer, lSize)

Yes, you are right

Yes, you are right

Using The Win32 API to get computername

I hope a simple sample.