Visual Basic

  1. Home
  2. Computing & Technology
  3. Visual Basic

VB.NET, SetEnvironmentVariable, and the Windows API

Using the Win API isn't as easy as it was in VB 6!

By Dan Mabbutt, About.com

May 25 2007

In VB 6, it was common to be forced to use Windows API calls to get things done. In VB.NET, it doesn't happen very often because VB.NET can do just about everything. Before .NET 2.0, using SetEnvironmentVariable was one one of those times when you did have to call the Win API. This article shows you how to do it in VB.NET. If you're using .NET 2.0 or later (and you should be), the Environment namespace does the same thing much more easily. The article Environmental Awareness! shows you how to do that.

Calling a Windows API in VB.NET, however, is quite a bit different than doing it in VB 6. (One more thing in a very long list.) And it's both harder and easier at the same time.

Let me explain ...

A number of years ago, a small technical startup asked me to figure out why VB 6 program calls to routines in a C++ system didn't work. To make a long story short, I figured out that the reason was that the C++ program expected pointer variables in a datatype that just couldn't be duplicated in VB 6. Short of recoding the C++ program, I couldn't solve the problem.

That won't ever happen in VB.NET!

The Win API Puzzle

As Microsoft puts it:

"Windows APIs represent a special category of interoperability. Windows APIs do not use managed code, do not have built-in type libraries, and use data types that are different than those used with Visual Studio."

Although VB.NET doesn't support every datatype that might be required, there is a way to match any inteface requirement. And calling the (old) SetEnvironmentVariable Windows API is a great example.

The syntax for the VB 6 Declare statement that has to be coded to use call the SetEnvironmentVariable Windows API is shown below. Microsoft, however, in their documentation for calling Windows API functions at MSDN, recommends that you use the Auto charsetmodifier so that's been added too. (You'll see below that this is a mistake, but it helps to illustrate what's going on behind the scenes so we're going to continue down this false trail for a while.)

Declare Auto Function SetEnvironmentVariable _
   Lib "kernel32" Alias "SetEnvironmentVariableA" ( _
   ByVal lpName As String, ByVal lpValue As String) _
   As Boolean

You can pair this up with a call like this ...

Dim SetEnvResult As Boolean
Dim EnvName As String = "AboutVB"
Dim EnvValue As String = "Grrrreat!"
SetEnvResult = SetEnvironmentVariable(EnvName, EnvValue)

... and it almost works. What happens is that you get is this environment variable

A=G

Ummmmmm ... Not quite what is required. Just the first character of each string is used rather than the whole thing.

In fact, it's amazing that it works at all. Reading the documentation for this API a bit more, we discover that ...

lpName
Pointer to a null-terminated string that specifies the environment variable whose value is being set.

lpValue
Pointer to a null-terminated string containing the new value of the specified environment variable.

Visual Basic strings are called BStr and are a long pointer to Unicode (if you run on NT/2000/XP). So far, so good. But the problem is that whenever you pass an argument to an API (in VB), a conversion is automatically done by VB behind the scene according to common language runtime rules (because C uses LPStr which is an ANSI null terminated string instead of Unicode). That's what the Auto charsetmodifier does.

But Microsoft has a way around that, too. The MarshalAsAttribute class is designed to marshal data between managed and unmanaged code - exactly what we're trying to do in calling this API. The documentation notes that:

"By default, the common language runtime marshals a string parameter as a BStr to COM methods. You can apply the MarshalAsAttribute attribute to an individual field or parameter to cause that particular string to be marshaled as a LPStr instead of a BStr."

Great! How do we do this! The code below shows how in this case:

Add this Imports to make the code more compact:

Imports System.Runtime.InteropServices

Then modify the Declare for the API call as follows:

Declare Auto Function SetEnvironmentVariable Lib _
   "kernel32" Alias "SetEnvironmentVariableA" ( _
   [MarshalAsAttribute(UnmanagedType.LPStr)] _
   ByVal lpName As String, _
   [MarshalAsAttribute(UnmanagedType.LPStr)] _
   ByVal lpValue As String _
   ) As Boolean

Now the same call ...

Dim SetEnvResult As Boolean
Dim EnvName As String = "AboutVB"
Dim EnvValue As String = "Grrrreat!"
SetEnvResult = SetEnvironmentVariable(EnvName, EnvValue)

... works like a shot!

So why is this a mistake? Well ... Because the original, unmodified VB 6 Declare works just fine too. It turns out the default charsetmodifier in the Declare is Ansi, not Unicode and a conversion from Unicode to Ansi only happens when Auto (or Unicode) is specified.

Now, if you're not confused yet, read some more MSDN pages and come back. That should do the trick!

The goal here is to give you an idea of what the difficulties, and the possible solutions are for coding successful Windows API calls. If you ever are required to use one, the only way to do it is to work your way through considerations just like these.

Explore Visual Basic

By Category

About.com Special Features

Build Your Own Website

Step-by-step advice on how to do everything from choosing a Web host to promoting your content. More >

Connect Your Home Computers

Easy ways to connect two computers for networking purposes. More >

Visual Basic

  1. Home
  2. Computing & Technology
  3. Visual Basic
  4. Using VB.NET
  5. Calling the Windows API in VB.NET

©2009 About.com, a part of The New York Times Company.

All rights reserved.