Concepts in the Windows API

Introduction

Although this series has already discussed the mechanics of declaring and using API functions, it has not yet dealt with some basic concepts used throughout the Windows API. Few of these ideas appear explicitly in Visual Basic, although their knowledge and mastery is crucial for using the API effectively. This page identifies these important ideas, explains them, and briefly demonstrates where they can appear.

Handles

Handles are internal constructs of the Windows API. The API uses handles to keep track of a large number of objects throughout the system. For example, open files, windows, open registry keys, brushes, icons, menus, and many other objects are refered to using handles. In fact, their handles are really the only way a program is able to manipulate these objects, since Windows itself handles all the little details.

In reality, a handle refers to an internal structure in Windows detailing various information and properties of the object. However, there is no way to access this structure directly; that's why it is internal! The handle itself is just a 32-bit integer used by a program calling API functions. In Visual Basic, the Long data type is used to store handles. As far as Visual Basic is concerned, there is no special difference between a handle and any other 32-bit integer value. The significance of a handle only becomes apparent when it is passed as a parameter to an API function.

As already mentioned, most objects under Windows have handles attached to them. This includes form windows, command buttons, picture boxes, and many other objects which Visual Basic can create and manage for you. Fortunately, these objects, when created using Visual Basic, have a property called .hWnd. This property equals the handle to that window. So, for example, if you have a command button called Command1, you can use the property Command1.hWnd to get the handle to the command button (remember that buttons, forms, etc. are all just types of windows). This handle can be given to any API function which can use it properly.

The following example demonstrates how handles can be used. The example uses the API to draw the first icon stored in the file C:\Windows\sol.exe. This example uses three handles. The first handle, App.hInstance, is merely a handle to this particular instance of the example program. The second handle, called hIcon, is a handle to the icon used to depict the icon. The third handle, Form1.hDC, is a handle to Form1's device context. Device contexts are explained in the next section.

Dim windir As String  ' receives path of Windows directory
Dim slength As Long  ' receives length of Windows directory path
Dim exename As String  ' full filename of sol.exe
Dim hIcon As Long  ' handle to the display icon
Dim retval As Long  ' generic API function return value

' First, since the Windows directory might not be C:\Windows,
' obtain the actual path of it.
windir = Space(256)  ' make enough room in the buffer
' Place the directory name in the buffer.
slength = GetWindowsDirectory(windir, 256)
' Remove the blank space from the buffer.
windir = Left(windir, slength)

' Combine the Windows directory path and the filename.
exename = windir
If Right(exename, 1) <> "\" Then exename = exename & "\"
exename = exename & "sol.exe"

' Now, extract the first icon stored in the Solitaire program.
hIcon = ExtractIcon(App.hInstance, exename, 0)
' Draw this icon in the upper-left corner of Form1.
retval = DrawIcon(Form1.hDC, 0, 0, hIcon)
' Destroy the loaded icon to free resources.
retval = DestroyIcon(hIcon)

Device Contexts

Device contexts have a similar initial appearance to handles. Like a handle, a device context is actually an internal data structure inside Windows which cannot be accessed by your program. As the name suggests, a device context is an intermediary between your program and a physical device, such as a display monitor, the keyboard, or the printer. Device contexts are important because they allow your program to treat different models and brands of one type of device (say, printers) in identical ways. By using device contexts, your program doesn't care if the user has an HP DeskJet or a Canon BubbleJet because the device contexts for both are almost identical.

As already mentioned, a program cannot access the device context of a device directly. Instead, it can only use a handle to the device context. It is important to remember that the device context and a handle to a device context are not the same thing! A device context is not accessable directly by a program. A handle to a device context is, like any other handle, a 32-bit integer.

Interestingly, windows themselves are considered to be devices in the sense that they can have a device context. This allows graphics API functions to draw on and otherwise manipulate the appearance of a window. Fortunately again, Visual Basic automatically assigns the .hDC property of such windows to the handle to that window's device context. This allows calls to API functions to reference VB-created objects relatively easily.

The following example demonstrates how a device context can be used. Here, a rounded rectangle is drawn on the form window Form1. The rounded rectangle is drawn with a thin white pen and is filled with a green diagonally cross-hatched brush. Notice that whenever an API function needs to do something to Form1, it requires the handle to its device context (Form1.hDC). You can also see those all-important handles used to identify the brush and pen, not to mention the device context!

Dim hPen As Long  ' handle to the pen
Dim hBrush As Long  ' handle to the brush
Dim hOldPen As Long  ' handle to Form1's previous pen
DIm hOldBrush As Long  ' handle to Form1's previous brush
Dim retval As Long  ' generic API return value

' (Recall that all constant definitions appear on the pages which
' explain the function they are used by.  So here, the constant
' WHITE_PEN is defined on GetStockObject's page.)

' Get a handle to the desired pen.
hPen = GetStockObject(WHITE_PEN)
' Get a handle to the desired brush.
hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(0, 255, 0))

' Select the new pen and brush for use by Form1.
hOldPen = SelectObject(Form1.hDC, hPen)
hOldBrush = SelectObject(Form1.hDC, hBrush)

' Draw the rounded rectange on Form1.
retval = RoundRect(Form1.hDC, 100, 100, 300, 200, 5, 5)

' Select the previous pen and brush for Form1 and delete
' the ones created by this example.
retval = SelectObject(Form1.hDC, hOldPen)
retval = SelectObject(Form1.hDC, hOldBrush)
retval = DeleteObject(hPen)
retval = DeleteObject(hBrush)

Pointers

A pointer is simply a 32-bit integer variable which holds a memory address. This memory address is usually the location of some other object, such as a structure, another variable, or a structure. Despite this deceptively simple definition, pointers are a core component of computer programming, especially in the C++ language. Interestingly, Visual Basic has no intrinsic support for pointers whatsoever (well, that's not quite true, but the exception will come in a later page of this series), although you can use the Long data type to store a pointer. Presumably, VB has no explicit pointers to make programming easier. (A later page in this series will illustrate some places where Visual Basic hides the pointers it uses.)

Because the DLLs which constitute the Windows API were built using some flavor of C++, many of them desire pointers to one object or another as one or more of their parameters. At this time, it is sufficient to say that 99.5% of the time Visual Basic successfully relieves the burden of creating all the otherwise necessary pointers to structures and strings and other variables. However, the other .5% of the time, your code will need to generate its own pointers, usually by using other API functions.

The following example demonstrates one instance where your program ought to manipulate a pointer explicitly. Here, a PIDL -- a pointer to an ITEMIDLIST structure -- is used to help display the icon used for the Favorites folder in the Windows system shell. Because the API is kind enough to create and delete ITEMIDLIST structures itself, there is no need to access the structure directly; in fact, doing so will almost always generate some error. Therefore, the example blissfully uses a pointer to such a structure. (Once again, handles and device contexts pop up in this example. See how fundamental they are?)

If you have problems understanding this example, you might want to to the page of this series about structures, since this example uses one structure. I couldn't think of any good examples of pointers that didn't necessitate using one structure or another.

Dim pidl As Long  ' PIDL to the Favorites folder
Dim fileinfo As SHFILEINFO  ' stores file information
Dim retval As Long  ' generic return value

' Get a PIDL (pointer to an ITEMIDLIST) which refers to the
' Favorites folder in the Windows shell.  (pidl is the PIDL)
retval = SHGetSpecialFolderLocation(Form1.hWnd, CSIDL_FAVORITES, pidl)

' Now get a little info about the Favorites folder.  Specifically, the following
' function call merely requests the folder's icon.
retval = SHGetFileInfo(pidl, 0, fileinfo, Len(fileinfo), SHGFI_ICON Or SHGFI_PIDL)

' The handle fileinfo.hIcon now refers to the Favorites folder's
' icon in the shell.  Display it on Form1.
retval = DrawIcon(Form1.hDC, 0, 0, fileinfo.hIcon)
' Destroy the icon to free resources.
retval = DestroyIcon(fileinfo.hIcon)

Footnote: Combining Flags

In the call to SHGetFileInfo in the above example, you see your first exposure to flags. A flag is simply a type of named constant. The special thing about flags is that they can be combined with other related flags. This allows multiple on/off or yes/no options to be passed to a function using a single parameter. Above, you see that the function is told to retrieve a handle to the icon (SHGFI_ICON) and that the first parameter is a PIDL to the folder (SHGFI_PIDL).

When combining multiple flags, the bitwise Or operator should always be used. This assures an error-free combination of the flags (as long as the attempted combination is valid). Why not use the addition operator (+) to combine the flags. Although the effect may be the same, using regular addition can have some unexpected consequences. To demonstrate the problem which can appear when using addition, I will create my own hypothetical function.

Consider a message-box-like function which accepts a single parameter. This parameter is a combination of the following flags specifying the buttons you want to appear in the box:

Const PK_OK = 1         ' OK button only
Const PK_CANCEL = 2     ' Cancel button only
Const PK_OKCANCEL = 3   ' OK and Cancel buttons
Const PK_HELP = 4       ' Help button only

Remember that this is a hypothetical example -- these flags do not exist in the API. You will notice that the PK_OKCANCEL flag is actually the bitwise Or combination of the PK_OK and PK_CANCEL flags (1 Or 2 = 3) and not a separate flag in itself. This is done to simplify things for the programmer, since the OK and Cancel buttons frequently appear together.

Now suppose that a novice API programmer decides he wants to use the OK and Cancel buttons. At first, he sends merely the PK_OK flag to the function:

retval = ExampleFunction(PK_OK)

Accordingly, only the OK button appears in the dialog box. Realizing his mistake, he decides to fix his code by adding the PK_OKCANCEL flag as one of the flags. So he enters the following:

retval = ExampleFunction(PK_OK + PK_OKCANCEL)

And he expects both the OK and Cancel buttons to appear. But instead the Help button appears by itself! Why? Notice that PK_OK + PK_OKCANCEL = PK_HELP (i.e., 1 + 3 = 4). His expression actually evaluated to the value for the PK_HELP flag by itself! Had he used the bitwise Or operator has follows, he would have entered:

retval = ExampleFunction(PK_OK Or PK_OKCANCEL)

That line executes as expected, displaying the OK and Cancel buttons. Why? Notice that PK_OK Or PK_OKCANCEL = PK_OKCANCEL (i.e., 1 Or 3 = 3). Using Or instead of addition guarantees that accidentally specifying the same flag twice will not cause an error. Of course, it is sloppy coding, so you should avoid doing so anyway. Of course, in the "real" world this error might not be so obvious. (For example, see the SetWindowPos function; the SWP_DRAWFRAME and SWP_FRAMECHANGED flags are actually identical, and adding the two together gives you the SWP_SHOWWINDOW flag instead.)

| Contents of Introduction | Forward to Part 4 >>

Go back to the Articles section index.
Go back to the Windows API Guide home page.


Last Modified: January 13, 2000
This page is copyright © 2000 Paul Kuliniewicz. Copyright Information Revised October 29, 2000
Go back to the Windows API Guide home page.
E-mail: vbapi@vbapi.com Send Encrypted E-Mail
This page is at http://www.vb-world.net/articles/intro/part03.html