Calling Windows APIs

Level:
Level2

I remember when I first learned how to call Win32 API’s using classic Visual Basic. It was like a whole new world of possibilities was opened up. Suddenly any VB limitations could be worked around by utilizing Microsoft’s built in API’s. I could change other programs windows and have my program show up in the task tray. Overall many of these limitations have been removed with VB.NET but whether its needing to call your own legacy code or wanting to once again interface with Windows, you will still find yourself as a VB developer needing to call into “old school” APIs.

Note: This tutorial walks through a VB.NET source sample that shows how to call many windows API’s. It is recommended that you download this sample and look at it as you read through this article:


imageAs you open the WindowsAPISample project you will see three files in the project explorer pane: MainForm.vb is a standard VB.NET winform. It contains a TabSet that has four Tabs. The first three tabs have to do with working with API calls that interact with Windows (for this tutorial we will be ignoring those). The last tab (labeled “API Calls”) contains the user interface to interact with a bunch of different API’s. Another file you will see in the solution explorer is labeled Win32API.vb. This file contains a class named Win32API that has most of our API declarations in it. The last file in the project is labeled CallingVariations.vb. This file contains API declarations for the Beep API. The purpose of this is to show all the variations you can use when declaring an API you wish to call.

Now that you have the lay of the land in regrads to the sample project lets dig in and see how we can interact with the Microsoft Windows APIs.


The GetFreeDiskSpace API in the Kernel32 Dll

You will find the declaration for this API in the Win32API.vb file (around line 78):

Public Declare Function GetDiskFreeSpace Lib "kernel32" _
	Alias "GetDiskFreeSpaceA" (ByVal lpRootPathName As String, _
			   ByRef lpSectorsPerCluster As Integer, _
			   ByRef lpBytesPerSector As Integer, _
			   ByRef lpNumberOfFreeClusters As Integer, _
			   ByRef lpTotalNumberOfClusters As Integer) As Integer

This function is called in the click event handler of the GetFreeDiskSpace button:

image

If you double click on this button and look at the click event handler you will see the calling code:

Dim rootPathName As String
Dim sectorsPerCluster As Integer
Dim bytesPerSector As Integer
Dim numberOfFreeClusters As Integer
Dim totalNumberOfClusters As Integer
 
rootPathName = txtDriveLetter.Text & ":" & Path.DirectorySeparatorChar
 
Win32API.GetDiskFreeSpace(rootPathName, sectorsPerCluster, bytesPerSector, _
	numberOfFreeClusters, totalNumberOfClusters)
 
txtFunctionOutput.Text = "Number of Free Clusters: " & _
	numberOfFreeClusters.ToString()

Notice that the GetDiskFreeSpace function requires that you pass the path name to what you want to check (such as “c:\”). It then returns the sectorsPerCluster, the bytesPerSector, the numberOfFreeClusters, and the totalNumberOfClusters. The sample just outputs the number of free clusters, but obviously we could use all these variables to calculate the free space in bytes instead.


The GetFreeDiskSpaceEx API in the Kernel32 Dll

Often you will find that Microsoft provides an API function by one name and then another API function by the same name with an Ex at the end of it. I think this stands for Extended. Most of the time you just want to jump straight to this function as it will give better (or more useful) results compared to the old one. The declaration for this is in the Win32API.vb file (around line 86):

Public Declare Function GetDiskFreeSpaceEx Lib "kernel32" _
	Alias "GetDiskFreeSpaceExA" (ByVal lpRootPathName As String, _
				 ByRef lpFreeBytesAvailableToCaller As Integer, _
				 ByRef lpTotalNumberOfBytes As Integer, _
				 ByRef lpTotalNumberOfFreeBytes As UInt32) As Integer

You will find we call this function in our sample app in the click event handler for the GetDiskFreeSpaceEx button:

Dim rootPathName As String
Dim freeBytesToCaller As Integer
Dim totalNumberOfBytes As Integer
Dim totalNumberOfFreeBytes As UInt32
 
rootPathName = txtDriveLetter.Text & ":" & Path.DirectorySeparatorChar
 
Win32API.GetDiskFreeSpaceEx(rootPathName, freeBytesToCaller, totalNumberOfBytes, _
	totalNumberOfFreeBytes)
 
txtFunctionOutput.Text = "Number of Free Bytes: " & _
	totalNumberOfFreeBytes.ToString()

Notice that like the standard version, GetDiskFreeSpaceEx takes the rootPathName as its first parameter. Also notice that instead of returning cluster information it returns the actual byte sizes. On question that might come up is what is the second parameter for? It tells you the number of free bytes available to the person calling the function. The last two parameters return the totalNumberOfBytes and totalFreeBytes for everyone on the system.


The GetDriveType API in the Kernel32 Dll

Sometimes its nice to know what type of drive you are querying that’s what the GetDriveType API is for. You will find it declared in the Win32API.vb file (around line 92):

Public Declare Function GetDriveType Lib "kernel32" _
	Alias "GetDriveTypeA" (ByVal nDrive As String) As Integer

When this is called it returns an integer that represents what type of drive is associated with the string value you passed in. I think the code makes it pretty obvious what each of these return types means. If you need to reference them in more than one area you probably want to define some constants or an enumeration for each value.

Dim rootPathName As String
rootPathName = txtDriveLetter.Text & ":" & Path.DirectorySeparatorChar
 
Select Case Win32API.GetDriveType(rootPathName)
	Case 2
		txtFunctionOutput.Text = "Drive type: Removable"
	Case 3
		txtFunctionOutput.Text = "Drive type: Fixed"
	Case Is = 4
		txtFunctionOutput.Text = "Drive type: Remote"
	Case Is = 5
		txtFunctionOutput.Text = "Drive type: Cd-Rom"
	Case Is = 6
		txtFunctionOutput.Text = "Drive type: Ram disk"
	Case Else
		txtFunctionOutput.Text = "Drive type: Unrecognized"
End Select

The CreateDirectory API in the Kernel32 Dll

To be clear VB.NET provides a perfectly easy way to create a directory with the Directory.CreateDirectory() function. However, by studying API calls such as this one, we can learn how to interact with all different types of functions. This API declaration looks like this:

<StructLayout(LayoutKind.Sequential)> _
Public Structure SECURITY_ATTRIBUTES
	Public nLength As Integer 'dword
	Public lpSecurityDescriptor As Integer 'lpvoid
	Public bInheritHandle As Integer 'bool
End Structure
 
Public Declare Function CreateDirectory Lib "kernel32" _
   Alias "CreateDirectoryA" (ByVal lpPathName As String, _
		 ByRef lpSecurityAttributes _
			 As SECURITY_ATTRIBUTES) As Boolean

Calling the API requires a bit more work (because of the lpSecurityAttributes parameter:

Dim security As New Win32API.SECURITY_ATTRIBUTES()
 
' The function needs to know the structure size when it is marshaled to unmanaged code.
security.nLength = Marshal.SizeOf(security)
security.lpSecurityDescriptor = 0
security.bInheritHandle = 0
 
If Win32API.CreateDirectory(txtDirectory.Text, security) Then
	txtFunctionOutput.Text = "Directory created."
Else
	txtFunctionOutput.Text = "Directory not created."
End If

The GetVersionEx API in the Kernel32 Dll

Quick what version of windows are you running? XP, Vista, Windows 7? Wrong that may be what Microsoft’s marketing department told you, but with the GetVersionEx API you can find out the real version number (in fact you can also find out the build number and many other details as well). Here is the declaration for the GetVersionEx function:

<StructLayout(LayoutKind.Sequential)> _
Public Structure OSVersionInfo
	Public OSVersionInfoSize As Integer
	Public majorVersion As Integer
	Public minorVersion As Integer
	Public buildNumber As Integer
	Public platformId As Integer
	<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)> _
	Public versionString As String
End Structure
 
Declare Ansi Function GetVersionEx Lib "kernel32.dll" _
	Alias "GetVersionExA" (ByRef osvi As OSVersionInfo) As Boolean

When calling this function we need to first create the OSVersionInfo to pass into it. Once we create this structure we can pass it into the function and it will give the VersionInfo back to us.

Dim versionInfo As New Win32API.OSVersionInfo()
 
' The function needs to know the structure size when it is marshaled to unmanaged code.
versionInfo.OSVersionInfoSize = Marshal.SizeOf(versionInfo)
Win32API.GetVersionEx(versionInfo)
 
txtFunctionOutput.Text = "Build Number is: " & versionInfo.buildNumber.ToString() & Chr(13) & Chr(10)
txtFunctionOutput.Text += "Major Version Number is: " & versionInfo.majorVersion.ToString()

Hibernate Windows With the SetSuspendState API

If we want to put Windows into Hibranation we can do so with the SetSuspendState API, but before doing that we can call the IsPwrHibernateAllowed API to make sure we are even allowed to hibernate our computer. Here are the API declarations for both of these functions.

Public Declare Function IsPwrHibernateAllowed Lib "Powrprof.dll" _
	Alias "IsPwrHibernateAllowed" () As Integer
 
Public Declare Function SetSuspendState Lib "Powrprof.dll" _
	Alias "SetSuspendState" (ByVal Hibernate As Integer, _
		 ByVal ForceCritical As Integer, _
		 ByVal DisableWakeEvent As Integer) As Integer

Here is how we can use both these functions to hibernate a computer:

If Win32API.IsPwrHibernateAllowed() <> 0 Then
	If MsgBox("Do you want to hibernate this computer?", MsgBoxStyle.YesNo, "Confirm Hibernation") = MsgBoxResult.Yes Then
		Win32API.SetSuspendState(1, 0, 0)
	End If
Else
	txtFunctionOutput.Text = "Your computer does not support hibernation.  " & _
		"This may be due to system settings or simply a computer bios that does not support hibernation."
End If

Notice we pass a 1 in for the Hibernate parameter and 0’s for the other two. Also notice that with the IsPwrHibernateAllowed function we can check to see if we are even allowed to do this with the computer our program is running on.


All the ways to Beep

The last API function we are going to look at is a very simple one. It’s the beep function. It takes two parameters – the first is what frequency to beep at; the second is for how long to beep. All and all not a very intersting (or useful) function. However, in the sample source provided it serves another purpose. It allows you to see all the different ways an API function can be declared in VB.NET. Specifically the ways we will declare this function are as follows: The standard declare statement, the DLLImport statement, a unicode version, an ANSI version, and an auto version. You will find the declarations for each one of this in the CallingVaritions.vb file:

' This class encapsulates the calling variations of the function Beep
Public Class CallingVariations
 
    ' Declare version 
    Public Declare Function DeclareBeep Lib "kernel32" Alias "Beep" _
        (ByVal dwFreq As Integer, ByVal dwDuration As Integer) As Integer
 
    ' DLLImport version
    <DllImport("kernel32.dll", EntryPoint:="Beep")> _
    Public Shared Function DLLImportBeep(ByVal dwFreq As Integer, _
        ByVal dwDuration As Integer) As Integer
    End Function
 
    ' Specifying  Unicode
    Public Declare Unicode Function UnicodeBeep Lib "kernel32" Alias "Beep" _
        (ByVal dwFreq As Integer, ByVal dwDuration As Integer) As Integer
 
    ' Specifying Ansi
    Public Declare Ansi Function ANSIBeep Lib "kernel32" Alias "Beep" _
        (ByVal dwFreq As Integer, ByVal dwDuration As Integer) As Integer
 
    ' Specifying Auto
    Public Declare Auto Function AutoBeep Lib "kernel32" Alias "Beep" _
        (ByVal dwFreq As Integer, ByVal dwDuration As Integer) As Integer
 
    ' Using Exact Spelling
    ' The default value of ExactSpelling is False.
    ' If False an A is appended under ANSI and a W under Unicode so that Beep becomes BeepW.
    <DllImport("kernel32.dll", ExactSpelling:=True, CharSet:=CharSet.Ansi, EntryPoint:="BeepW")> _
    Public Shared Function ExactSpellingBeep(ByVal dwFreq As Integer, _
        ByVal dwDuration As Integer) As Integer
    End Function
 
End Class

You can also see how each of these is called in the MainForm.vb file (under the Beep button’s click event handler).


Summary

Being able to call into the Windows API in VB6 (and other languages) was always a useful and much needed feature. Its nice to see that Microsoft not only included this capability in VB.NET, but they made it even easier. One thing to remember though, VB.NET is capable of doing soooo much more than classic VB. Don’t just blindly call Windows APIs – instead look for a DotNet way of solving the problem and if there is not one, then jump into the API functions.

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