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:
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.
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:
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.
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.
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
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
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()
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.
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).