Basically Visual

August/September 1997

by Peter G. Aitken (c)1997

Originally published in Visual Developer magazine .


Pointers in Basic

I can envision three possible reactions to the title of this column. If your programming knowledge is limited to Basic, you may be puzzled. If you know the C language and like it, you may be thrilled. If you know the C language and dislike it, you may be horrified. So what's the story?

A program's code and data are stored in memory, and locations in memory are identified by numerical values called addresses. When a program accesses a variable to calls a procedure, the address is required to know where in memory the item is located. Throughout the history of Basic, however, addresses have been kept hidden from programmers. The interpreter or compiler would take care of the task of maintaining the required associations between a program's variables and procedures and the numerical addresses where the data or code was stored. Addresses were used, but in a behind-the-scenes manner.

The people who developed the C language took a different approach. They realized that if a language could manipulate memory addresses directly it would provide a great deal of flexibility and power. Thus C (as well as some other languages) permit not only indirect access to memory, via variable and procedure names, but also direct access to and manipulation of addresses. This manipulation is done by means of pointers, which are nothing more than program variables that hold a memory address.

Like dynamite, pointers are powerful but dangerous. Programming with pointers is not a simple matter, and a minor error with pointers can not only crash your program but send the whole system off into la-la land. Just about anything you can do with pointers you can do without them (although not always as efficiently or elegantly), so Basic programmers were generally content to leave pointers to the C weenies, trading that last iota of power for Basic's simplicity and speed of development.

Call me Back, Jack

With Windows programming, however, Basic's lack of pointers started to take on a more serious tone. The reason was a technique named callbacks, and because Visual basic lacked pointers this was a technique that was simply not available. So what's a callback?

Callbacks are related to the Windows API. As most readers probably know, the Windows API (Applications Programming Interface) is a huge collection of procedures that is a part of Windows itself. When a program, whether it's a program you bought or a Visual Basic program you wrote, needs to do something, it almost invariably does so by calling the appropriate API procedures. In many ways, the API is Windows. Often its operation remains hidden in the background. For example, when your Visual Basic program calls the Print method or changes a form's size, it is actually calling API procedures to perform the requested action. You may not know or care about the API, but it's there all the time.

Perhaps you should care. While Visual Basic's statements, properties, and methods provide indirect access to most of the API's functionality, there will be times when you need more. There are capabilities in the API that Visual Basic does not provide access to at all, and there are other capabilities where access is limited, without the full flexibility that you may require. To access the full power of the API you must call its procedures directly rather than going through the intermediary of Visual Basic statements and methods. Fortunately, Visual Basic has always provided the capability to call API procedures directly – most of them, that is, but not quite all.

Why is this? Most API procedures are standard functions that take one or more arguments and return a value to the calling program (the return value is sometimes ignored). Visual Basic has never had a problem with this sort of API function (which actually constitutes the bulk of the API). There are some API functions, however, that require a callback. Before version 5, Visual Basic was unable to utilize these API functions.

A callback is invoked when the API function requires some assistance from your program. You write a function in your Visual basic program, called appropriately enough a callback function, that performs the required actions. Your program calls the API function which in turn calls the callback function as needed. This arrangement is illustrated in Figure 1. The four steps are (1) The program calls the API function, (2) The API function calls the callback function, (3) The callback function returns execution to the API function, and (4) The API function returns execution to your Visual Basic program.

Figure 1: Operation of a callback function.

How does the API function know about the callback function? You tell it, that's how. One of the arguments that your program must pass to the API function is the address of the callback function - in other words, a pointer to the function. The API function uses the address to call the callback function when it needs to. Lacking pointers, previous versions of Visual Basic could not use API functions that required callbacks.

What's Your Address, Baby?

All that has changed with Version 5. The AddressOf operator provides the address of a function that is defined in your Visual Basic program, permitting you to pass the callback function's address to an API function. To see how it is used, it will be preferable to use a real example. EnumWindows() is an API function that permits you to loop through all of the top-level windows on the screen. Its prototype in a Visual Basic program is:

 

Declare Function EnumWindows lib "user32" _

(ByVal lpEnumFunc as Long, _

ByVal lParam as Long ) As Long

In this declaration, lpEnumFunc is the address of your program's callback function (which I'll explain soon), and lParam is a parameter that the API function passes to the callback function when it is called. Note that this is a common feature of API functions that use callbacks – you can pass a parameter to the API function to be passed to the callback function. How this parameter is used (if at all) depends on how you write the callback function.

The return value of EnumWindows() is True if the function was successful and False otherwise. If the callback procedure in your Visual Basic program is named EnumWinCallBack() then you would call the API function as follows:

 

retval = EnumWindows(AddressOf EnumWinCallBack, 0)

For each API function that uses a callback, the callback function must have the proper signature – that is, its parameters and return value must meet specifications. If you think about it for a moment, this makes perfect sense. The callback procedure is going to be called by the API procedure, which is going to pass certain arguments and expect a certain return value. For EnumWindows(), the callback procedure header is as follows:

 

Function ProcName(hWnd as Long, lParam As Long) As Long

You can see that when the API procedure EnumWindows() calls the callback procedure, it will pass two type Long parameters and will expect a type Long return value. If the signature of the callback procedure does not exactly match what the API procedure expects, you can expect big trouble!

Let's review how callbacks work.

  1. Your Visual Basic program calls the API procedure.
  2. The API procedure performs some processing then calls the callback procedure in your Visual Basic program.
  3. The callback procedure performs the required tasks then terminates, and execution returns to the API procedure.
  4. The API procedure performs additional processing, if required, which may include additional calls to the callback procedure. Note that execution always passes from the callback procedure back to the API procedure – execution never passes from the callback procedure directly back to the Visual Basic program.
  5. The API procedure terminates and execution returns to your Visual Basic program.

There are several rules and cautions that you must observe when using callbacks. First of all, both the callback procedure and the call to the API procedure must be placed in a Basic module. They cannot be part of a form or class module. The Basic module must be part of the same project, as well. Also, you can use AddressOf only with user-defined functions, and not with external functions declared with the Declare statement, or with functions referenced from type libraries.

There are also some caveats regarding errors. Since the caller of a callback is not within your program, it is important that an error in the callback procedure not be propagated back to the caller. You can accomplish this by placing the On Error Resume Next statement at the beginning of the callback procedure.

Working with function pointers can be tricky because you lose the stability of the Visual Basic development environment any time you call a DLL. When working with function pointers it can be especially easy to cause the application to crash, so save frequently and keep backups. Here are some areas that require special attention when working with function pointers:

Using Callbacks

One of the important uses for callbacks is to use API functions that provide certain kinds of system information. Often, the amount of information cannot be known ahead of time. A good example is system fonts. The API procedure EnumFontFamilies() provides information on the available fonts, but without knowing in advance how many fonts are present it is difficult to deal with the information. Instead the API function uses a callback and works by iterating through all the fonts. For each font, EnumFontFamilies() calls the callback function, passing information about the single font that was just processed. Code in the callback function does whatever is necessary with the information, displaying it in a list box for example. The process repeats until all the fonts have been processed, at which time execution returns to the Visual Basic program. This iterative process is illustrated in Figure 2.

Figure 2. How EnumFontFamilies() uses a callback to provide information about all system fonts. [[see hardcopy]]

There's a nice example of using EnumFontFamilies() in the Visual Basic help system, in the section on the AddressOf operator. For now, however, let's get back to EnumWindows() which, I think, is potentially a more useful function. As its name suggests, this function enumerates the active windows, or to be more precise, the top-level windows. Its operation is iterative, just like we saw above for EnumFontFamilies(). On each iteration, EnumWindows() calls your callback function and passes it the handle (hWnd) of an active top-level window. It loops though all of the active windows, calling the callback for each one, and returns to the calling Visual Basic program once it has processed all of the windows.

What can your program do with this information? Lots of things, but for the demonstration I'll keep it simple. Given an hWnd, you can use the API functions GetWindowsText() and GetClassName() to retieve the window's title bar text and the name of its underlying class. A third function, GetWindowTextLength(), retrieves the length of the text in the window's title bar are permits us to skip over titleless windows where the text length is zero.

To create the demonstration program, place a List Box control on a form. Assign the name frmCallback to the form and the name lstWindows to the List Box. Place the following single line of code in the form's Load event procedure:

 

Call GetWindows

Next, use the Add Module command on the Project menu to add a Basic module to the project. Add the code in Listing 1 to the module. I don't think you'll have any trouble understanding the code. When you run the program, the list box is filled with the titles and class names of all current top-level windows (those that have some title text, that is).

There is one fine point that bears mentioning. Notice how the strings buf1 and buf2 are loaded with spaces before being passed to the API functions GetWindowsText() and GetClassName(). Why is this? To be honest I am not sure of the details, but I am sure that without this step – if you declare the string but don't initialize it – the API call will crash.

Before you run the program, you have my permission to go and thumb your nose at your C++ programmer friends. With AddressOf, there is one less item on the list of things that Visual Basic cannot do!

Listing 1. Basic module code in the callback demonstration program.

 

Option Explicit

Declare Function GetWindowTextLength Lib _

"user32" Alias "GetWindowTextLengthA" _

(ByVal hwnd As Long) As Long _

Declare Function GetWindowText Lib _

"user32" Alias "GetWindowTextA" _

(ByVal hwnd As Long, ByVal lpString As _

String, ByVal cch As Long) As Long

Declare Function GetClassName Lib _

"user32" Alias "GetClassNameA" _

(ByVal hwnd As Long, ByVal lpClassName _

As String, ByVal nMaxCount As Long) As Long

Declare Function EnumWindows Lib "user32" _

(ByVal lpEnumFunc As Long, ByVal lParam As Long) _

As Long

Function EnumWinCB(ByVal hwnda As Long, _

lParam As Long) As Long

Dim buf1 As String, buf2 As String, buf3 As String

Dim length As Long

On Error Resume Next

' Pad the strings with spaces.

buf1 = Space(255)

buf2 = Space(255)

' Get the length of the window text.

length = GetWindowTextLength(hwnda)

' Retrieve information about this window only if

' the text is not empty.

If length > 0 Then

' Get the window's title.

GetWindowText hwnda, buf1, 254

' Get the associated class name.

GetClassName hwnda, buf2, 254

' Concatenate the information.

buf3 = Left$(buf1, length) & ": " & buf2

' Stuff it in the list box.

frmCallback.lstWindows.AddItem buf3

End If

' Return True so EnumWindows() will continue.

EnumWinCB = 1

End Function

Public Sub GetWindows()

Dim retval As Long

retval = EnumWindows(AddressOf EnumWinCB, 0)

If Not retval Then

MsgBox ("Error enumerating windows.")

End

End If

End Sub

Visual Basic on the Web

There's a lot of useful stuff on the web, if only you can find it. This is true for Visual Basic programmers just like everyone else. I've been doing some investigating and want to share my findings with you. I won't claim to have located every Visual Basic site, but I have found some very useful ones.

The site I visit most often is Gary Beene's Visual Basic World at http://web2.airmail.net/gbeene/visual.html. This is an extensive and professionally done site that provides information and links related to just about every aspect of Visual Basic, including Visual Basic Script and Visual Basic for Applications. You'll find lists of books (some with reviews by Gary), lists of relevant magazines (including on-line material), vendors, links to other Visual Basic pages, and lots more. You can easily get lost in this site, it's so extensive, but you are pretty sure to find what you're looking for.

Another well done site is Carl & Gary's Visual Basic Home Page at http://web2.airmail.net/gbeene/visual2.html. It provides a wide range of resources, including links, book information, a jobs page, vendor informartion, and so on. Their links page is modestly called The Mother of All Visual Basic Web Sites Lists. One particularly nice feature is the Beginner's Page, which newbie programmers should find useful.

The Visual Basic Instinct page at http://home.sn.no/~balchen/vb/visual.htm is definitely worth a visit. Highlights include a section of Winsock programming, a rated list of FTP sites offering files of interest to Visual Basic programmers, and a series of FAQs (frequently asked questions).

Strollo Software's page (http://www.op.net/~jstrollo/vblinks.html) claims to have the most extensive list of Visual Basic related links anywhere (the Grandmother of All Visual Basic Web Sites Lists?). They also provide a registration service that will notify you when new links are added.

Let's not forget Microsoft's Visual Basic page at http://www.microsoft.com/vbasic/. There's lots of stuff here, and while some of it is rah-rah-rah marketing you'll also find useful information and files. Microsoft also hosts several newsgroups that are devoted to Visual Basic programming. They are hosted at microsoft.public and all come under the "vb" subheading: for example, microsoft.public.vb.winapi.graphics.

Other newsgroups are available also. Use your newsreader to look under comp.lang.basic.visual. The four that my news host provides are .misc, .3rdparty, .announce, and .database.

Last and (I hope) not least is my own Visual Basic page at http://ourworld.compuserve.com/homepages/peter_aitken/computer.htm. I confess that I am just getting started, and my offerings are not nearly as extensive and some other sites, but I am working on it! You'll find the text of past Basically Visual columns, a "tips and techniques" section, and a modest collection of links.

Complete Windows 95 Backup

Windows 95 makes it difficult to perform a complete backup of your hard disk. By complete I mean a backup of everything including system files which. A complete backup is one which, when restored onto a brand new hard disk, will have you up and running exactly the way things were before. The problem lies in the fact that when Windows 95 is running, some of the most important system files are always open and your backup program is not permitted to read them. Any backup you make is unavoidably incomplete, and any restore that you perform after a disk crash will also be incomplete and requires re-installation of Windows 95 before you can restore from your tape or CD.

There's a solution, and we have Steve Heller to thank for it. It's not for the faint hearted, because it requires messing around with your hard disk's partitions, but there are several moderately priced utilities that automate the task of partition-tweaking, so it's not really that bad. I have had good experiences with Partition Magic.

Here's how it's done. First, create a backup of all essential files on your hard disk. This is a good insurance policy whenever you are going to perform any non-trivial disk manipulations. Next, use Partition Magic or a similar utility to create a new bootable partition on your disk. It doesn’t have to be very big, perhaps 100 MB. Install a bare-bones version of Windows 95 in the new partition, along with your backup software. Now at boot-time you'll have the option of booting the original Windows 95, the "real" one that you use for your daily work, or the new one. When it's time for a backup, boot the new partition, run your backup program, and do a complete backup of the other Windows 95 partition. Because that other copy of Windows 95 is not running, all of its files will be available and the backup you create will, in fact, be complete. If you need to restore, the process is reversed. You can find a more complete description of the process on Steve's web page at http://www.websurfer.net/personal/technovelist/homepage.htm.