Chapter 23. Exploiting the Power of the Windows API

<feature><title>In This Chapter</title> </feature>

Why This Chapter Is Important

One of the richest libraries of programming code functions is supplied by Windows itself. This function library commonly is referred to as the Windows API (Application Programming Interface). Fortunately, as a VBA programmer, you can tap into the Windows function library by using these built-in Windows functions in your own VBA modules.

Furthermore, you might discover other dynamic link libraries (DLLs) that contain functions that would be useful in your applications. These DLLs also are available to you as long as you are properly licensed to use and distribute them.

A DLL is a library of procedures that applications can link to and use at runtime. Functions contained in the Windows API and other DLLs can provide your applications with significant, added functionality. It is often much more efficient to use an external DLL to accomplish a task than to attempt to write a VBA function to accomplish the same task.

Declaring an External Function to the Compiler

To use a DLL function, you must do the following:

  • Declare the function to the VBA compiler.

  • Call the function.

  • Use the return value.

The VBA language is not intrinsically aware of the functions available in external libraries. Declaring a DLL function means making the VBA compiler aware of the name of the function, the library it is located in, the parameters it expects to receive, and the values it expects to return.

If you do not properly declare the library function to the VBA compiler, you receive an error message stating Sub or Function Not Defined. User-defined functions and subroutines written in VBA are declared using Sub or Function keywords. These keywords define the procedures so that VBA can locate the routines when they are called. Functions in a DLL are declared in the same way. After you declare a DLL function to the compiler, Access knows where to locate it, and you can use it throughout your application.

You declare an external function to VBA using a Declare statement. You can place Declare statements in the Declarations section of a standard module, a standalone class module, or the class module behind a form or report. A Declare statement placed in a standard module is immediately available to your entire application. If the Declare statement is explicitly declared as private, it is available only to the module in which it was declared. A Declare statement placed in the General Declarations section of a standalone class module or the class module behind a form or report is available only after the form or report is loaded or after the class is instantiated. Furthermore, a Declare statement placed in the General Declarations section of a standalone class module or the module behind a form or report can have only private scope.

You can use a Declare statement to declare both subroutines and functions. If the procedure returns a value, you must declare it as a function. If it does not return a value, you must declare it as a subroutine.

A Declare statement looks like this:

Private Declare Function GetKeyboardType Lib "user32" _
   (ByVal nTypeFlag As Long) As Long

This statement declares a function called GetKeyboardType, which is located in the Windows 9.X or Windows NT System folder in a DLL file called user32. It receives a long integer parameter by value and returns a long integer. Notice that this function was declared as private.

Note

Remember that the function name and library name are both case-sensitive. Unless you explicitly include the path as part of the Declare statement, the default system path, the Windows folder, and the Windows System folder are all searched for in the library. Most Windows API functions are contained within the library files user32.exe, gdi32.exe, and kernel32.exe.

Note

Do not include unnecessary Declare statements in your applications. Each Declare statement consumes memory, whether or not you use the declaration. A large number of unused Declare statements can dramatically increase the amount of memory and resources required by your application.

Passing Parameters to DLL Functions

You pass parameters to a DLL function just as you pass them to a VBA routine. The only difference is that it is very important that you pass the parameters by reference or by value, as appropriate, and that you always pass the correct data type for each argument. Sending the correct data type means that, if the function expects a long integer value, you shouldn’t send a double. Doing so can make your application unstable. Passing by reference versus passing by value is covered in the next section.

Passing by Reference Versus Passing by Value

When a parameter is passed by reference, the memory address of the argument is passed to the function. When a parameter is passed by value, the actual value of the argument is passed to the function. Unless explicitly told otherwise, VBA passes all parameters by reference. Many library functions expect to receive parameters by value. If such a function is passed a reference to a memory location, it cannot function properly. If you want to pass an argument by value, the ByVal keyword must be placed in front of the argument in the Declare statement. When calling library functions, you must know the types of arguments a function expects to receive and whether the function expects to receive the parameters by reference or by value. Passing an argument by reference rather than by value, or passing the incorrect data type for an argument, can cause your system to become unstable and even can result in a General Protection Fault (GPF) or illegal operation.

Passing String Parameters

String parameters require special handling when being passed to DLL functions. Windows has two ways of storing strings: the BSTR and LPSTR formats. Unless you are dealing with an API call specifically involving object linking and embedding (OLE), the string you are passing to the function is stored in the LPSTR format. DLL functions that receive strings in the LPSTR format cannot change the size of the string they are passed. This means that, if a DLL function is passed a small string that it must fill in with a large value, the function simply overwrites another area of memory with the extra characters. This usually results in a GPF or illegal operation. The following code demonstrates this point and handles the error that is generated:

Sub WinSysDir()
   Dim strBuffer As String
   Dim intLength As Integer
   Dim strDirectory As String

   strBuffer = Space$(160)

   intLength = abGetSystemDirectory(strBuffer, Len(strBuffer))
   strDirectory = Left(strBuffer, intLength)
   MsgBox strDirectory
End Sub

Note

The code here and most of the code in this chapter is located in CHAP23EX.MDB on your sample code CD-ROM.

Notice that the Space$ function is used to store 160 spaces in the string variable strBuffer. Actually, the Space$ function stores 160 spaces, followed by a Null character in the strBuffer variable.

The abGetSystemDirectory Windows API function receives two parameters:

  • The buffer that it will fill with the name of the Windows System folder—in this case, strBuffer.

  • The length of the buffer that will be filled—in this case, Len(strBuffer). The key here is that the length of the buffer that is passed to the GetSystemDirectory function is more than sufficient to hold the name of the Windows System folder.

The GetSystemDirectory function fills the buffer and returns the length of the string that it actually found. By looking at the left intLength number of characters in the strBuffer variable, you can determine the actual location of the Windows System folder.

The Declare statement for the GetSystemDirectory function looks like this:

Declare Function abGetSystemDirectory _
   Lib "kernel32" _
   (ByVal lpBuffer As String, ByVal nSize As Long) _
   As Long

Notice the ByVal keyword that precedes the lpBuffer parameter. Because the ByVal keyword is used, Visual Basic converts the string from BSTR to LPSTR format by adding a Null terminator to the end of the string before passing it to the DLL function. If the ByVal keyword is omitted, Visual Basic passes a pointer to the function where the string is located in memory. This can cause serious problems.

Caution

Windows API calls are fraught with potential danger. To reduce the chances of data loss or database corruption, always save your work before testing a procedure containing an external function call. If the Access application terminates, at least you won’t lose your work. In addition, always make sure that your database is backed up. If the Access application terminates and your database is not closed properly, you risk the chance of damage to the database. Regularly backing up ensures that if the database becomes corrupted during testing, you can retrieve the last good version from a backup.

Aliasing a Function

When you declare a function to VBA, you are given the option to alias it, as in the preceding function. To alias means to refer to a function by a substitute name. You might want to alias a Windows API function for several reasons:

  • A DLL procedure has a name that includes an invalid character.

  • A DLL procedure name is the same as a VBA keyword.

  • You want to omit the A required by ANSI versions of the API call.

  • You want to ensure that you have a unique procedure name in an Access library or application.

  • You want to call a DLL procedure referenced by an ordinal number.

  • You want to give your API functions a distinctive prefix to prevent conflicts with API declarations in other modules or add-ins.

Reasons for aliasing an API function are discussed in more detail in the following sections.

Function Calls and Invalid Characters

It is not uncommon for a DLL procedure name to contain a character that is not allowed in VBA code, for example, a DLL procedure that begins with an underscore (_). VBA does not allow a procedure name to begin with an underscore. To use the DLL function, you must alias it, as this example shows:

Declare Function LOpen _
   Lib "kernel32" _
   Alias "_lopen" _
   (ByVal lpPathName As String, ByVal ReadWrite As Long) _
   As Long

Notice that the Windows API function _lopen begins with an underscore. The function is aliased as LOpen for use in the Access application.

DLL Functions with Duplicate Names

The DLL procedure name you want to use might share the same name as a VBA function. You can resolve this conflict only by aliasing the DLL function. The following code aliases a DLL function:

Declare Function GetObjectAPI _
    Lib "gdi32" _
    Alias "GetObject" _
    (ByVal hObject As Long, _
    ByVal nCount As Long, _
    lpObject As Any) As Long

The GetObject function is part of the Windows API and is a VBA function. When you alias the function, there is no confusion as to whether the API or the VBA GetObject function is being called.

Eliminating the “A” Suffix Required by ANSI

Many API function calls have both ANSI and Unicode versions. The ANSI versions of the functions end with an “A”. You might want to call the ANSI version of a function but prefer to use the name of the function without the “A.” You can accomplish this by using an alias, as this code shows:

Declare Function FindWindow _
   Lib "user32" Alias "FindWindowA" _
   (ByVal lpClassName As Any, ByVal lpWindowName As String) As Long

This Declare statement creates an alias of FindWindow for the ANSI function FindWindowA.

Note

Unicode is a standard developed by the International Standards Organization (ISO). It was developed to overcome the 256-character limit imposed by the ANSI character standard. The ANSI standard uses only 1 byte to represent a character, limiting the number of characters to 256. This standard uses 2 bytes to represent a character, allowing up to 65,536 characters to be represented. Access uses Unicode for string manipulation, which can lead to problems with DLL calls. To overcome this problem, you always should call the ANSI version of the API function (the version of the function that ends with an “A”).

Unique Procedure Names in an Access Library or Module

Sometimes you simply want to ensure that a procedure name in a library you are creating is unique, or you might want to ensure that the code you are writing will not conflict with any libraries you are using. Unless you use the Private keyword to declare each procedure, external function declarations are global throughout Access’s memory space. This can lead to potential conflicts because Access does not allow multiple declarations of the same external routine. For this reason, you might want to place a unique identifier, such as your initials, at the beginning or end of the function declaration, as in this example:

Declare Function ABGetWindowsDirectory Lib "kernel32" _
Alias "GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long

This statement declares the Windows API function GetWindowsDirectoryA in the library kernel32. The function is aliased as ABGetWindowsDirectory. This function was aliased in order to differentiate it from other calls to the GetWindowsDirectoryA function that might share this procedure’s scope.

Calling Functions Referenced with Ordinal Numbers

Every DLL procedure can be referenced by an ordinal number in addition to its name. In fact, some DLLs use only ordinal numbers and do not use procedure names at all, requiring you to use ordinal numbers when declaring the procedures. When you declare a function referenced by an ordinal number, you should declare the function with the Alias keyword, as in this example:

Declare Function GetAppSettings _
    Lib "Utilities" _
    Alias "#47" () As Long

This code declares a function with an ordinal number 47 in the library called Utilities. It now can be referred to as GetAppSettings whenever it is called in VBA code.

Working with Constants and Types

Some DLLs require the use of constants or user-defined types, otherwise known as structures or parameters. You must place these in the General Declarations section of your module, along with the Declare statements you have defined.

Working with Constants

Constants are used by many of the API functions. They provide you with an English-like way of sending required values to an API function. The constant is used as an alias for a specific value. Here’s an example:

Global Const SM_CXSCREEN = 0
Global Const SM_CYSCREEN = 1

The constant declarations and function declarations are placed in the General Declarations section of a module. When the GetSystemMetrics function is called, the SM_CXSCREEN and SM_CYSCREEN constants are passed as arguments to the function:

Sub GetScreenInfo()
   MsgBox "Screen Resolution is : " & _
      GetSystemMetrics(SM_CXSCREEN) & _
      " By " & _
      GetSystemMetrics(SM_CYSCREEN)

End Sub

When the SM_CXSCREEN constant is passed to the GetSystemMetrics function, the horizontal screen resolution is returned; when the SM_CYSCREEN constant is passed to the function, the vertical screen resolution is returned.

Working with Types

When working with types, you first must declare the type in the General Declarations section of a module. You then can pass elements of a user-defined type, or you can pass the entire type as a single argument to the API function. The following code shows an example of a Type declaration:

Type OSVERSIONINFO
   dwOSVersionInfoSize As Long
   dwMajorVersion As Long
   dwMinorVersion As Long
   dwBuildNumber As Long
   dwPlatformId As Long
   strReserved As String * 128
End Type

The Type structure OSVERSIONINFO is declared in the General Declarations section of the module, as shown in Listing 23.1.

Example 23.1. Declaring the Type Structure OSVERSIONINFO in the General Declarations Section of the Module

Function GetOSInfo()

    Dim OSInfo As OSVERSIONINFO
    Dim strMajorVersion As String
    Dim strMinorVersion As String
    Dim strBuildNumber As String
    Dim strPlatformId As String

    ' Set the length member before you call GetVersionEx
    OSInfo.dwOSVersionInfoSize = Len(OSInfo)
    If GetVersionEx(OSInfo) Then
        strMajorVersion = OSInfo.dwMajorVersion
        strMinorVersion = OSInfo.dwMinorVersion
        strBuildNumber = OSInfo.dwBuildNumber And &HFFFF&
        strPlatformId = OSInfo.dwPlatformId
        MsgBox "The Major Version Is: " & _
            strMajorVersion & vbCrLf & _
            "The Minor Version Is: " & strMinorVersion & vbCrLf & _

            "The Build Number Is: " & strBuildNumber & vbCrLf & _
            "The Platform ID Is: " & _
            IIf(strPlatformId  = 1, "Win 95 or Win 98", _
            "Win NT") & vbCrLf
    End If
End Function

In this listing, the statement Dim OSInfo As OSVERSIONIFO creates a Type variable. The entire structure is passed to the GetVersionEx function (declared in basAPICalls), which fills in the elements of the structure with information about the operating system. This information then is retrieved and stored into variables that are displayed in a message box.

Using the Windows API Text Viewer

As you might imagine, Declare statements, constant declarations, and type structures can be time-consuming and difficult to add to your code modules. Fortunately, the Windows API Text Viewer, a tool that ships with Microsoft Office Developer, helps you complete these tasks. It makes it easy for you to add Declare statements, types, and constants required by the Windows API function calls to your code. You can launch the Window API Text Viewer as an add-in to the Access 2002 VBE. To do this, follow these steps:

  1. Activate the VBE.

  2. Select Add-ins|Add-In Manager. The Add-In Manager appears.

  3. Select VBA WinAPI Viewer.

  4. Click Loaded/Unloaded.

  5. Click Load on Startup if you want the API Viewer to be available each time you launch Access.

  6. Click OK.

  7. Open the Add-Ins menu. WinAPI Viewer should appear.

  8. Select WinAPI Viewer from the Add-Ins menu.

When you first launch the Windows API Text Viewer, it appears as shown in Figure 23.1. You can load a text file or a database file containing declares, types, and constants.

The Windows API Text Viewer.

Figure 23.1. The Windows API Text Viewer.

Loading a Text File

Microsoft Office Developer Edition ships with a file called WIN32API.TXT. You can load and browse this file so that you can obtain Declare statements, type structures, and constants easily. To load the WIN32API.TXT file into the Windows API Text Viewer, follow these steps:

  1. Choose File|Load Text File. The Select a Text API File dialog box appears, as shown in Figure 23.2.

    The Select a Text API File dialog box.

    Figure 23.2. The Select a Text API File dialog box.

  2. Select a text file to load into the viewer and click Open. The WIN API Text Viewer appears, as in Figure 23.3.

    The WinAPI Viewer after loading a text file.

    Figure 23.3. The WinAPI Viewer after loading a text file.

Loading a Database File

After a text file is converted to a database, you should load the database each time you use the Windows API Text Viewer. To load the database file, follow these steps:

  1. Choose File|Load Database File. The Select a Jet Database dialog box appears, as shown in Figure 23.4.

    Use the Select a Jet Database dialog box to specify the database to load into the Text Viewer.

    Figure 23.4. Use the Select a Jet Database dialog box to specify the database to load into the Text Viewer.

  2. Select the database you want to load and click Open.

Pasting API Declares, Types, and Constants

Regardless of whether you have loaded a text or a database file, the Windows API Text Viewer appears as shown in Figure 23.5. All the Declare statements for the 32-bit API appear in the Available Items list box. Select each Declare statement you want to add to your module and click Add. You can use the API Type drop-down list to view and select types or constants. In Figure 23.6, the GetVersionEx and GetWindow Declare statements have been added to the Selected Items list. The SM_CXSCREEN and SM_CYSCREEN constants, as well as the OSVERSIONINFO type, also have been added.

The Windows API Text Viewer after a file has been loaded.

Figure 23.5. The Windows API Text Viewer after a file has been loaded.

The Windows API Text Viewer with several items in the Selected Items list.

Figure 23.6. The Windows API Text Viewer with several items in the Selected Items list.

Follow these steps to add the selected items to a module:

  1. In the API Text Viewer, click Copy. The selected declares, constants, and types are placed on the Windows Clipboard.

  2. Place your cursor in the module where you want the selected declares, constants, and types to be placed.

  3. Click Paste. The selected items are pasted into the module, as shown in Figure 23.7.

    A module after selected items are pasted into it.

    Figure 23.7. A module after selected items are pasted into it.

Calling DLL Functions: Important Issues

After a procedure is declared, you can call it just like any VBA function. The main issue is that you must ensure that you are passing correct values to the DLL. Otherwise, the bad call can cause your application to shut down without warning. In fact, external library calls are very tricky. You therefore should always save your work before you test the calls.

Most DLLs expect to receive standard C strings. These strings are terminated with a Null character. If a DLL expects a Null-terminated string, you must pass the string by value. The ByVal keyword tells VBA to pass the string as Null-terminated.

Although you must pass strings by value, they actually are received by reference. The ByVal keyword simply means that the string is Null-terminated. The DLL procedure actually can modify the value of the string, which can mean problems. As discussed in the “Passing String Parameters” section earlier in this chapter, if you do not preallocate space for the procedure to use, it overwrites any memory it can find, including memory currently being used by your application, another application, or even the operating system. You can avoid this problem by making the string argument long enough to accept the longest entry that you think will be placed into the parameter.

Examining the Differences Between 16-Bit and 32-Bit APIs

You might be familiar with the 16-bit API from earlier versions of Windows, but you must be aware of some issues when working with the 32-bit API. These issues can cause you significant grief:

  • Window handles (hWnd properties) are long integers in the 32-bit API. They were integers in the 16-bit API.

  • Function names are not case-sensitive in the 16-bit API. They are case-sensitive in the 32-bit API.

  • When working with the 16-bit API, you should reboot whenever you get a GPF because it is likely that the memory of your computer is corrupted. With the 32-bit API, each program runs in its own virtual machine. Therefore, it often is unnecessary to reboot simply because a GPF occurs.

Using API Functions

The potential uses for API calls are endless. You can use API calls to modify the System menu, obtain system information, or even switch between running applications. In fact, you can accomplish so many things using API calls that entire books are devoted to the topic. The remainder of this chapter covers several of the common uses of API calls.

Manipulating the Windows Registry

Four built-in VBA functions help you manipulate the Windows registry. They include GetAllSettings, GetSetting, SaveSetting, and DeleteSetting. These four functions only allow you to manipulate and work with a specific branch of the registry, HKEY_CURRENT_USERSoftwareVB, and VBA program Settings. There are times when it is necessary to read from or write to other parts of the registry. This is one situation in which the Windows API can really help you out. Using the Windows RegQueryValueEx function, you can extract information from registry keys. Using the RegSetValueEx function, you can write information to the registry. The declarations for these two functions (found in the basAPICalls module) look like this:

'The RegQueryValueExA function is used to
'read information from the Windows registry

Declare Function RegQueryValueEx _
 Lib "advapi32.dll" Alias "RegQueryValueExA" _
 (ByVal hKey As Long, ByVal lpValueName As String, _
 ByVal lpReserved As Long, lpType As Long, _
 lpData As Any, lpcbData As Long) As Long

'The RegSetValueExA function is used to
'write information to the Windows registry

 Declare Function RegSetValueEx _
 Lib "advapi32.dll" Alias "RegSetValueExA" _
 (ByVal hKey As Long, _
 ByVal lpValueName As String, _
 ByVal Reserved As Long, _
 ByVal dwType As Long, _
 lpData As Any, _
 ByVal cbData As Long) As Long

Before you use either function, you must first obtain a handle to the registry key you wish to affect. This requires the RegOpenKeyEx function:

'The RegOpenKeyExA function is used to
'Return a numeric value that references
'a specific registry key

Declare Function RegOpenKeyEx _
 Lib "advapi32.dll" Alias "RegOpenKeyExA" _
 (ByVal hKey As Long, ByVal lpSubKey As String, _
 ByVal ulOptions As Long, ByVal samDesired As Long, _
 phkResult As Long) As Long

Finally, when you are done reading from or saving to the registry, you must use the RegCloseKey function to close the registry key. The declaration for the RegCloseKey function looks like this:

'The RegCloseKey fucntion closes the designated
'registry key

Public Declare Function RegCloseKey _
    Lib "advapi32.dll" (ByVal hKey As Long) As Long

Listing 23.2 shows how you can use the RegQueryValueEx function to read from the registry.

Example 23.2. Using RegQueryValueEx to Read Registry Information

Private Sub cmdRead_Click()
    Dim strValue As String * 256
    Dim lngRetval As Long
    Dim lngLength As Long
    Dim lngKey As Long

    'Retrieve handle of the registry key
    If RegOpenKeyEx(HKEY_CURRENT_USER, _
        Me.txtKeyName.Value, _
        0, KEY_QUERY_VALUE, lngKey) Then
    End If

    lngLength = 256

    'Retrieve the value of the key
    lngRetval = RegQueryValueEx( _
        lngKey, Me.txtValueName, 0, 0, ByVal strValue, lngLength)
    Me.txtValue = Left(strValue, lngLength)

    'Close the key
    RegCloseKey (lngKey)
End Sub

This code is found in frmRegistry in the sample database. Notice that the code first retrieves a handle to the requested registry key. It then uses the RegQueryValueEx function to retrieve the designated value from the registry. After the code is complete, it closes the registry key. For example, you could request the value Last User from the SoftwareMicrosoftOffice10.0AccessSettings registry key. The value stored for the Last User setting displays in the txtValue text box.

Listing 23.3 shows how you can use the RegSetValueEx function to write to the registry.

Example 23.3. Using RegSetValueEx to Write Information to the Registry

Private Sub cmdWrite_Click()
    Dim strValue As String
    Dim strKeyName As String
    Dim lngRetval As Long
    Dim lngLength As Long
    Dim lngKey As Long

    'Create string with Key name
    strKeyName = Me.txtKeyName.Value & vbNullString
    'Retrieve handle of the registry key
    If RegOpenKeyEx(HKEY_CURRENT_USER, _
        strKeyName, _
        0, KEY_WRITE, lngKey) Then
    End If

    'Create string with string to store
    strValue = Me.txtValue.Value & vbNullString

    'Create variable with length of string to store
    lngLength = Len(Me.txtValue) + 1

    'Save the value to the key
    lngRetval = RegSetValueEx( _
        lngKey, Me.txtValueName, 0, REG_SZ, _
        ByVal strValue, lngLength)

    'Close the key
    RegCloseKey (lngKey)

End Sub

The routine first opens a handle to the designated registry key. It then calls the RegSetValueEx function, passing the handle, the value you wish to modify, the type of data the key contains, and the new value. Finally, it closes the registry key.

Caution

I generally do not make a practice of writing information to the Windows registry. If you write to an important registry key, and make a mistake, you can render the Windows operating environment unusable.

Note

Listing 23.3 shows you how to write to a registry key that contains a string. To write to a registry that expects a DWORD value, you must use the REG_DWORD constant rather than the REG_SZ constant.

Getting Information About the Operating Environment

By using Windows API calls, you can get volumes of information about the system environment, including the type of hardware on which the application is running, the amount of memory that exists or is available, and the operating system version under which the application is running. It is handy and professional to include system information in your application’s Help About box. It also is important to include this system information in your error handling and logging because such information can help you diagnose the problem. This is discussed in Chapter 16, “Error Handling: Preparing for the Inevitable.”

Figure 23.8 shows a custom About dialog box that includes system environment information. This form uses several Windows API calls to get the system information displayed on the form.

A custom About dialog box illustrating the capability to obtain system information from the Windows API.

Figure 23.8. A custom About dialog box illustrating the capability to obtain system information from the Windows API.

Before any of the DLL functions required to obtain this information can be called, all the functions need to be declared to the compiler. This is done in the General Declarations section of the module basUtils. Any constants and type structures used by the DLL calls also must be included in the General Declarations section. Listing 23.4 shows what the General Declarations section of basAPICalls looks like.

Example 23.4. The General Declarations Section of basAPICalls

Option Compare Database
Option Explicit

Public Const MAX_PATH = 160
'The function GetVersionEx gets information about '
'the version of operating system that is currently '
'running. The information is filled into the type
'structure OSVERSIONINFO.

Declare Function abGetVersionEx _
   Lib "kernel32" _
   Alias "GetVersionExA" _
   (lpOSInfo As OSVERSIONINFO) As Boolean

Type OSVERSIONINFO
   dwOSVersionInfoSize As Long
   dwMajorVersion As Long
   dwMinorVersion As Long
   dwBuildNumber As Long
   dwPlatformId As Long
   strReserved As String * 128
End Type

'The GetSystemMetrics function utilizes three constants to
'determine whether a mouse is present, and to determine
'the width and height of the screen.

Const SM_CXSCREEN = 0
Const SM_CYSCREEN = 1
Const SM_MOUSEPRESENT = 19

Declare Function abGetSystemMetrics _
   Lib "user32" _
   Alias "GetSystemMetrics" _
   (ByVal nIndex As Long) As Long

'The GlobalMemoryStatus function retrieves information
'about current available memory. It points to a type
'structure called SYSTEM_INFO, filling in its elements
'with relevant memory information.

Type MEMORYSTATUS
   dwLength As Long
   dwMemoryLoad As Long
   dwTotalPhys As Long
   dwAvailPhys As Long
   dwTotalPageFile As Long
   dwAvailPageFile As Long
   dwTotalVirtual As Long
   dwAvailVirtual As Long
End Type

Declare Sub abGlobalMemoryStatus _
   Lib "kernel32" _
   Alias "GlobalMemoryStatus" _
   (lpBuffer As MEMORYSTATUS)

'The GetSystemInfo function returns information about
'the system. It fills in the type structue SYSTEM_INFO
'with relevant information about the system.

Type SYSTEM_INFO
   dwOemID As Long
   dwPageSize As Long
   lpMinimumApplicationAddress As Long
   lpMaximumApplicationAddress As Long
   dwActiveProcessorMask As Long
   dwNumberOrfProcessors As Long
   dwProcessorType As Long
   dwAllocationGranularity As Long
   dwReserved As Long
End Type

Declare Sub abGetSystemInfo Lib "kernel32" _
   Alias "GetSystemInfo" _
   (lpSystemInfo As SYSTEM_INFO)

'The function GetWindowsDirectory retrieves the name of the
'directory within which windows is running

Declare Function abGetWindowsDirectory _
   Lib "kernel32" _
   Alias "GetWindowsDirectoryA" _
   (ByVal lpBuffer As String, _
   ByVal nSize As Long) As Long

'The GetSystemDirectory function retrieves the name of the
'directory in which the Windows system files reside.

Declare Function abGetSystemDirectory _
   Lib "kernel32" _
   Alias "GetSystemDirectoryA" _
   (ByVal lpBuffer As String, _
   ByVal nSize As Long) As Long

'The GetTempPath function retrieves the name of the
'directory where temporary files are stored

Declare Function abGetTempPath _
   Lib "kernel32" _
   Alias "GetTempPathA" _
   (ByVal nBufferLength As Long, _
   ByVal lpBuffer As String) As Long

'The GetCommandLine Function retrieves the command
'line for the current process

Declare Function abGetCommandLine _
   Lib "kernel32" _
   Alias "GetCommandLineA" () _
   As String

 

As you can see, several type structures, constants, and Declare statements are required to obtain all the information that appears on the form. When the form (frmSystemInformation) is opened, all the Windows API functions are called, and the text boxes on the form are filled with the system information. The Open event of the form frmSystemInformation calls a subroutine called GetSysInfo, which is shown in Listing 23.5.

Example 23.5. The GetSysInfo Subroutine

Sub GetSysInfo(frmAny As Form)
    Dim intMousePresent As Integer
    Dim strBuffer As String
    Dim intLen As Integer
    Dim MS As MEMORYSTATUS
    Dim SI As SYSTEM_INFO
    Dim strCommandLine As String

    frmAny.txtScreenResolution = abGetSystemMetrics(SM_CXSCREEN) & _
    " By " & abGetSystemMetrics(SM_CYSCREEN)
    intMousePresent = CBool(abGetSystemMetrics(SM_MOUSEPRESENT))
    frmAny.txtMousePresent = IIf(intMousePresent, "Mouse Present", _
    "No Mouse Present")

    'Set the length member before you call GlobalMemoryStatus
    MS.dwLength = Len(MS)
    abGlobalMemoryStatus MS
    frmAny.txtMemoryLoad = MS.dwMemoryLoad & "%"
    frmAny.txtTotalPhysical = Format(Fix(MS.dwTotalPhys / 1024), _
    "###,###") & "K"
    frmAny.txtAvailablePhysical = Format(Fix(MS.dwAvailPhys / 1024), _
    "###,###") & "K"
    frmAny.txtTotalVirtual = Format(Fix(MS.dwTotalVirtual / 1024), _
    "###,###") & "K"
    frmAny.txtAvailableVirtual = Format(Fix(MS.dwAvailVirtual / 1024), _
    "###,###") & "K"

    abGetSystemInfo SI
    frmAny.txtProcessorMask = SI.dwActiveProcessorMask
    frmAny.txtNumberOfProcessors = SI.dwNumberOrfProcessors
    frmAny.txtProcessorType = SI.dwProcessorType

    strBuffer = Space(MAX_PATH)
    intLen = abGetWindowsDirectory(strBuffer, MAX_PATH)
    frmAny.txtWindowsDir = Left(strBuffer, intLen)

    strBuffer = Space(MAX_PATH)
    intLen = abGetSystemDirectory(strBuffer, MAX_PATH)
    frmAny.txtSystemDir = Left(strBuffer, intLen)

    strBuffer = Space(MAX_PATH)
    intLen = abGetTempPath(MAX_PATH, strBuffer)
    frmAny.txtTempDir = Left(strBuffer, intLen)

End Sub

Now take a look at this subroutine in detail. The subroutine calls the function GetSystemMetrics (aliased as abGetSystemMetrics) three times. The first time, it is sent the constant SM_CXSCREEN, and the second time, it is sent the constant SM_CYSCREEN. These calls return the horizontal and vertical screen resolutions. When passed the constant SM_MOUSEPRESENT, the GetSystemMetrics function returns a logical True or False indicating whether a mouse is present.

The GlobalMemoryStatus API call fills in a structure with several pieces of information regarding memory. The elements of the structure are filled with the memory load, total and available physical memory, and total and available virtual memory.

The GetSystemInfo API call also provides you with valuable system information. It fills in a structure with several technical tidbits, including the active processor mask, the number of processors, and the processor type.

Finally, the function calls GetWindowsDirectory, GetSystemDirectory, and GetTempPath. These three functions return the Windows folder, System folder, and temp file path, respectively. Notice that buffer space is preallocated before each call. Because each call returns the length of the folder name retrieved, you then take the characters on the left side of the buffer for the number of characters specified in the return value.

Determining Drive Types and Available Drive Space

Often, it is necessary to determine the types of drives available and the amount of space free on each drive. Fortunately, Windows API functions are available to help you to accomplish these tasks. The frmListDrives form lists the type of each drive installed on the system and the amount of free space on each drive, as shown in Figure 23.9. The declarations that are required for the APIs are shown in Listing 23.6.

The frmListDrives form, showing the type of each drive installed on the system and the amount of free space on each drive.

Figure 23.9. The frmListDrives form, showing the type of each drive installed on the system and the amount of free space on each drive.

Example 23.6. API Declarations

'The GetDriveType Function returns an integer
'indicating the drive type

Public Const DRIVE_UNKNOWN = 0
Public Const DRIVE_UNAVAILABLE = 1
Public Const DRIVE_REMOVABLE = 2
Public Const DRIVE_FIXED = 3
Public Const DRIVE_REMOTE = 4
Public Const DRIVE_CDROM = 5
Public Const DRIVE_RAMDISK = 6

Declare Function abGetDriveType _
   Lib "kernel32" _
   Alias "GetDriveTypeA" _
   (ByVal nDrive As String) _
   As Long

'The GetDiskFreeSpace Function determines the amount of
'free space on the active drive

Declare Function abGetDiskFreeSpace _
   Lib "kernel32" _
   Alias "GetDiskFreeSpaceA" _
   (ByVal lpRootPathName As String, _
   lpSectorsPerCluster As Long, _
   lpBytesPerSector As Long, _
   lpNumberOfFreeClusters As Long, _
   lpTotalNumberOfClusters As Long) _
   As Long

The Click event of the cmdListDrives command button located on frmListDrives calls a subroutine called GetDriveInfo, sending it the txtDrives text box. Listing 23.7 shows the GetDriveInfo procedure.

Example 23.7. The GetDriveInfo Procedure

Sub GetDriveInfo(ctlAny As Control)
   Dim intDrive As Integer
   Dim strDriveLetter As String
   Dim strDriveType As String
   Dim strSpaceFree As String

   'Loop through all drives
   For intDrive = 65 To 90 'A through Z
      strDriveLetter = (Chr(intDrive) & ":")
      'Get Drive Type
      strDriveType = TypeOfDrive(strDriveLetter)
      'Get Space Free
      strSpaceFree = NumberOfBytesFree(strDriveLetter)
      ctlAny.Value = _
         ctlAny.Value & _
         Left(strDriveLetter, 2) & _
         " - " & strDriveType & _
         IIf(strDriveType <> "Drive Doesn't Exist", _
            strSpaceFree, "") & _
         vbCrLf
   Next intDrive

End Sub

The routine loops through all available drive letters. For each drive letter, two user-defined functions are called: TypeOfDrive and NumberOfBytesFree. Listing 23.8 shows the TypeOfDrive function.

Example 23.8. The TypeOfDrive Function

Function TypeOfDrive(ByVal strDrive As String) As String
   Dim intDriveType As Integer
   Dim strDriveType As String

      intDriveType = abGetDriveType(strDrive)
      Select Case intDriveType
         Case DRIVE_UNKNOWN
            strDriveType = "Type Unknown"
         Case DRIVE_UNAVAILABLE
            strDriveType = "Drive Doesn't Exist"
         Case DRIVE_REMOVABLE
            strDriveType = "Removable Drive"
         Case DRIVE_FIXED
            strDriveType = "Fixed Drive"
         Case DRIVE_REMOTE
            strDriveType = "Network Drive"
         Case DRIVE_CDROM
            strDriveType = "CD-ROM"
         Case DRIVE_RAMDISK
            strDriveType = "RAM Disk"
      End Select
      TypeOfDrive = strDriveType
End Function

The TypeOfDrive function receives a drive letter as a parameter. It calls the Windows API function GetDriveType to determine the type of drive whose drive letter was passed to the function. The GetDriveType function returns a numeric value that indicates the type of the specified drive. The returned value is evaluated with a case statement, and text representing the drive type is returned from the function.

The NumberOfBytesFree function determines how many bytes are free on a particular drive, as shown in Listing 23.9.

Example 23.9. The NumberOfBytesFree Function

Function NumberOfBytesFree(ByVal strDrive As String) As String
   Dim lngSectors As Long
   Dim lngBytes As Long
   Dim lngFreeClusters As Long
   Dim lngTotalClusters As Long
   Dim intErrNum As Integer

   intErrNum = abGetDiskFreeSpace(strDrive, lngSectors, _
   lngBytes, lngFreeClusters, lngTotalClusters)
   NumberOfBytesFree = " with " & _
         Format((CDbl(lngBytes) * CDbl(lngSectors)) * _
         CDbl(lngFreeClusters), "#,##0") & _
         " Bytes Free"
End Function

This function receives a drive letter as a parameter. It then calls the GetDiskFreeSpace Windows API function, sending it the drive letter and several long integers. These long integers are filled in with the information required to determine the number of bytes free on the specified drive.

After the type of drive and number of bytes free are determined, the GetDriveInfo procedure concatenates the information with the text contained in a text box on the frmListDrives form. If the drive specified is unavailable, the amount of available disk space is not printed.

Summary

External libraries, referred to as dynamic link libraries (DLL), open up the entire Windows API as well as other function libraries to your custom applications. Using external libraries, your applications can harness the power of functions written in other languages, such as C, Delphi, or Visual Basic. In this chapter, you learned how to declare API functions, type structures, and constants, and how to call Windows API functions. Using the techniques that you learned, you can easily extend beyond the power of Access, harnessing the power of the operating system environment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.184.122