Visual Basic Expert Solutions

book coverQUE

Chapter 23

Mixed Language Development with DLLs

By Andrew Dean


This chapter describes using Windows Dynamic Link Libraries (DLLs) written in other programming languages with your Visual Basic programs. DLLs allow you to take advantage of most of the internal system functions in the Windows API as well as incorporate many third-party function libraries. Whether you are programming in Visual Basic and calling DLL functions or writing DLLs that will be called by Visual Basic programs, you need to understand the rules and limitations of mixing Visual Basic with other development languages. This chapter covers the ground rules of mixed-language development with Visual Basic and gives some useful tips on how to avoid the most common problems associated with calling DLLs.

This chapter will teach you the following:

What is a Dynamic Link Library?

Visual Basic is a powerful development tool in its own right. Many useful programs are written using Visual Basic as it comes out of the box. However, if you are a heavy-duty Visual Basic developer, eventually you will find yourself running into the Visual Basic wall. In reality, you cannot really do many things well with Visual Basic alone. The Visual Basic programming language has many limitations in terms of program performance, user interface objects, developer efficiency, and other areas. However, the Visual Basic programming system includes facilities to step beyond the limitations of straight Visual Basic.

Extendibility is one of the primary strengths of Visual Basic. Visual Basic is a very open, flexible development platform, and it has been since its very first release. In fact, Visual Basic has defined the standard for open application development platforms in the Windows environment. Now that Visual Basic, as Visual Basic for Applications (VBA) is becoming the standard application automation language in the Windows environment, you will have more opportunities to integrate a variety of languages and applications.

You can extend Visual Basic in several different ways, including third-party custom controls, OLE servers, and DLLs. This chapter introduces you to one way to mix other development languages with Visual Basic. We will explore the details of extending Visual Basic programs using DLLs.

DLLs are function libraries that an application can link to at runtime rather than at compiletime. DLLs are very important to the architecture of Windows itself, so learning a little about DLLs in general is worthwhile before learning how you can use them Visual Basic.

Static Linking

In the DOS world, if you use a function library to develop your application, you must link that library with your program when you create your executable file for distribution. This is known as static linking because the library is now part of the executable file and cannot be changed independently. Linking resolves the address references that are made when your program calls one of the library procedures. Essentially, the code, or executable instructions, from the library are copied into your executable file. If your application consists of several executable files, all of which use the same function library, each executable will contain a copy of the function library. When your application is installed, you end up installing multiple copies of the function library, embedded in each of the different executable files. This can significantly waste disk space.

Another characteristic of static linking is that the function library cannot be updated independently from the actual executable files of your application. Suppose you have developed a function library that is used by several applications that you distribute. One day you make some changes to the library that improves its performance. Now you want to distribute updates to your users. You will have to recompile all programs, statically link the updated function library, and redistribute the applications. Your users then must reinstall each of the updated applications.

Dynamic Linking

Wouldn't it be easier if you could simply distribute the updated function library, users could install it once, and all their applications that use the library could automatically take advantage of the improvements? Dynamic link libraries do this by addressing the problems with static linking described above. Dynamic link libraries are linked (the addresses are resolved) at runtime rather than compiletime. The library (DLL) exists as a separate file on disk, distinct from the application's .EXE file. Because of this, DLLs have several advantages over static linked libraries. If several applications all use the same library, that library only needs to exist on the disk once, provided that all the applications can find the library file (DLL). This is usually assured by installing the library in either the WINDOWS or WINDOWS\SYSTEM directory. As another advantage, you can update DLLs independently of the application that calls them. Because the DLL is just another file on your hard drive, you can replace it with an updated version. When an application program calls the DLL, it will use the executable instructions contained in the updated disk file.

Dynamic link libraries are a central component of the Windows system architecture. In fact, Windows itself is a collection of DLLs. The GDI.EXE and USER.EXE files that are part of any Windows installation are actually DLLs.

Note: Although .DLL is a common file extension for dynamic link library files, it is not required. A DLL can have any file extension. Two of the most common file extensions for Dynamic Link Libraries, besides .DLL, are .EXE and .VBX.

The previous note brings out an interesting point. Visual Basic custom controls, both OCX and VBX controls, are actually DLLs. Yes, it's true. A custom control is simply a Windows DLL that has been written to specifications that allow it to integrate with the Visual Basic development environment. A custom control file may itself also contain functions that can be called directly, in addition to manipulating properties of custom controls. We will explore these possibilities later in this chapter.

Using DLLs with VB

OK, so now we all agree that DLLs are an excellent way to extend the power of Visual Basic. The next question is: Where do you find DLLs that you can use with Visual Basic? The answer is that you can use just about any DLL with Visual Basic. (Many people refer to calling a DLL, when they really mean calling a specific function within a DLL. Remember that a DLL is a library of procedures. Rather than calling a DLL, you actually call a specific procedure within a DLL.)

The truth is, any time you run a Visual Basic program, you are already using DLLs. The Visual Basic run time library, VBRUN400.DLL, is actually a DLL. VBRUN400.DLL must always be present in order to run a Visual Basic executable program.

Note: Older versions of Visual Basic required runtime libraries VBRUN100.DLL, VBRUN200.DLL, and VBRUN300.DLL.

Because VBRUN400.DLL is a dynamic link library, you only need a single copy of the file no matter how many Visual Basic programs you may have. This makes Visual Basic a very practical development tool in corporate environments where many applications developed in Visual Basic will be deployed.

Although specific technical restrictions prevent some DLL procedures from being called from Visual Basic directly, you can call the vast majority of DLL procedures from Visual Basic. All you need to know is the proper calling sequence to invoke them.

Windows API

The DLLs most commonly used with Visual Basic are the Windows DLLs themselves. Because everybody who uses Visual Basic for Windows will already have these DLLs available to them, we will use the Windows DLLs for our examples in this chapter.

The internal Windows procedures that are available as DLL calls are commonly referred to as the Windows API (Application Programming Interface). These are the same procedures that programmers using C/C++, Pascal, Fortran, and other development languages use to develop the GUI portions of their programs. As a Visual Basic developer, you do not need to master the Windows API when you first start writing Windows programs because Visual Basic takes care of most of that for you. However, the power of the Windows API is available when you want to start taking advantage of it.

Third-Party Products

Many commercial third-party function libraries are distributed as DLLs. You can find libraries for image processing, financial functions, dBase file access, and many other specialized application areas. Although many of these libraries were originally written for C/C++, they are increasingly being marketed to Visual Basic developers as well. Some libraries are written specifically for use with Visual Basic, but the vast majority are fairly generic and can be used with any Windows development tools.

Custom DLLs

The final source of DLLs for extending Visual Basic is you. You can write your own DLLs that can be called from your Visual Basic programs. There is nothing special about a DLL procedure that makes it callable from Visual Basic. For this reason, Visual Basic is often used for prototyping and as a testing harness for DLLs that will ultimately be called from systems written in other languages.

Unfortunately, you can call DLLs all you want from Visual Basic, but you will not be able to create your own DLLs with Visual Basic 4.

There are many tools that you can use to create DLLs, including compilers that work with C/C++, Fortran, Pascal, and Basic.

A simple Windows resource monitor

Let's look at a simple example of calling procedures from the Windows API. We'll write a program, SYSMON, to monitor Windows system resources such as memory, using a Timer control to update the resource information once every second. Notice the Declare statement that appears in the SYSMON project. Declare statements tell Visual Basic that a procedure is located in a dynamic link library. The Declare statement will be fully explained later in this chapter. Figure 23.1 shows the system resource monitor as it is running.

Fig. 23.1 The system resources monitor.

Start a new Visual Basic project. we'll name it SYSMON.MAK. Include a single form, and a single code module, and name the code module SYSMDECS.BAS.

Place five Label controls on the form. Name them lblTime, lblFreeSpace, lblSystemResources, lblGDIResources, and lblUserResources. For each of these controls, set the BorderStyle to 1 (Fixed single) and make the Caption property a Null string. These label controls will be used to display data that is being updated by our program. Listing 23.1 shows the all property settings from the .FRM file for one of the data display labels, lblUserResources.

Listing 23.1 Property Settings for a Display Label

Begin Label lblUserResources
Alignment = 1 'Right Justify
BorderStyle = 1 'Fixed Single
Height = 240
Left = 1545
TabIndex = 9
Top = 3405
Width = 990
End

Place 5 additional Label controls to the left of the above label controls. Set the captions for these new controls to, respectively, Current session length (seconds), Free space (Kbytes) in Global Heap, % system resources available, % GDI Resources available, and % user resources available. Make sure these labels are positioned appropriately relative to the previous controls.

Place a Timer control on the form.

Listing 23.2 shows the contents of the SYSMDECS.BAS file, which contains declarations of Windows API procedures used by the system resource monitor. These declarations will be described in detail later in the chapter.

Listing 23.2 The Contents of the SYSMDECS.BAS File

Option Explicit
Declare Function GetCurrentTime Lib "User" () As Long
Declare Function GetFreeSpace Lib "Kernel" (ByVal wFlags_
As Integer) As Long
Declare Function GetFreeSystemResources Lib "User" _
(ByVal fuSysResource As Integer) As Integer
Global Const GFSR_SYSTEMRESOURCES = &H0
Global Const GFSR_GDIRESOURCES = &H1
Global Const GFSR_USERRESOURCES = &H2

Listing 23.3 shows the code for the Timer event.

Listing 23.3 The Timer1_Timer Event Handler

Sub Timer1_Timer ()
Dim lSeconds As Long_
Dim lSpace As Long
lblTime = CInt(GetCurrentTime() / 1000)_
lblFreeSpace = CInt(GetFreeSpace(0) / 1024)_
lblSystemResources = GetFreeSystemResources(GFSR_SYSTEMRESOURCES)_
lblGDIResources = GetFreeSystemResources(GFSR_GDIRESOURCES)_
lblUserResources = GetFreeSystemResources(GFSR_USERRESOURCES)
End Sub

Set the Interval property of the Timer control to 1,000. Because the Interval property is in milliseconds, the Timer event will fire once every second.

Begin Timer Timer1
Interval = 1000
Left = 0
Top = 3360
End

The Timer event is called once every second. The program calls the Windows API procedures to update the contents of the displayed fields.

Setting Control Properties with Windows API Calls

Sometimes the standard Visual Basic controls do not do what you need. This is the reason for the third-party custom control market. However, there are times when you don't need to use a new custom control to retrieve some missing functionality. One option is always to write Visual Basic code to overcome the shortcomings of a control. Often, the easier option is to take advantage of built-in Windows functionality by calling the Windows API. Instead, a call to the Windows API may let you tweak the behavior of the control enough so that it works the way you want.

An example of this is the Visual Basic combo box control. The standard Visual Basic combo box does not have a property that can be used to limit the length of input to the combo box. You can use the SendMessage() function from the Windows API to force the combo box to limit its input. Listing 23.4 shows the declarations that are necessary to call SendMessage() for this purpose.

Listing 23.4 A SendMessage() Declaration

Global Const WM_USER = &H400
Global Const CB_LIMITTEXT = (WM_USER + 1)
Declare Function SendMessage Lib "User" (ByVal hWnd As_
Integer, ByVal wMsg As Integer, ByVal wParam As Integer,_
lParam As Any) As Long

The Visual Basic code that will limit Combo1 to five characters of input is shown in Listing 23.5.

Listing 23.5 Limiting Input in a Combo Box with SendMessage()

Dim lRet as Long
lRet = SendMessage(Combo1.hWnd, CB_LIMITTEXT, 5, ByVal 0& )

The Declare Statement

The previous examples all included at least one line of Visual Basic code beginning with the keyword Declare. The Declare statement identifies a single DLL procedure to Visual Basic. This section of the chapter will explain the details of the Declare statement. The WIN31API.TXT file that comes with Visual Basic contains all the procedure and type declarations for the Windows API. (Don't be confused by the name of WIN31API.TXT. It works for Windows 95 as well.) Visual Basic also comes with two utilities, APILOD16.EXE and APILOD32.EXE, that you can use to paste Declare statements directly into your project.

In order to use a procedure that is included in a DLL, you must export that procedure from the DLL. This means that the developer of the DLL must identify the procedure as one which can be called externally. This is not an action that you as a Visual Basic programmer have any control over (unless, or course, you are also the developer of the DLL). You must identify procedures as exported when the DLL is compiled. If the DLL contains procedures that have not been exported, there is nothing you can do with Visual Basic to gain access to those procedures. For the purposes of this chapter, when we refer to DLL procedures, we mean procedures that have been exported from a DLL.

Utilities such as EXEHDR, which comes with the Microsoft Visual C++ compiler, can be used to list all the exported procedures that are included in a DLL. Listing 23.6 shows a portion of the results from using EXEHDR to list exported functions in the file GDI.EXE.

Listing 23.6 Sample Output from EXEHDER.EXE

Exports:
ord seg offset name
59 1 1ec0 WEP exported, shared data
46 1 79f4 __GP exported, shared data
474 1 5f6d GETWINDOWEXTEX exported
47 1 4d4e COMBINERGN exported, shared data
482 1 4ef4 SETWINDOWORGEX exported, shared data
480 1 4f28 SETVIEWPORTORGEX exported, shared data
155 7 052d QUERYABORT exported, shared data
301 6 0560 ENGINEDELETEFONT exported, shared data
232 27 00fe EXTRACTPQ exported
149 1 515a GETBRUSHORG exported

The first step in using a DLL procedure is to declare the procedure in the Visual Basic program. To do this, we need to identify the procedure, the DLL file that contains it, and any arguments and return values used by the procedure.

Syntax

The declaration of a DLL procedure can go in the Declarations section of a form, standard, or class module. If you put the declaration in a standard module, it is global or public, and code can call the DLL procedure anywhere in your application. If you declare the DLL procedure in the Declarations section of a form or class module, you must use the Private keyword. This prevents the procedure from being globally available within your Visual Basic application, but allows it to be called only within the scope of that form or class module.

The Declare statement has the following syntax:

Declare [Private] Sub publicname Lib "libname" [Alias_
"alias"] [([[ByVal] variable _[As type][,[ByVal]_
variable [As type]]...]))

or

Declare [Private] Function publicname Lib "libname" _
Alias "alias"] [([[ByVal] variable_[As type][,[ByVal]_
variable [As type]]...])) As type

If an exported procedure returns a value, declare that procedure as a Function. Otherwise, declare the procedure as a Sub. The differences between Sub and Function procedures are discussed in detail in a later section.

The simplest example of a Declare statement is the ReleaseCapture() routine, which releases the mouse capture after the mouse has been captured or limited, to a specific window. This procedure has no arguments and no return value. The Declare statement for ReleaseCapture() simply identifies the name of the subroutine and the disk file where it can be found. The declaration is as follows:

Declare Sub ReleaseCapture Lib "User" ()

Naming Exported Procedures

The publicname specified in a Declare statement is the name used to refer to the procedure within your Visual Basic program. This will usually be the name that the DLL developer used when the procedure was exported.

Be aware that the procedure name is not case-sensitive in 16-bit versions of Visual Basic. In 32-bit versions of Visual Basic, the function name is case-sensitive. If there is any chance that your code will be running in 32-bit Windows as some time in the future, always type your procedure names consistently. But you are doing that anyway, aren't you?

Using Aliases

The Alias section of a Declare statement makes it possible for you to use an identifier for an exported procedure that is different from the name used by the DLL developer when the procedure was exported from the DLL. The string literal that follows the Alias keyword is the identifier that Windows will use when attempting to locate the procedure in the DLL. Several occasions pop up when using the Alias keyword may be necessary.

Invalid Procedure Names

Some languages that you can use for DLL development follow rules for naming identifiers that are different from the rules used by Visual Basic. For instance, you can name a C/C++ function with a leading underscore. The Windows API includes such a function, _lopen(), which is used to open a file. In Visual Basic, underscores can be used in identifier names, with the exception that an underscore cannot be the first character in the name. An identifier with a name that begins with the underscore character will generate syntax errors in you Visual Basic program. Although the _lopen() function is exported in a DLL, a Visual Basic program cannot include a reference to a procedure named _lopen(). Instead, the procedure name used in Visual Basic is lopen(), and the Alias section of the Declare statement is used to identify the function's name in the DLL. The appropriate declaration would be as follows:

Declare Function lopen Lib "Kernel" Alias "_lopen"_
(ByVal lpPathName As String, ByVal iReadWrite As Integer)_
As Integer

Duplicate Identifiers

The Alias section is also useful when a DLL procedure name conflicts with a Visual Basic keyword or an identifier that you are already using in your application. For example, the Windows API includes a procedure named SetFocus. This is the same name as a Visual Basic method, SetFocus. To distinguish between the two, the Visual Basic program will refer to the Windows API function by appending "API" to its name, making it SetFocusAPI. The declaration is as follows:

Declare Function SetFocusAPI Lib "User" Alias "SetFocus"_
(ByVal hWnd As Integer) As Integer

This declaration indicates that the SetFocusAPI() procedure referenced in your Visual Basic program is actually the SetFocus() procedure in the "User" DLL.

Ordinal Numbers for DLL Procedures

Another way (though less valuable for source code documentation purposes) to specify a function in a DLL is to specify the ordinal number of a DLL procedure as its Alias. Every exported DLL procedure has an associated ordinal number that specifies the position of the procedure in the DLL. In fact, you can export a procedure without specifying the name of the procedure. The ordinal number is then the only way to reference an exported function.

In this case, you use the ordinal number of the procedure, preceded by a pound sign (#) as the literal string following the Alias keyword. Use any name you want as the procedure name for the routine. For example:

Declare Sub ReleaseCapture Lib "User" Alias "#19"()

The first column of the report produced by EXEHDR gives the ordinal values for exported procedures.

Exports:
ord seg offset name
...
19 1 28bd RELEASECAPTURE exported, shared data

DLLs and the Windows Search Path

The Lib keyword identifies the dynamic link library disk file that contains the exported DLL. If a full path is not specified, the standard Windows path is searched in order to find the appropriate file. If the DLL needs to be installed in a specific directory, the full path of the file should be specified in the Declare statement. The most common places to install DLLs are the Windows directory, the Windows system directory, or an application's home directory.

The standard Windows search path is as follows:

1. The current working directory as specified in Program Manager.

2. The Windows directory.

3. The Windows system directory.

4. The default system path.

5. The directory of the calling application's .EXE file.

When a Visual Basic application includes calls to DLL procedures, the application does not search for the DLL until one of it's procedures is actually invoked. At this time, if the DLL file cannot be found, you will receive an error message.

Fig. 23.2 The error message when a DLL file is not found.

Sometimes the DLL may not contain the procedure that is defined in the Declare statement, or it may have a significant argument list. In this case, the Visual Basic program will not be able to load the DLL properly to call the procedure. In this case, the error message in figure 23.3 will be displayed.

Fig. 23.3 The DLL file did not contain the procedure as specified in the Declare statement.

Argument Lists

Each procedure exported from a DLL procedure can have a list of arguments. The simplest argument list is empty; the procedure has no arguments. In this case, a pair of parentheses, with nothing inside, is used. The parentheses cannot be omitted even if there are no arguments to the procedure. If there are arguments, they are listed in order, along with their data types.

Passing Arguments by Reference

In Visual Basic, you can pass arguments to subroutines and functions in two ways. The default is to pass arguments by reference. Passing an argument by reference means that the called routine can change the actual value of the variable passed from the calling routine. A reference to the actual memory location of the variable is passed to the called routine. For example, you would pass an argument by reference to a routine that will then perform a calculation and return the result in the passed argument.

Passing Arguments by Value

Arguments can also be passed to a subroutine by value. This means that a copy of the variable's value is passed to the called subroutine. Any changes made to the argument inside the called subroutine will not be visible to the calling routine once the subroutine returns. You can think of passing an argument by value as passing a read-only argument, whereas passing an argument by reference is essentially passing a read/write argument.

By default, all arguments are passed to DLL procedures by reference. If an argument is passed by value, the keyword ByVal must precede the argument name.

Visual Basic user-defined types, corresponding to user-defined types, structures, or records used in the DLL, can also be used. The user-defined types should be defined in the Visual Basic file containing the Declare statements. Any user-defined types should be defined above where they are referenced in a Declare statement.

Comparing Functions and Subs

A DLL procedure is not required to have a return value. Procedures that have return values are called functions. Procedures that do not return a value are called subs (short for subroutines). Every Visual Basic Declare statement will be for either a Function or a Sub. One of the two keywords, Function or Sub, follows the Declare keyword in the Declare statement.

If a procedure is a Function, the Declare statement must also specify the data type of the returned value. The return type of a DLL function is specified at the end of a Declare statement. User-defined types cannot be used as return values for DLL functions. You can only use Visual Basic's built-in data types, such as Integer and Double. However, this does not pose much of a problem because very few functions will return a value that Visual Basic data types cannot handle. If a DLL function returns a pointer, that value should be stored in Visual Basic as a Long. Although the pointer value cannot be dereferenced in Visual Basic, it can be returned, stored, or passed back into other DLL procedures that will then be able to dereference the pointer correctly.

Many DLL procedure libraries include functions that allocate objects and return a pointer to the object. The pointer is then used as an input argument to other procedures in the library. You can use these libraries by returning the created pointer in a Long variable and using that Long as the input pointer argument to other procedures in the library.

Translating Between Data Types

The overall structure and syntax of the Visual Basic Declare statement is pretty straightforward. You have the most opportunity for problems when you have to worry about the specific argument data types and function return values. Understanding how the data types of different languages correspond goes a long way to understanding how to call DLL procedures with Visual Basic. Although it helps if you are familiar with other programming languages, such as C/C++ or Pascal, and even have experience with Windows programming, these are by no means prerequisites for using DLL procedures with Visual Basic. Once you master the basic rules, any Visual Basic programmer can use DLL procedures.

Comparing C and VB Data Types

The following tables display how the standard C/C++ data types, as well as some standard Windows data types (such as handles), are used with DLL procedures. For example, if you need to call a DLL routine that has an argument declared as long in C, then you would have to make the corresponding argument as long in the Visual Basic Declare statement for that routine.

Table 23.1

Table 23.1 Converting Between C/C++ and Visual Basic Data Types

C/C++ Argument VB Data Type Size Declaration of
Data Type (in Bits) Possible VB
Argument
void No arguments N/A N/A
char ByVal x As Byte 8 Dim x as String*1
Dim x as Byte
int (16-bit) ByVal x as 16 Dim x as Integer
Integer Dim x as Boolean
ByVal x as Dim x as Variant
Boolean Any Integer constant
Any Boolean constant
int (32 bit) ByVal x as 32 Dim x as Long
Long
Dim x as Variant
Any Integer constant
long ByVal x as Long 32 Dim x as Long
Dim x as Variant
Any Long integer constant
float ByVal x as Float 32 Dim x as Float
Dim x as Variant
Any Float constant
double ByVal x as 64 Dim x as Double
Double Dim x as Variant
Any double constant
void * x as Any 32 Dim x as Long
Any VB variable
char * ByVal x as 32 Dim x as String
String Dim x as
String*10
int * x as Integer 32 Dim x as Integer
long * x as Long 32 Dim x as Long
float * x as Single 32 Dim x as Single
double * x as Double 32 Dim x as Double
To pass Null x as Any 32 ByVal 0&
pointers,
invoke with
(...,ByVal
0&,...) as
argument
struct x as MyType, 32 Dim x as MyType
MyType * where MyType is
the name of a
corresponding VB
user-defined type.

Table 23.2

Table 23.2 Standard Windows C Data Types

C/C++ VB Data Type Size (in Declaration of Possible
Argument Bits) VB Argument
Data Type
INT ByVal x as 16 Dim x as Integer
UINT Integer Dim x as Boolean
WORD ByVal x as Any Integer constant
BOOL Boolean
DWORD ByVal x as Long 32 Dim x as Long
LPWORD x as Integer 32 Dim x as Integer
Dim x as Boolean
LPDWORD x as Long 32 Dim x as Long
LPSTR ByVal x as 32 Dim x as String
String Dim x as String*256
HWND(16-bit) ByVal x as Integer 16 Dim x as Integer
HWND(32-bit) ByVal x as Long 32 Dim x as Long

HDC Integer Any HDC or HWND
HMENU control property

Working with Variants

The extent to which you can pass Visual Basic variant variables to a DLL procedure depends on whether the DLL is written to support OLE automation data types. More specifically, the DLL procedure must expect a variable of type OLE automation VARIANT data structure. If the DLL supports OLE automation, you can pass variant variables to the DLL procedure the same way you would pass any other data type.

If the DLL procedure does not directly support the VARIANT data type, you cannot pass Visual Basic variants by reference. You can, however, pass variant variables by value (using the ByVal keyword ). That is to say, if a DLL procedure expects a pointer to an integer, you should not send the procedure a pointer to a variant data structure. However, if the DLL procedure expects an integer value, you can use a variant as the argument. Before invoking the DLL, Visual Basic will evaluate the variant as an integer and send a copy of the appropriate value to the DLL procedure.

Passing Arrays

Understanding how arrays are passed to DLL functions requires an understanding of how C/C++ programs work with arrays. In C/C++, arrays are closely linked with pointers. So hold on, this section will be getting down into the nitty gritty. However, if you understand this section, you will have survived the toughest section of this chapter, and you will have gained some insight into how Windows and C/C++ programs work as well.

The data, or variables, of a computer program are stored in memory. You can think of this memory as a linear set of cells, each of which holds some small chunk of data. Each cell is the same size (say, for example, 8 bits). The cells are often called bytes, or words. All the memory is laid out in a single block. Each individual cell can be identified by its position relative to the beginning of the memory block. The position of a cell is called the cell's memory address.

A pointer is actually a memory address. Although all programming languages that include variables deal with memory addresses internally, not all languages allow the programmer to manipulate memory addresses directly. Visual Basic is one such language that does not allow direct access to pointers. C/C++, on the other hand, does allow such access.

In C/C++, and therefore in Windows, an array is stored as a set of contiguous data cells. If an array is 10 elements long, 10 adjacent blocks of memory (with each block the size of one element of the array) would be used to store the array. There are no gaps in the memory block used to store the entire array. The memory address of the first element in the array, therefore, is actually the memory address of the entire array. If a C/C++ program can access the address of the first element of an array, the program can access the entire array.

To pass a Visual Basic array to a DLL procedure that expects an array, you must recognize that the DLL expects the address of the first element in the array. All you have to do is pass the first element of the array by reference. Simple. Suppose you have an array of integers that you need to pass to a DLL procedure. The procedure's declaration is as follows:

Declare Sub PassArray Lib "Test" (IntArray as Integer)

The Visual Basic dimension statement for the array is as follows:

Dim IntArray(0 to 9) as Integer

The invocation of the DLL procedure would be as follows:

PassArray IntArray(0)

Notice how passing an array to a DLL procedure differs from passing an array to another Visual Basic procedure. In Visual Basic, arrays are referenced as arguments in procedure invocations by specifying the array name followed by an empty set of parentheses. If, in the previous example, the PassArray() procedure were written in Visual Basic, its definition would be as follows:

Sub PassArray( IntArray() as Integer)
'...
End Sub

The invocation would be as follows:

PassArray IntArray().

This is an important distinction to keep in mind when you are debugging your application. To pass an array to a DLL procedure, pass an element of that array by reference.

Working with Strings

String variables are the most likely to give you trouble when being passed to DLL procedures. The reason is that C/C++, which most DLL procedures are written in, handles character string data much differently than Visual Basic. Let's look at how character strings are stored in both Visual Basic and C/C++ before we look deeper at how to pass string data between the two.

In DLL, procedures that use C/C++ style strings (which includes all the procedures in the Windows API), a data structure called LPSTR is used to store character strings. An LPSTR is simply a pointer to the first character in a Null-terminated data string.

When a Visual Basic String argument, or BSTR, is passed to a DLL procedure, the default is to pass the argument by reference. This means that a pointer to the BSTR is passed. Because the BSTR is a pointer, this means that a pointer to a pointer is being passed. If the DLL procedure expects an LPSTR to be passed as an argument, it is looking for a pointer to a character. Therefore, to pass Visual Basic strings to DLL procedures which expect LPSTR arguments, you must pass the string by value, rather than by reference.

With simpler data types such as Integer, the way to pass a Visual Basic variable as an argument that a DLL procedure will fill is to pass the argument by reference. The DLL procedure will fill the address pointed to by the argument with the appropriate value. However, we have just learned that strings should always be passed by value. How then do we modify a string by passing it to a DLL procedure?

The trick to remember when passing Visual Basic strings that DLL procedures will fill is to pass in a string that is known to be long enough to handle the result. This is done by passing a fixed-length string into the DLL procedure. You cannot pass an uninitialized variable-length string to a DLL procedure that will fill the string with a value. If the Visual Basic argument is a variable-length string, make sure that it has been initialized longer than any possible result. An easy way to do this is by using the String() function from Visual Basic, as follows:

Declare Function GetSystemDirectory Lib "Kernel" _
(ByVal lpBuffer as String, ByVal nSize as Integer) as Integer
Dim Path as String
Path = String(256,0)
ReturnLength = GetSystemDirectory(Path, Len(Path))
Path = Left(Path, ReturnLength)
Another approach is to define the target string buffer with a fixed length, as follows:
Dim Path as String * 256
ReturnLength = GetWindowsDirectory(Path, Len(Path))

Many DLL procedures that return character strings in their arguments require another argument that gives the maximum length of the variable which holds the string. This is useful because it allows the DLL procedure to avoid overwriting the target variable and possibly corrupting other program data. However, not all DLL procedures require such a length argument. In this case, consult your documentation for the DLL to determine if limits exist. If you have no way of knowing, a general rule of thumb for many DLL procedures, including most Windows API procedures, is to allow at least 256 characters to be returned.

Memory Buffers and Binary Data

One difference between Visual Basic and languages such as C/C++ and Pascal is that Visual Basic cannot directly manipulate binary data. Visual Basic cannot dereference pointers to memory addresses. However, you can use Visual Basic to store such data, moving the data back and forth between DLL procedures.

In previous versions of Visual Basic, String variables were used to hold buffers of binary data returned by DLL procedures. Visual Basic 4 has a new data type, Byte, that you should use to hold generic binary data buffers. Because a String customarily contains only characters, binary data may not be interpreted properly if it is passed as a String rather than a Byte variable.

Handling User-Defined Types

For the most part, user-defined data types are handled the same as simple numeric data types. User-defined data types are always passed by reference. If the DLL procedures you are using already have documentation and Declare statements for Visual Basic, you are all set and can call the DLL procedures directly.

If you have only documentation for calling the routines from C/C++ or another language, you will first have to define new data types for Visual Basic and build the corresponding Declare statements. This is where passing user-defined types differs from passing standard data types to DLL procedures.

Use Tables 23.1 and 23.2 for matching corresponding Visual Basic and C/C++ data types, and of course, the problem data type is again character strings. If the C/C++ struct definition includes pointers (including LPSTR), you should make the corresponding Visual Basic fields in the Type statement Long. Also, be aware that your Visual Basic code will not be able to dereference the data pointed to.

If you happen to be specifying a Visual Basic user-defined type that will contain strings to be filled by a DLL procedure, use fixed-length strings as fields in the user-defined type. This will allow your Visual Basic program to dereference the strings returned from the DLL procedure.

Passing Arguments to DLL Procedures

Visual Basic control properties can be passed to DLL procedures only if they are passed by value. If the Declare statement for the DLL procedure specifies the ByVal keyword for an argument, a control property can be used for that argument directly. This is commonly used when passing handles to device contexts (hDC) or windows (hWnd) to DLL procedures.

Caution: If you are passing handles to Windows API functions, be careful which version of Windows you are going to run your application. On 32-bit Windows versions, handles are Long values. On 16-bit Windows, handles are Integer values.

If you want to modify a property using a DLL procedure, you will have to use an intermediate variable to hold the value. Copy the property to the variable, pass the variable by reference to the DLL procedure, and assign the property the value of the variable.

When a DLL procedure takes pointers as arguments, these are 32 bit long integer values. Although Visual Basic does not have pointer data types, you can use Long variables to store such pointers.

Sometimes a DLL procedure will expect to receive a Null pointer. A Null pointer is a pointer with a value of zero. DLL procedures usually interpret this to mean the pointer is uninitialized. DLL procedures often accept Null pointers for String (and other) arguments to indicate that the particular argument is to be ignored. If an argument can be either a Null pointer or a valid pointer to data (frequently a character string), declare the argument with As Any. Then, when the procedure is invoked, pass a Null pointer as ByVal 0&. The ByVal is used to make sure the DLL procedure gets a zero value rather than a (non-Null) pointer to a zero value. The trailing & makes sure a 32-bit zero is passed to the DLL procedure, rather than a 16-bit zero.

Drawing with the Windows API

The Visual Basic Forms and Picture controls have several methods that allow you to draw on them. However, they don't include methods for drawing polygons. For any Visual Basic application that does lots of drawing, using polygon drawing functions will often be more efficient than drawing all the individual line segments separately with the Line method.

The Windows API includes functions that will draw polygons. Let's look at an example of a Visual Basic drawing program that uses Windows API procedures for all its low-level graphic output. Instead of using methods such as .Circle and .Line, we will draw directly with Windows API procedures. In the course of developing DRAWDEMO, we will exercise many of the possible combinations for calling DLL procedures from Visual Basic, including passing arrays and user-defined types. Figure 23.4 shows the finished DRAWDEMO program.

Fig. 23.4 The finished DRAWDEMO program.

DrawDemo allows us to draw several different types of graphical objects. Once drawn, you can select these objects and move them around on the screen. Although a fair amount of Visual Basic code is associated with managing the objects that have been drawn, we will focus on the parts of the program that call the DLL procedures of the Windows API.

DrawDemo is always in one of three modes. You can either draw objects, move objects, or delete objects. The option buttons on the left side of the screen indicate which mode is current. The drawing area is in the center of the screen.

We will use a data structure called a display list to hold all the current objects and their attributes. Our display list is simply an array of object data structures. Listing 23.7 shows the data structure used to hold a single object.

Listing 23.7 DRAWDEMO.BAS The Data Structure for a Drawing Object

' Define a single graphical object
Type DisplayObject
ObjectType as Integer ' LINE, RECTANGLE, ...
rectBBox as RECT ' Bounding box coordinates
MidPoint as POINTAPI ' Center point of the object
iNumPts as Integer ' Number of points _
defining the object
Points(20) as POINTAPI ' At most 20 points.
End type

The ObjectType field of an object indicates whether an object is a LINE, POLYLINE, CIRCLE, or RECTANGLE. The rectBBox field contains the bounding box of the object. MidPoint is the center point of the object. The MidPoint is used as a hotpoint when selecting an object. The field iNumPts holds how many points are included in the object, and the Points(20) array holds all the points used to define the object. For example, a LINE object is always defined with two points, and a POLYLINE object could have an arbitrary (but less than 20 in our case) number of points.

RECT and POINTAPI are user-defined types used by the Windows API. RECT is used to hold the defining points of rectangles on the screen. POINTAPI defines a single point on the screen. Listing 23.8 shows the definition of RECT and POINTAPI.

Listing 23.8 DRAWDEMO.BAS Windows API Data Structures for Screen Locations

Type RECT
left As Integer
top As Integer
right As Integer
bottom As Integer
End Type
Type POINTAPI
x As Integer
y As Integer
End Type

The DrawDemo program uses a global array to hold all the objects in the drawing. At most, you can use 100 objects in the drawing.

Global DisplayList(100) as DisplayObject
Global iNumObjects As Integer

A more elegant approach would be to use ReDim to redimension the DisplayList() array, rather than hardcoding the value to 100. To avoid obfuscating our example, we will simply dimension the array to a fixed size.

Several Visual Basic routines are used to manipulate objects in the display list:

Function AddObject (iMode As Integer, iNumPts As Integer,_
PointList() As POINTAPI) As IntegerFunction

Sub DrawDisplayList()

Sub DrawObject (iObject As Integer)

Function FindNearestObject (x as Variant, y as Variant) As_
Integer

Sub RelMoveObject (iObject As Integer, fDeltaX As Single,_
fDeltaY As Single)

Sub RemoveObject (iObject As Integer)

Listing 23.9 shows the DrawObject() routine, which calls several Windows API drawing functions to draw the objects on the screen. Note that you could replace some of the calls to DLL procedures with Visual Basic drawing methods.

Listing 23.9 DRAWDEMO.BAS The DrawObject() Subroutine

''''''''''''''''''''''''''''''''''''''''''''''''''
' Draw a single object.
''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DrawObject (iObject As Integer)
Dim lRet As Long
Dim iRet As Integer
Select Case DisplayList(iObject).ObjectType
Case MODE_LINES
' MoveTo() and LineTo() are WINAPI functions.
lRet = MoveTo(Form1.Picture1.hDC, DisplayList(iObject)_
.Points(0).x, DisplayList(iObject).Points(0).y)
iRet = LineTo(Form1.Picture1.hDC, DisplayList(iObject)._
Points(1).x, DisplayList(iObject).Points(1).y)
Case MODE_CIRCLES
' Arc() is a WINAPI function.
iRet = Arc(Form1.Picture1.hDC, DisplayList(iObject)._
Points(0).x, DisplayList(iObject).Points(0).y, _
DisplayList(iObject).Points(1).x, DisplayList(iObject)._
Points(1).y, 0, 0, 0, 0)
Case MODE_RECTANGLES
'Rectangle() is a WINAPI function.
iRet = Rectangle(Form1.Picture1.hDC, _
Min(DisplayList(iObject).Points(1).x, DisplayList(iObject)._
Points(0).x), Min(DisplayList(iObject).Points(1).y, _
DisplayList(iObject).Points(0).y), Max(DisplayList(iObject)._
Points(1).x, DisplayList(iObject).Points(0).x), _
Max(DisplayList(iObject).Points(1).y, DisplayList(iObject)._
Points(0).y))
Case MODE_POLYLINES
' PolyLine() is a WINAPI function.
' Note that the second argument is an array of user
' defined POINTAPI types.
iRet = Polyline(Form1.Picture1.hDC, _
DisplayList(iObject).Points(0), DisplayList(iObject).iNumPts)
End Select
End Sub

Debugging Hints

Using DLL procedures with Visual Basic requires some additional debugging skills and techniques that are not necessary when programming with straight Visual Basic. Not only are you now dealing with your Visual Basic code, but you must interface with code in the DLL, which may even be written in a language with which you are not familiar.

Note: You cannot use the step-through debugging (pressing F8 or choosing Debug, Single Step) that is so useful in the Visual Basic development environment to step into DLL procedures.

Unfortunately, you cannot use the step-through debugging (pressing F8 or choosing Debug, Single Step) that is so useful in the Visual Basic development environment to step into DLL procedures. From the point of view of the Visual Basic developer, the DLL procedure is a black box. Program execution enters the DLL procedure, and waits for the procedure to end before moving on. With the tools supplied with Visual Basic, you cannot do anything to debug from inside the DLL, such as looking at the values that the DLL procedure actually received. (If you are the developer of the DLL, you can debug the DLL procedure the same way you would any other DLL, using tools such as CodeView.) There are some techniques you can use to debug problems you may have working with DLL procedures. These, though, will have to assume that the DLL procedure is correct.

Checking the declare statement

If you have problems calling a DLL procedure, the first thing you should do is verify that the DLL procedure is correct.

If the DLL procedure expects an array for an argument, make sure that the argument is declared to be passed by reference (not ByVal). By default, Visual Basic programs pass arguments to DLL procedures by reference.

If the DLL procedure expects a single scalar value (such as an integer), make sure that the argument is declared to be passed by value (using the keyword ByVal).

If the DLL procedure expects a pointer to a variable, make sure that the argument is declared to be passed by reference. If the argument is a user-defined structure, the argument is expected to be passed by reference.

If the DLL procedure expects to receive a Null-terminated character string, or LPSTR, make sure that the string is passed by value. Visual Basic will convert constant strings and variable-length strings automatically. If the string is stored in a fixed-length String variable, make sure that the string has Chr$(0) at its end.

If the DLL procedure expects a string buffer (as an LPSTR) that can be used to return a string value, you cannot use a variable-length string. Instead, use a fixed-length string that is bigger than the maximum possible length of the returned string. You can use the Visual Basic procedure in Listing 23.10 to convert the fixed-length string with embedded Null character back to a Visual Basic variable-length string:

Listing 23.10 Trimming Terminating Null Character from C Strings

'
' Trim a terminating Null character from a string.
' This is useful when fixed char buffers have been
' passed into DLL functions.
'
Private Function TrimTerminatingNull (szStr As String) As String
Dim iPos As Integer ' Position of terminating Null.
TrimTerminatingNull = szStr
iPos = InStr(1, szStr, Chr$(0))
If iPos > 0 Then
TrimTerminatingNull = Left$(szStr, iPos - 1)
End If
End Function

If the DLL procedure is a function with a return value that is a pointer, make the return value in the Declare statement a Long data type.

Checking the Procedure Invocation

If a Visual Basic array is being passed to a DLL procedure, make sure that you pass the first element of the array. If you are using 0-based array indexing, pass the 0th element of the array. It is possible to pass a subset of an array by specifying some element of the array other than the first. Listing 23.11 shows how you would pass just the second half of a 10-element integer array to a DLL procedure.

Listing 23.11 Passing Part of an Array to a DLL Procedure

Declare Sub ArrayFunc Lib "Xxxx" ( IntArray As Integer)
Dim iaArray(1 to 10) as Integer
ArrayFunc iaArray(5)

If you are passing a value stored in a Visual Basic variant, make sure that the corresponding argument is declared as ByVal.

If a DLL procedure expects a pointer as an argument, do not invoke the procedure with a constant value (neither a number or String variable). Even if you know the value will not be changed by the DLL, the referencing will be incorrect.

If you are having problems with a DLL procedure that is a function returning a floating point value, check which compiler was used to build the DLL. Microsoft compilers will not cause any problems, but some leading commercial C/C++ compilers return floating point numbers differently than Visual Basic expects.

Double-Check Arrays

If you are passing an array to a DLL procedure, make sure that you pass the first element of the array, rather than passing the array with empty parentheses as you would if you were passing an array to a Visual Basic routine.

Path Issues Revisited

If the declaration of the DLL procedure is correct and the procedure is being invoked correctly, maybe the DLL file cannot be found. Double check that the DLL is stored in your Windows directory, Windows system directory, or the directory which is the current directory when your application runs.

DLLs Can Crash Windows

DLLs typically work more closely with Windows internals. Although crashing Windows is not very common when you are working just with Visual Basic, once you start calling DLLs, the potential for a crash becomes much greater. Save your project every time before you test your Visual Basic program that calls DLLs. The easiest way to remember to save is to go to Tools, Options, and select the Save Before Run, Prompt option (or select Save Before Run, Don't Prompt) in the Environment tab.

Stack Space Considerations

Although it is not likely to be a consideration, if a DLL uses a lot of stack space, it may cause problems for your application. The DLL uses the stack of the calling program. A Visual Basic program has a limited amount of stack space available, particularly in 16-bit Windows. Using highly recursive code or lots of local variables in subroutines eats up lots of stack space, whether in a Visual Basic program or in the code of a DLL procedure.

Using .INI files

Many Windows programs use an initialization file to store information about the current status of the program, such as which files have been opened or the positions of various windows on the screen. These files typically are named with the .INI file extension and are referred to as profile files, or more commonly .INI files. Windows itself includes several .INI files that you may be familiar with: WIN.INI and SYSTEM.INI. Visual Basic uses VB.INI as its initialization file.

The general structure of a .INI file is as follows:

• [section]
entry = string

A typical section of a .INI file looks like the following:

• [Visual Basic]
MainWindow=31 16 616 76 1
ToolBox=6 82 0 0 1
ProjectWindow=402 152 248 215 9

The line with square brackets identifies a section of the .INI file. A single .INI file can have multiple sections. Each line below the section has a left side and a right side, separated by an equal sign. The left side is the name of some setting, and the right side is the value of the setting. The meaning of the left and right side is interpreted by the application that uses the .INI file. There are no rules as to what you may or may not put in a .INI file, as long as you follow the structure of identify sections with square brackets, and settings with equal signs.

The Windows API includes several procedures (see Listing 23.12) for manipulating .INI files.

Listing 23.12 WIN31API.TXT Windows API Procedures for .INI Files

Declare Function GetProfileInt Lib "Kernel" (ByVal lpAppName_
As String, ByVal lpKeyName As String, ByVal nDefault As Integer)_
As Integer
Declare Function GetProfileString Lib "Kernel" (ByVal_
lpAppName As String,lpKeyName As Any, ByVal lpDefault As_
String, ByVal lpReturnedString As String, ByVal nSize As_
Integer) As Integer
Declare Function WriteProfileString Lib "Kernel" (ByVal_
lpAppName As String,lpKeyName As Any, lpString As Any)_
As Integer
Declare Function GetPrivateProfileInt Lib "Kernel"_
(ByVal lpAppName As String, ByVal lpKeyName As String,_
ByVal nDefault As Integer, ByVal lpFileName As String) As Integer
Declare Function GetPrivateProfileString Lib "Kernel" _
(ByVal lpAppName As String,_
lpKeyName As Any, ByVal lpDefault As String, ByVal _
lpReturnedString As String,ByVal nSize As Integer, _
ByVal lpFileName As String) As Integer
Declare Function WritePrivateProfileString Lib "Kernel"_
(ByVal lpAppName As String,lpKeyName As Any, lpString_
As Any, ByVal lpFileName As String) As Integer

The arguments names used by the Windows API are not the easiest to understand. You can remember the arguments by thinking of the structure of a .INI file as follows:

• [lpAppName]
lpKey = lpString

The procedures in Listing 23.12, which include Private as part of their name, are for accessing application specific .INI files. These procedures include an argument, lpFileName, that identifies the .INI file to use. The other procedures read and write to the WIN.INI file in your Windows directory.

Note that several of the functions declare lpKeyName as Any, rather than as String, to allow the passage of Null pointers.

As an example, let's write some code to read and write the name of the current file to an application's private .INI file. The .INI file will be called INIDEMO.INI. Listing 23.13 shows the necessary Visual Basic code.

Listing 23.13 INISAMP.FRM Writing to an Application's .INI File

Sub SaveCurFile(szCurFile as String)
Dim iLen as Integer
iLen = WritePrivateProfileString("Files",_
ByVal "CurrentFile", ByVal szCurFile, App.Path + _
"\INIDEMO.INI")
End Sub

Two of the arguments in the function invocation of Listing 23.13 are preceded by the ByVal keyword. This is necessary because the WritePrivateProfileString() declaration identifies the data type of those arguments as As Any, passed by reference, rather than As String passed by value. This is to allow Null pointers to be passed as well.

The Declare statements we are using for the INI file declarations are the most general. If you know that you will not be passing Null pointers to the procedures, you can go ahead and use ByVal Xxx as String in the declarations instead of Xxx as Any to avoid having to pass the arguments with the ByVal keyword when the procedures are called.

If szCurFile contains STUDENTS.MDB, after calling the code in Listing 23.13, the INIDEMO.INI file will include the following:

[Files]

CurrentFile=STUDENTS.MDB

If the INIDEMO.INI file does not already exist, it will be created by the call to WritePrivateProfileString().

You will also want to retrieve any information that you write to a .INI file. Listing 23.14 shows a Visual Basic function that retrieves the current file and passes it back as a returned string. This function would be called when your application loads to restore the same file being used when the application was last closed.

Listing 23.14 Reading from an Application's .INI File

Function GetCurFile() as String
Dim szCurFile as String
szCurFile = String(256, 0)
iLen = GetPrivateProfileString( "Files", ByVal _
"CurrentFile", "", szCurFile,_
Len(szCurFile),App.Path + "\INIDEMO.INI" )
GetCurFile = Left$(szCurFile, iLen)
End Function

Practical Examples

The rest of this chapter will introduce you to several features you can easily add to your applications by calling Windows API procedures. The examples are all solutions to common problems that you could encounter in just about any Visual Basic programming project.

Waiting for a Shelled Process to Finish

A common technique when using Visual Basic to integrate other applications is to use the Shell() function to start another program running. However, the Shell() command does not wait for the shelled program to finish. If you want your VB application to wait, you can use the GetModuleUsage() function from the Windows API. GetModuleUsage retrieves the usage count for a module. When the usage count drops to zero the module is no longer running. Your VB program simply needs to call Shell() to start the other application, and then wait for the module count to drop to zero. In the polling loop you must call DoEvents so Windows will allow the shelled application to run. Without DoEvents, the shelled program would never get time to use the processor.

Listing 23.15 shows a Visual Basic subroutine, SyncShell(), which starts an application running and waits for the application to finish before returning and allowing the program to proceed.

Listing 23.15 Running Another Program Synchronously Using GetModuleUsage()

Declare Function GetModuleUsage Lib "Kernel" _
(ByVal hModule As Integer) As Integer
Sub SyncShell(szAppName as String)
Dim hModule as Integer
hModule = Shell( szAppName ,1 )
while GetModuleUsage(hModule) > 0
DoEvents
Wend
End Sub

Making a Window Stay on Top

You can configure some Windows applications so that they always stay on top of other windows. These are called floating windows because they appear to float on top of the screen in front of other windows. An example is the CLOCK.EXE applet that comes with Windows. Common contexts for floating windows include an application has a non-modal dialog box that needs to stay on top of the application's windows (such as a tool palette) or resource monitoring programs that should always remain visible to the user.

This technique is easy to incorporate into your applications. Listing 23.16 shows how the SetWindowPos() routine from the Windows API can be used to force a window to stay on top.

Listing 23.16 SetWindowPos() Can Make a Window Float

Declare Sub SetWindowPos Lib "User" (ByVal hWnd As _
Integer, ByVal hWndInsertAfter As Integer, ByVal X As Integer,_
ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer,_
ByVal wFlags As Integer)
Global Const SWP_NOSIZE = &H1
Global Const SWP_NOMOVE = &H2
Global Const HWND_TOPMOST = -1
Global Const HWND_NOTOPMOST = -2
Sub SetFormTopmost( F as Form )
SetWindowPos F.hWnd, HWND_TOPMOST, 0, 0, 0, 0, _
SWP_NOMOVE or SWP_NOSIZE
End Sub
Sub SetFormNoTopmost( F as Form )
SetWindowPos F.hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, _
SWP_NOMOVE or SWP_NOSIZE
End Sub

(d)Invoking Windows Help Files

Many applications written with Visual Basic include a Windows help file. One way to display a help file to your users is with the Common Dialog custom control that is included with Visual Basic. If your program does not use the Common Dialog control for anything other than to invoke help, including it with your program's installation is a bit of a waste. Instead, you can call the WinHelp procedure from the Windows API (see Listing 23.17). The wCommand argument allows you to specify which topic in the help file you want to display initially, as well as several other possibilities.

Listing 23.17 Invoking Windows Help Files

' Commands to pass WinHelp()
Global Const HELP_CONTEXT = &H1 ' Display topic in ulTopic
Global Const HELP_QUIT = &H2 ' Terminate help
Global Const HELP_INDEX = &H3 ' Display index
Global Const HELP_HELPONHELP = &H4 ' Display help _
on using help
Global Const HELP_SETINDEX = &H5 ' Set the current_
Index for multi index help
Global Const HELP_KEY = &H101 ' Display topic for_
keyword in offabData
Global Const HELP_MULTIKEY = &H201
Declare Function WinHelp Lib "User" (ByVal hWnd As Integer,_
ByVal lpHelpFile As String, ByVal wCommand As Integer, dwData_
As Any) As Integer
Sub CallForHelp()
Dim iRV as Integer
iRV = WinHelp(CurrentForm.hWnd, "PROGMAN.HLP", HELP_INDEX,_
ByVal 0&)
End Sub

From Here...

This chapter described the basic issues involved when writing using DLLs with Visual Basic. With well-behaved DLL procedures and the proper declarations and user-defined types, you really don't have much to do.

If you buy a commercial third-party DLL that includes a file containing the necessary declarations, using the library is a simple matter of including the file of declarations in your Visual Basic project and then writing your procedure calls. The procedures behave just like built-in Visual Basic statements and functions.

Sometimes, you have to debug calls to DLL procedures from Visual Basic. If you remember the differences between how data types, strings, and arrays are used in Visual Basic and DLL procedures, you will be able to debug most problems.

Dynamic link libraries offer huge improvements to a Visual Basic application. Whether you will be calling a few Windows API procedures or using Visual Basic as simply as a front-end for an extensive library of DLL procedures, Visual Basic can be effectively used with DLLs.

This chapter taught you how to integrate other languages into your Visual Basic program by calling DLLs. You have learned the basic mechanics of how to make the calls to DLLs, as well as some tips on how to debug DLL calls and how to take advantage of the Windows API.


| Previous Chapter | Search | Table of Contents | Book Home Page |

| Buy This Book | Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.