Using DLLs and The Windows API

Visual Basic does a good job of shielding developers from the nitty-gritty details of Windows. However, there comes a point where you can no longer hide behind the safe walls of VB. Windows is out there, waiting to talk to your program.

Windows provides a number of function calls, in the form of DLLs, that are useful to VB programmers. You can also co-opt DLLs from other programs to do work for you. We'll concentrate on using the Windows DLLs here, but what we learn is widely applicable.

In this chapter you'll learn:

   How to declare API and DLL functions in your programs and how to utilize the underlying power of Windows
   How to use the API to harness the power of multimedia in your VB applications

In this chapter we're really going to focus on the issues surrounding calling code that lives in the Windows API. There's also something known as DLL Callback facilities available in Visual Basic 5 which, instead of you calling code in the API, allows you to make use of certain API calls that will call your code as part of their working process. Believe me-this can be really useful, but it is alas a little beyond our scope here.

Enough with the waffle-on with the show.

How VB and Windows Fit Together

In this chapter we're going to look at how to use tools that lie outside what you might consider as Visual Basic, meaning functions and procedures that are actually part of Windows itself. These are pieces of code that Windows has at its disposal to do what it has to do: manage your applications and provide a consistent user interface and operating environment for your programs.

This sounds complicated, but it isn't. The first thing to note is that we've effectively used part of the Windows code already, without really knowing it, when we used the common dialog control. Let's consider for a while what's really happening when we do this.

DLLs the Easy Way - The Common Dialog

You remember the common dialog. It's that great little control that is a window on to a load of different dialogs. Here's the file open dialog:

These dialogs not only save us time, but also give our programs the look and feel of a real Windows application. The reason they're called common dialogs is that they look the same in all Windows applications. So what's the story (morning glory)? It makes you wonder whether they aren't really part of Windows after all....

Well actually they are. If you load up Explorer and take a look in your Windows\System directory you'll see a file named Comdlg32.DLL.

This file contains all the code needed to create the various common dialogs that Windows supports. Lumping all this code together in a file stored in a shared directory makes it accessible to all Windows programs, including Windows itself.

You can't look inside it-it's a compiled lump of C/C++ code and would look like gobbledygook. What interests us, of course, is how we can talk to this little monster and tell it what to do, from our VB code. To understand that, we'll have to take a quick lesson in how Windows works. Bear with me-there is a point.

Dynamic Link Libraries

The Comdlg32.dll file is what's known as a dynamic link library, hence the file extension. To you and me, this means that it's a block of code containing procedures and functions that are useful for more than one program; it is available to any program that wants to use it. The question is, of course-how?

Let's take a quick lesson in the history of programming. If this really is your first toe in the water of programming, then you don't need to bother to read this. Think yourself lucky you got into the game at the right time, and skip this next bit and jump straight to the bit titles the Windows API.

What is 'Linking'?

Traditional programming languages like C, when you use them outside an environment like Windows, like to operate as stand-alone blocks. This means that when you compile the program, you end up with an executable file that is self-contained, and doesn't need any other files to run (unlike say a .vbp file which requires Visual Basic to run). All the code it needs is 'hardwired' into the body of the program.

This doesn't mean you can't use prewritten code-there are lots of C libraries out there that are very widely used. The question, then, is how do you get the prewritten code into your program. This is what is meant by linking, and it can be done in two ways-static linking and dynamic linking.

Static Linking

In static linking, you provide fixed links between your program and the prewritten modules at design time, just like when you create a module in a VB project and call procedures in it from other parts of your code. However, in order to use these prewritten lumps of code, you have to effectively copy them into your final file at compile time in a process called static linking. After that, they are part of your program, locked away in your executable file.

Dynamic Linking

This is the opposite of static linking, and if you understand how it works, you'll see why dynamic linking is a good plan. In dynamic linking, the external library file never gets bound into the final executable file. It remains outside the program as a DLL, hopefully in a place where the executable file can find it and send it messages. At run time, these messages are function or procedure calls, requesting that certain parts of the DLL code are executed.

To link your executable and the DLL it needs to run, you just tell your program where the DLL is, and which bit of code you want to run from inside it. It's up to your program to make the connection when the big moment arrives. It is, as they say, dynamically linked.

The Visual Basic DLLs

Perhaps the most graphic illustration of dynamic linking is Visual Basic itself. Take a peek into your Windows System directory and you should see a set of files that comprise VB's run-time engine. For example VB5DB.DLL contains some of the code necessary to link to the Data Access Objects at run time should your application choose to look at a local database.

When you write a database application and compile it, even if you compile to a fully independent compiled application rather than simply an interpreted one, the resulting EXE file knows nothing about databases, what they do or how to deal with them. Instead, your application includes a block of code provided by Visual Basic itself, which at run time loads up that VB5DB.DLL file and uses the functions contained within.

The Advantages of Dynamic Linking

Of course, with dynamic linking, you have the hassle of making sure that all the DLLs a program needs are present, in the right place, and in the right version. However, while this is not a trivial problem, you are well taken care of by Windows and VB in this respect. And the advantages of dynamic linking are real and important.

Consistency

Users like Windows because it has a more or less common user interface across applications. To achieve this, it helps if you generate as much of your user interface as possible from common code. The common dialog, along with the new Office 97 menus, toolbars, etc., are good examples of this.

Maintenance

By having a lot of common code in one place, you can update and amend that code centrally, and the changes are reflected in all the applications that use it. That's why, when you run Windows 3.1 applications on Windows 95, they inherit some user interface features of the new system. This applies to Visual Basic as well.

Smaller Executables

By moving a lot of the back room business out to another file, rather than statically linking the functions and procedures, you can reduce the size of your executable. The flip side of this is that the DLL files tend to be massive as they need to contain every possible piece of code they support, not just the ones you need. However, they are shared across many applications, so there's still a net gain.

The Windows Architecture

Dynamic linking is fundamental to the design of Windows. Windows is really just a bag of DLLs that the various applications you run use to do their jobs. In fact, even things that you think of as being Windows itself, like the desktop and Explorer, are just applications that run like any other program, calling the procedures from the intrinsic Windows DLLs as you need them.

The great news is that they aren't the only ones that can tap into the Windows DLL goodie bag. All those DLLs are sitting there waiting to work, and they'll work for anyone who shows up with the right program code. If the mood takes you, you can even replace the Windows desktop with your own version, although I'd suggest we leave that one for a rainy day.

There a lot of DLLs that ship with Windows to give it all the functionality it needs. Inside each of these is anything from a handful, to hundreds of available functions and procedures. Collectively, these 1000+ individual routines are called the Windows API.

The Windows API

The Windows Application Programmer's Interface (API) is a collection of ready-made functions and procedures. These have traditionally been the domain of C and C++ programmers-the way that the connections to the API operate are more intuitive for C and C++ programmers, for whom arcane and incomprehensible syntax are a stock in trade.

Visual Basic was created to free us from the kind of drudgery that bedevils C/C++ Windows development, and this extends to the API as well. Most of the API calls are already implemented in Visual Basic in the form of VB commands, keywords, methods and properties. These are translated into the corresponding API calls inside VB. In a way, VB is a friendly wrapper around the Windows API.

However, there are still some API functions for which Visual Basic has no substitute. For example, standard Visual Basic has no way for the programmer to get real control of the Windows multimedia system. OK, you can embed files in OLE controls, or use other custom controls. But with the API you can achieve great effects without the overheads of lots of other controls.

Working with the Multimedia API is easy. Well, relatively speaking. Although there's nothing to be frightened of, using API calls in VB is a little fiddly at times. However, armed with a clear understanding of what to do and why you're doing it, you can unlock the power of the API.

API Wrappers and Custom Controls

An alternative to diving around in the API is to look for a custom control that does what you want to do. Many custom controls (OCXs or ActiveX controls) are themselves a wrapper around a particular bit of the API, which deliver that functionality to your program in a VB-style, user-friendly manner.

Having said that, more and more ActiveX controls go much further than this, like for example a mapping control. These can really extend the feature set of your applications, without requiring huge programming effort on your behalf. I can tell you now that there's no DrawAmerica function call in the API, no matter how hard you look.

ActiveX Controls and OLE Automation Servers (collectively ActiveX Components) actually represent the most useable way to distribute code in an API-like format between projects, without having to go to all the hassle of dropping into a compiler and writing a true DLL. We cover them in a little more detail, and discuss how to create your own, in the next chapter.

Another approach is to enclose some API calls into a Visual Basic class module of your own, which brings the power of the API into a VB object. We'll have a look at doing this ourselves later in this chapter.

Finding and Using API Calls

We said that Windows has a lot of DLLs in it—some large, some small. There's no point in trying to take all these in at one go, let alone all of the 1000+ calls they contain. The best strategy for working with the API is to get to know a few common API calls and then fan out your knowledge from there. In this chapter, we'll look at a couple of API calls, and leave you to find out more at your own speed. Often magazines publish these-there are a few listed in the VB Help file, and there are some very good reference books available which list the majority of them.

The Text API Viewer

Depending on which version of Visual Basic 5 you've splashed out all your hard-earned cash on, you may find that it includes a useful utility to help you find the correct declarations for API routines. What the Text API Viewer doesn't do, however, is give you any real help as to what the routine does, exactly what values it expects, and what values it returns. You'll need to buy a reference book to learn more.

Still, it can help to get the declarations correct, and find our the types and constants it requires. It can even copy them to the clipboard, so you can paste them directly into your code.

Some Windows DLLs

If it helps, think of each API call as a subroutine or function. Given the number of API calls that make up Windows, Microsoft wisely decided to group them together into four main libraries.

KERNEL32

The main DLL, Kernel32, handles memory management, multitasking of the programs that are running, and most other functions which directly affect how Windows actually runs.

USER32

Windows management library. Contains functions which deal with menus, timers, communications, files and many other non-display areas of Windows.

GDI32

Graphics Device Interface. Provides the functions necessary to draw things on the screen, as well as checking which areas of forms need to be redrawn.

WINMM

Provides multimedia functions for dealing with sound, music, real-time video, sampling and more. This is a 32-bit only DLL. The 16 bit equivalent is called MMSYSTEM.

You can see these files in your Windows\System directory. There are also many other smaller, less frequently used DLLs which provide specialist services to applications.

Those listed above are the 32-bit DLL names. If you have been reading this so far with the idea that you are going to support the millions of 16-bit Windows users out there with your application, then I'm afraid I have some bad news for you.In the past, Microsoft have always released a version of Visual Basic that's useable on 16-bit Windows platforms, such as Windows for Workgroups, or Windows 3.1. However, Visual Basic 5 is the first ever 32-bit-only version of VB, with Visual Basic 4 being the last incarnation of a 16-bit development tool from Microsoft.

Having done our homework, now comes the fun part. We're going to check out some common API calls and, along the way, make sure we know everything we need to know about using the API in general. After that, it's up to you to explore away.

Calling API Routines

Calling a procedure in the API is really no different to calling a function or subroutine that you've written yourself and added to a module in your project. For example, if you have this piece of code:

Public Sub FindText(objDataControl As Control, sFieldName As String)

' Code to implement function does here.

End Sub

To invoke the procedure, you could use this code:

FindText datTitles, "Titles"

Let's apply the same logic to an API call, which is a subprocedure that isn't only outside our current module, but also outside VB.

A Quick Look at Declaring an API Call

Before a DLL routine can be used, it needs to be declared. Visual Basic needs to be told:

The name of the subroutine or function

Which DLL file it can be found in

The parameters it expects to receive

The type of value that it can return if the routine is a function

You still use the word Sub or Function to start the code off, but it must be prefixed with the word Declare. Because we're calling an API function, the code isn't directly in our VB program after the declaration, it's off in the DLL we indicated. Apart from that, the declaration is the same as for a function that you wrote yourself.

Once the function is declared, calling it is straightforward. Let's take a look at how this works, using a quick example of an API call.

Try It Out - Flashing a Window with an API Call

1 Create a new project in Visual Basic.

2 Draw a timer control on the form and set the timer Interval property to 10. This will cause a timer event to occur every 10 milliseconds.

3 Double-click on the timer control to display its code window. Then type in code so that it looks like this:

Private Sub Timer1_Timer()

Dim nReturnValue As Integer

nReturnValue = FlashWindow(Form1.hWnd, True)

End Sub

4 Now declare the FlashWindow function in the general declarations section as follows:

Private Declare Function FlashWindow Lib "user32" Alias "FlashWindow" (ByVal hWnd As Long, ByVal bInvert As Long) As Long

5 Now run the program. When the form appears, its caption should be flashing. This is a very simple program, but flashing the caption of a window using pure Visual Basic code is extremely difficult and requires a lot of code-try it at your own peril!

How It Works - The API Declaration

The function declaration itself is fairly straightforward once you understand its constituent parts. It helps to have a Windows API reference manual handy to determine whether the API call you're about to use is a subroutine or function, which DLL it's contained in, and what the parameters to be passed to it should be.

The word Declare tells Visual Basic that we're declaring a DLL routine.

Immediately following Declare is the word Sub or Function, which declares either a subroutine or a function. Of course, you can't declare subroutines and functions indiscriminately.

The Lib keyword tells Visual Basic the DLL in which the function we want is contained. In this case, it's the User32 DLL file. Alias tells VB what the actual name of the function inside the library is-this could be different for the name we assign to it before the Lib keyword.

Private Declare Function FlashWindow Lib "user32" Alias "FlashWindow" (ByVal hWnd As Long, ByVal bInvert As Long) As Long

Finally, the parameters which are to be passed to the function are declared, along with the type of value that the function will return.

The parameters we're passing here are:

(ByVal hWnd As Integer, ByVal bInvert As Integer) As Integer

The first parameter, hWnd, is a handle that identifies the window we want to blink. It's important that you understand the concept of handles in Windows, and we'll come back to it later. The second parameter, bInvert, switches the flashing property on and off. If bInvert is set to True by the calling statement, then the bar flashes. To return it to its original state, you need to call the function again, with the value False.

In many API routines, the Alias is the same as the actual routine name, such as in FlashWindow. In these cases, we can omit the Alias part altogether, for example:

Private Declare Function FlashWindow Lib "user32" (ByVal hWnd As Long, ByVal bInvert As Long) As Long

However, some have names that are illegal in VB, such as _lopen, and other come in different versions-sometimes with an A or W appended to the name. In general, it's safer to use the definition as it is. Some programmers use the Alias to change the name that the refer to the routine by, or even to declare two different versions of a routine which accept different parameter types-but we'll steer clear of these techniques for now.

How It Works - Calling the API

We called the function in this way:

nReturnValue = FlashWindow(Form1.hWnd, True)

Once you've declared an API call, it's used in almost exactly the same way as a normal call to a Visual Basic function or subroutine. In the above example, the FlashWindow call is a call to a function held in a DLL. Just as with Visual Basic functions, API functions return values which must then be stored somewhere. We store the value that the FlashWindow function returns in a variable called nReturnValue.

Again, just as with Visual Basic functions, you don't have to do anything with the values returned by API functions. But you do need to store them somewhere, even if you intend to ignore them, by assigning them to a variable. Almost every API function returns a numeric error code, which you can use to see if everything worked correctly.

In fact, ignoring these values is not only a bit lazy-it can actually be dangerous if you're using more than one call in your code. In this straightforward example, however, it's fine to just store the return value in a variable that we never subsequently use.

Using Windows API calls can potentially crash Windows, if not your machine. When you come to the more complex API calls, such as those which are responsible for allocating vast amounts of memory and system resources, woe-betide the programmer who casually ignores the return code. Since the DLL functions live outside of your application they handle all their own error checking-your only indication that something might have gone wrong is that return code. Remember that, and ignore it at your peril later on!!

Windows' Handles

Visual Basic provides you with a nice soft buffer between your code and the underlying Windows DLL calls. One of the areas where this is most evident is in a control's properties.

Take the form as an example. Windows uses something called a structure to hold information about a form. This information is almost identical to the information contained in the form's properties window. However, whereas you or I can easily check a window's properties-we just click on a form and press F4 to bring up its Properties window-Windows stores each window's structure in a large list of data structures which relates to every window of every program actually running. To determine which structure relates to which window, it uses something called a handle. It can't use the name of the form to find it, because the name is just a property of that form. The handle is Windows' own shorthand ID for an object.

As you start to use API calls more and more, particularly those that deal directly with your Visual Basic forms, you'll find handles cropping up again and again. Conveniently, Visual Basic stores handles as read only properties, which you can use to pass to Windows functions when required.

The property is called hWnd (handle to a window) and can only be accessed at run time. The property means nothing to your Visual Basic code, but it can be read, and passed as a parameter, to those API calls that need it. You'll find that almost any API call that could do something to a displayed window will need you to pass it an hWnd parameter so that the function knows exactly which window you want to deal with.

Declaring Parameter Types

When you declare the type of parameter that a DLL subroutine or function needs, it's important to make sure that the ByVal keyword is used whenever necessary.

With regular Visual Basic code, if you pass a parameter to a function by value, it tells Visual Basic that the function can only deal with a copy of the parameter you pass it. This is how you do it with a regular function:

  Function Square(ByVal Number As Double) As Double

The alternative to passing by value is to pass the variables by reference. Here, you effectively send the whole variable to the routine, not just a copy of its contents. Therefore, if the routine changes the parameter, those changes are also reflected in the original variable. If you don't specify ByVal, the variable will be passed by reference automatically. Have another look at Chapter 8 for a reminder of these keywords.

If you write an internal Visual Basic function or subroutines and you miss out the ByVal keyword, as long as you pass the correct number of parameters to the code, nothing serious will go wrong. Sure, you may get cases where variables passed as parameters are changed, causing your program to have weird results, but nothing really serious will happen. Windows won't crash, for example!

However, with DLLs, the situation is a little more serious. If you omit the ByVal keyword, Visual Basic actually passes a pointer to the variable. This number tells the function being called where the variable is stored in memory. It's then up to the function to go to that memory location and retrieve the value itself. This is also what happens in Visual Basic-only situations-but since, in that case, the functions and subroutines are all written in Visual Basic code, Visual Basic can cope. DLL code, written in a language like C, expects things to happen in a certain way, and can get quite upset when they don't.

If a DLL function expects a number, let's say between 0 and 3, and you pass it a variable by reference, the actual value passed could be something like 1,002,342, which would be the address in memory where your variable lives. The DLL function would then try to deal with the number 1,002,342 instead of a number between 0 and 3, the net result of which would be that your system crashes.

There are no nasty error messages here; you know when a DLL call is wrong because your system generally misbehaves, or locks up completely! One of the golden rules of messing about with API calls is save your work! Since you're venturing outside the protected world of Visual Basic, when things go wrong, it can easily result in the whole system crashing, and you losing your work. Always save your project before running code with API calls in it. The best way to do this is to check the Save Before Run box in the Options | Environment menu.

Using Classes with the API

It goes without saying that the API is a powerful weapon in the VB programmer's arsenal. However, if you were to put API calls throughout your VB apps, you would pretty soon end up with a garbled mass of code that makes no sense to anyone, except perhaps you. I can't count the number of times I have frantically hunted through a huge VB app in search of a certain function only to realize after much angst that the programmer is using an API call.

The solution with VB5 is to turn the Windows API into easily reusable classes (or ActiveX controls-but more on them later). Each API call can be categorized according to which part of Windows it deals with. These categories can, in turn, be translated quite effectively into VB classes.

For our examples, how about a multimedia class that encapsulates the functionality of the multimedia API calls and, thus, the entire Windows multimedia system? That's what we'll look at next.

We'll start off by seeing how the class that I've put together works, then delve inside and see what's really happening.

Using the Multimedia Class

I have a great reuseable class module here which neatly wraps up a lot of the functionality of the multimedia API in Windows. Use it in your apps to provide access to sound and video files without the overhead of the heavyweight multimedia control that comes with Visual Basic. Let's do some typing.

Start a new Standard EXE project in Visual Basic, and then go to the Project menu and add a brand new class into your application. Then, bring up the code window and type this little lot in...it looks huge but it really isn'tI've padded the whole thing out with comments to make it a lot easier for you all to see what's going on.

 

Option Explicit

'-----------------------------------------------------

' Name : MMedia.cls

' Author : Peter Wright, For BG2VB4 & BG2VB5

'

' Notes : A multimedia class, which when turned

' : into an object lets you load and play

' : multimedia files, such as sound and

' : video.

'-----------------------------------------------------

' -=-=-=- PROPERTIES -=-=-=-

' Filename Determines the name of the current file

' Length The length of the file (Read Only)

' Position The current position through the file

' Status The current status of the object (Read Only)

' Wait True/False...tells VB to wait until play done

' -=-=-=- METHODS -=-=-=-=-

' mmOpen <Filename> Opens the requested filename

' mmClose Closes the current file

' mmPause Pauses playback of the current file

' mmStop Stops playback ready for closedown

' mmSeek <Position> Seeks to a position in the file

' mmPlay Plays the open file

'-------------------------------------------------------------

' NOTES

' -----

'

' Open a file, then play it. Pause it in response to a request

' from the user. Stop if you intend to seek to the start and

' play again. Close when you no longer want to play the file

'--------------------------------------------------------------

Private sAlias As String ' Used internally to give an alias name to

' the multimedia resource

Private sFilename As String ' Holds the filename internally

Private nLength As Single ' Holds the length of the filename

' internally

Private nPosition As Single ' Holds the current position internally

Private sStatus As String ' Holds the current status as a string

Private bWait As Boolean ' Determines if VB should wait until play

' is complete before returning.

'------------ API DECLARATIONS -------------

'note that this is all one code line:

Private Declare Function mciSendString Lib "winmm.dll" _

Alias "mciSendStringA" (ByVal lpstrCommand As String, _

ByVal lpstrReturnString As String, ByVal uReturnLength As Long, _

ByVal hwndCallback As Long) As Long

Public Sub mmOpen(ByVal sTheFile As String)

' Declare a variable to hold the value returned by mciSendString

Dim nReturn As Long

' Declare a string variable to hold the file type

Dim sType As String

' Opens the specified multimedia file, and closes any

' other that may be open

If sAlias <> "" Then

mmClose

End If

' Determine the type of file from the file extension

Select Case UCase$(Right$(sTheFile, 3))

Case "WAV"

sType = "Waveaudio"

Case "AVI"

sType = "AviVideo"

Case "MID"

sType = "Sequencer"

Case Else

' If the file extension is not known then exit the subroutine

Exit Sub

End Select

sAlias = Right$(sTheFile, 3) & Minute(Now)

' At this point there is no file open, and we have determined the

' file type. Now would be a good time to open the new file.

' Note: if the name contains a space we have to enclose it in quotes

If InStr(sTheFile, " ") Then sTheFile = Chr(34) & sTheFile & Chr(34)

nReturn = mciSendString("Open " & sTheFile & " ALIAS " & sAlias _

& " TYPE " & sType & " wait", "", 0, 0)

End Sub

Public Sub mmClose()

' Closes the currently opened multimedia file

' Declare a variable to hold the return value from the mciSendString

' command

Dim nReturn As Long

' If there is no file currently open then exit the subroutine

If sAlias = "" Then Exit Sub

nReturn = mciSendString("Close " & sAlias, "", 0, 0)

sAlias = ""

sFilename = ""

End Sub

Public Sub mmPause()

' Pause playback of the file

' Declare a variable to hold the return value from the mciSendString

' command

Dim nReturn As Long

' If there is no file currently open then exit the subroutine

If sAlias = "" Then Exit Sub

nReturn = mciSendString("Pause " & sAlias, "", 0, 0)

End Sub

Public Sub mmPlay()

' Plays the currently open file, from the current position

' Declare a variable to hold the return value from the mciSendString

' command

Dim nReturn As Long

' If there is no file currently open, then exit the routine

If sAlias = "" Then Exit Sub

' Now play the file

If bWait Then

nReturn = mciSendString("Play " & sAlias & " wait", "", 0, 0)

Else

nReturn = mciSendString("Play " & sAlias, "", 0, 0)

End If

End Sub

Public Sub mmStop()

' Stop using a file totally, be it playing or whatever

' Declare a variable to hold the return value from mciSendString

Dim nReturn As Long

' If there is no file currently open then exit the subroutine

If sAlias = "" Then Exit Sub

nReturn = mciSendString("Stop " & sAlias, "", 0, 0)

End Sub

Public Sub mmSeek(ByVal nPosition As Single)

' Seeks to a specific position within the file

' Declare a variable to hold the return value from the mciSendString

' function

Dim nReturn As Long

nReturn = mciSendString("Seek " & sAlias & " to " & nPosition, "", 0, 0)

End Sub

Property Get Filename() As String

' Routine to return a value when the programmer asks the

' object for the value of its Filename property

Filename = sFilename

End Property

Property Let Filename(ByVal sTheFile As String)

' Routine to set the value of the filename property, should the programmer

' wish to do so. This implies that the programmer actually wants to open

' a file as well so control is passed to the mmOpen routine

mmOpen sTheFile

End Property

Property Get Wait() As Boolean

' Routine to return the value of the object's wait property.

Wait = bWait

End Property

Property Let Wait(bWaitValue As Boolean)

' Routine to set the value of the object's wait property

bWait = bWaitValue

End Property

Property Get Length() As Single

' Routine to return the length of the currently opened multimedia file

' Declare a variable to hold the return value from the mciSendString

Dim nReturn As Long, nLength As Integer

' Declare a string to hold the returned length from the mci Status call

Dim sLength As String * 255

' If there is no file open then return 0

If sAlias = "" Then

Length = 0

Exit Property

End If

nReturn = mciSendString("Status " & sAlias & " length", sLength, 255, 0)

nLength = InStr(sLength, Chr$(0))

Length = Val(Left$(sLength, nLength - 1))

End Property

Property Let Position(ByVal nPosition As Single)

' Sets the Position property effectively by seeking

mmSeek nPosition

End Property

Property Get Position() As Single

' Returns the current position in the file

' Declare a variable to hold the return value from mciSendString

Dim nReturn As Integer, nLength As Integer

' Declare a variable to hold the position returned

' by the mci Status position command

Dim sPosition As String * 255

' If there is no file currently opened then exit the subroutine

If sAlias = "" Then Exit Property

' Get the position and return

nReturn = mciSendString("Status " & sAlias & " position", sPosition, 255, 0)

nLength = InStr(sPosition, Chr$(0))

Position = Val(Left$(sPosition, nLength - 1))

End Property

Property Get Status() As String

' Returns the playback/record status of the current file

' Declare a variable to hold the return value from mciSendString

Dim nReturn As Integer, nLength As Integer

' Declare a variable to hold the return string from mciSendString

Dim sStatus As String * 255

' If there is no file currently opened, then exit the subroutine

If sAlias = "" Then Exit Property

nReturn = mciSendString("Status " & sAlias & " mode", sStatus, 255, 0)

nLength = InStr(sStatus, Chr$(0))

Status = Left$(sStatus, nLength - 1)

End Property

 

When you're done typing this in, go to the properties window and change the name of the class to MMedia. Then save it to your hard disk and call it MMedia.cls. Save the main project's form as TestMM.frm, and the project itself as TestMM.vbp. We'll be using this project later-and you never know, you may want to use the class again later in your own projects.

This class turns a set of common multimedia calls into a stand-alone class. When an object is created from this class, it functions in exactly the same way as a control-in that it has properties you can set and examine, along with methods that actually make the object do something. This fits in well with the way we think about controls, and makes using the API calls invisible.

The methods that the class supports are as follows:

 

Method

Description

mmOpen

Opens a file (video, sound, music etc.) ready for playback.

mmClose

Closes the open file down, preventing any more playback.

mmPause

Pauses playback of the current file.

mmStop

Stops playback permanently.

mmSeek

Seeks a specific position within the file.

mmPlay

Take a guess; plays the open file, more often than not causing your speakers to burst into life.

These methods are all individual routines in MMedia.cls and all make use of the multimedia API calls in some way. We'll take a more detailed look at some of them in a moment to give you a feel for how the code actually fits together.

The following properties are implemented as property procedures in the source file:

 

Properties

Description

Filename

The name of the currently open file.

Length

The length of the currently open file.

Position

The current position through the file-you can use this in conjunction with the Length property to give the user some visual feedback as to the status of playback.

Status

A text word indicating the status of the file (playing, paused, stopped etc.).

Wait

Set this to true to make your code stop until playback has completed, or false to multitask.

Before we take a look at how the class does its thing, let's take a look at how to use the class itself. Along the way, you'll also see how seamless incorporating API calls into an app can be if you wrap the calls up nicely in a VB class.

Try It Out - Using the Multimedia Class

1 Open the TestMM.vbp project we just created if it isn't already open.

2 Resize the main form and draw a command button and common dialog control on it so that it looks like this:

3 We want to pop up the common dialog when the command button is pressed, so that the user is able to select a file name. Bring up the code window for the command button's Click event, and type this little lot in:

Private Sub Command1_Click()

With CommonDialog1

.Filter = "WaveAudio (*.wav)|*.wav|Midi (*.mid)|*.mid|Video files (*.avi)|*.avi"

.FilterIndex = 0

.ShowOpen

End With

End Sub

4 If you run the program now and click on the command button, you'll see the familiar file open dialog appear, asking you to select a multimedia file.

5 Quick and painless so far-all that remains is to bring the multimedia class into being as an object, and actually make use of it. Cancel the dialog and drop back into design mode, so that you can enter just a little more code. Bring up the command button Click event again and change it so that it looks like this:

Private Sub Command1_Click()

Dim Multimedia As New MMedia

With CommonDialog1

.Filter = "WaveAudio (*.wav)|*.wav|Midi (*.mid)|*.mid|Video files (*.avi)|*.avi"

.FilterIndex = 0

.ShowOpen

End With

If CommonDialog1.Filename <> "" Then

Multimedia.mmOpen CommonDialog1.Filename

Multimedia.mmPlay

End If

End Sub

6 Run the program. Find a multimedia file on your hard disk (there should be a few in Windows\Media) and have a play.

7 Save this project, because we'll be using it again in a while.

Of course, you'll need to have a sound card installed to play WAV and MID files.

How It Works

In the first line of the command button Click event code, we create a multimedia object which is derived from the MMedia class. This turns a class (which at this point is something ethereal and theoretical) into an object (something that can be used).

For the more technically minded amongst you, this process is normally referred to as instantiation.

Private Sub Command1_Click()

Dim Multimedia As New MMedia

The four lines of code we added at the bottom make use of our new multimedia object by opening the selected file using the class' mmOpen method, and playing it using the class' mmPlay method.

If CommonDialog1.Filename <> "" Then

Multimedia.mmOpen CommonDialog1.Filename

Multimedia.mmPlay

End If

As you can hopefully see from this, wrapping API calls up in a nice class make life a whole lot easier. If this class were used in a commercial organization, then the programmers using it wouldn't have to know anything about the underlying API calls-they would only need to be trained in how to use the multimedia class.

And of course, it has one other major attraction. Once you've been through the ritual of regular system reboots, as you find and eliminate the bugs in your API calling code, you don't want to have to do it all again in another project. Wrapping the API part in a class provides a safe, tested, and 'plug-in' capability-reducing the number of the API's customary three-fingered salutes that you'll need!

Understanding the Multimedia Class

While Visual Basic is great at detaching a programmer from some of the underlying complexities of Windows itself, there are still certain areas in which the API just can't be beaten, multimedia being one of them. For example, the multimedia class you just wrote uses just one API call, mciSendString. Before we take an in-depth look at the code itself, it's probably a good idea to look at this particular API call in a little detail.

The Multimedia Control Interface

Windows itself actually consists of a number of subsystems: separate units within Windows that handle entire areas of functionality. One such area is something called the MCI. MCI stands for Multimedia Control Interface, which provides a device-independent way to use the multimedia features of Windows through code.

Device-independent? OK! In the bad old days of DOS, a programmer writing a video game, for example, would have to cope with every possible standard and type of sound and video cards in order to satisfy the games market. Device-independence, and the device drivers that Windows provides, let you hit any sound card, video card, and so on with the same code, just so long as it's supported by Windows. The MCI provides this layer of independence, putting a neat bunch of functionality between you, the programmer, and the devices which would normally be used to handle multimedia data, namely the video and sound cards.

All this theory is great, but how exactly does the MCI work, and how does it provide this independence? The answer to both questions is that the MCI is responsible for talking to the Windows device drivers, and ultimately the multimedia hardware. You, the programmer, issue commands to the MCI using the API call mciSendString. These commands are then translated into calls to the appropriate Windows device driver, something that you no longer have to worry about. To put it into VB terms then, the MCI is a built-in Windows class, which we have essentially super-classed in the earlier example.

Hang on a minute-programmers issue commands to the MCI. A little strange? Well, yes. Normally, when you deal with API calls, you are actually calling the subroutines and functions embedded in Windows in order to do something. The MCI really is an independent object. It can be programmed, and has its own programming language. When you use mciSendString you are in fact programming the MCI-just as easily as if you were firing VB commands out of the debug window.

Using mciSendString

The format of mciSendString is quite simple

<ResultCode> = mciSendString("<Command>", <ReturnString>, <ReturnLength>, <CallbackHandle>)

This needs a bit of explanation. The <ResultCode> is a long integer, and varies depending on the command issued. The <Command> part (notice it is in quotes, so it's passed as a string literal) is the command you're sending to the MCI-such as Play to play a file, Open to open one, and so on. We'll look at exactly which commands the MCI understands later on, but for now let's cover the rest of the parameters.

Some MCI commands actually return a string value to your program. The MCI Status command, for example, can return a string telling your code whether a file is Stopped, Paused, Playing and so on. The string variable you place here will contain that return string.

The API call needs to know just how much data it can put in this string variable, so the next parameter passed is a number that is the length of the string. For this reason, if you are issuing a command to the MCI which returns a string, you must pass a fixed length string variable to the call and tell the mciSendString just how long that string is.

Dim sReturnString As String * 255

Dim nReturn As Long

nReturn = mciSendString("status waveaudio mode", sReturnString, 255, 0)

Don't worry about what this specific command does at this point, but notice the use of the fixed length string. Adding the * 255 to the declaration of sReturnString tells VB to fix its length to 255 characters.

The final parameter, the <CallbackHandle>, is something a bit specialist, which we're not going to be using in this book. However, to satisfy your curiosity, here's a brief description of what it means.

Callback Functions in Visual Basic

Callback functions really only used to apply to those writing code in C, C++, Delphi, or some other low-level compiled language-and not Visual Basic. However, as of version 5, Visual Basic now lets you use callbacks in your code, without requiring special add-ins that were the only way to make it work in earlier versions.

When you use API subroutines and functions normally, your code has no way of knowing what's going on while that routine is running. You have to wait until it ends, then examine the return value. The principle idea of a callback is that an API function can call routines that are inside your Visual Basic code, while the API routine is running.

To do this, you have to create a Public function inside a VB code module, which has the correct number and type of parameters-as expected by the API routine. Then, when you call the API routine, you send it a pointer (the address in memory) of your VB callback function. To do this, you make use of the new Visual Basic AddressOf operator:

nResult = SomeAPIFunction(ParamOne, ParamTwo, AddressOf MyCallback)

As the API routine executes, it will call your VB function and send it the relevant parameters. This is often used to update status bars, get lists of system fonts, and other varied tasks.

As I said earlier, we won't be covering callbacks in this book. They provide an extra layer of complexity in your code, and even more ways to crash your system. However, the Visual Basic help files do give some examples if you'd like to experiment further.

Opening the Media File

Before you can tell the MCI what to do, you have to tell it which file you want to do it on. This is like using disk files. You must start by sending the Open command before you can do anything else.

The first part of this code should be self explanatory: you tell the Open command the name of the file that you want to open. This is a standard filename like c:\video.avi.

Open <filename> Type <typestring> Alias <aname>

...

...

'Issue commands to do something to the file

...

...

Close <aname>

After the Type keyword, you need to tell Windows what kind of file you are dealing with. The standard Windows ones are WaveAudio for WAV files, AVIVideo for AVI files and Sequencer for MID files.

Finally, you can tell the MCI to give the file you just opened a name, an Alias, which you will use from now on to refer to the open file. It's rather like naming a variable, in that the name can be almost anything you want. For example:

Open c:\video.avi Type AVIVideo Alias Peter

If you sent this to the MCI with MCISendString, it would tell the MCI to open a file called c:\video.avi as a Microsoft video file and that, in future MCI commands, we'll refer to this file using the name Peter.

Once opened, normal MCI commands can be issued using the alias to play the file, stop it, pause it, find out its status and so on. For example:

Play Peter

Pause Peter

Stop Peter

There are literally hundreds of combinations of MCI commands that you can use, and we'll take a look at the most common a little later.

Once you have done your stuff with a file, you need to close it down by sending the Close command, followed by the alias of the file you are dealing with.

nReturn = mciSendString("Close Peter", "",0,0)

Let's take a look at how the code does what it does now, and see more of what our new class can do.

Try It Out - Displaying Status and Position for a Multimedia File

1 Open the TestMM.vbp project we just created if it isn't already open.

2 We're going to add some controls to see how the Status and Position properties of our MMedia class can be used. Add a ProgressBar, a Label, and a Timer control to your form, so it looks like this:

If you can't find the progress bar in your toolbox, add it by selecting Components from the Project menu, and check the Microsoft Windows Common Controls 5.0 option box.

3 Open the properties window for the Timer control, and set its Enabled property to False, and its Interval property to 500. Clear the caption for the label control at the same time.

4 Double-click on the Load and play a file button to open the code window for its Click event. Add these highlighted lines to the code:

Private Sub Command1_Click()

...

'other existing stuff here

...

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

End Sub

5 Back in the form, double-click the Timer1 control to open the code window for its Timer event. Add this code to it:

Private Sub Timer1_Timer()

ProgressBar1.Value = Multimedia.Position

Label1 = "Status: " & Multimedia.Status

If ProgressBar1.Value = ProgressBar1.Max Then

Multimedia.mmClose

Timer1.Enabled = False

End If

End Sub

As it stands now, we have a minor problem. We defined the variable that refers to instance of our MMedia class within the Command1_Click() event routine. Now we want to refer to it from our Timer1_Timer() event routine as well.

6 In the Click event for the command button, select the line that declares the Multimedia variable, press Ctrl-X to cut it to the clipboard. This removes it from the Command1_Click() event routine. Then select (General) in the top left drop-down list in the code window, and hit Ctrl-V to paste it into the General Declarations section, where it will be available to all the code in this form.

7 OK, we're ready to go. Click the Load and play a file button, and select a multimedia file as before. For a change this time, we've chosen one of the 'Welcome to Windows 95' videos from the Windows CD-ROM.

8 You can see the progress bar now shows how far through the file we are, and the status-in this case playing. When the video is finished, you'll see this change to stopped.

How It Works

We'll use this example to show you in more detail how some parts of the MMedia class work, and how we've used them. Firstly, the code that runs when you click the Load and play a file button.

We added four lines to the routine you saw in the earlier example. These are all concerned with setting up the controls on the form, and the properties of the Multimedia object, before we start playing the file.

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

Our MMedia class, and hence our Multimedia object, has a property named Wait. This determines if the code in our VB program will continue to run (multitask) while the file is playing, or just stop and wait for it to finish, like it did in the earlier example. The mmPlay method of the class, which we will shortly use to start the file playing, looks at the value of a private variable in the class named bWait. If it's True, it includes the command wait in the mciSendString call:

Public Sub mmPlay() 'in the MMedia class

Dim nReturn As Long

If sAlias = "" Then Exit Sub

If bWait Then

nReturn = mciSendString("Play " & sAlias & " Wait", "", 0, 0)

Else

nReturn = mciSendString("Play " & sAlias, "", 0, 0)

End If

End Sub

And how does bWait get set to the correct value? Remember from our discussions on class modules, that we can supply property routines which allow the values of the internal variables to be set and read just like a normal Visual Basic control's properties:

Property Get Wait() As Boolean

'Routine to return the value of the object's wait property.

Wait = bWait

End Property

Property Let Wait(bWaitValue As Boolean)

'Routine to set the value of the object's wait property

bWait = bWaitValue

End Property

The next step is to open the file we want to play. To do this, we use the MMedia class' mmOpen method.

Opening the File

First off, we declare a couple of local variables to hold temporary values. We'll see what these are for shortly. Then there's a lot of code devoted to building up the command string, before we send it to the MCI.

Public Sub mmOpen(ByVal sTheFile As String)

Dim nReturn As Long

Dim sType As String

If sAlias <> "" Then

mmClose

End If

Select Case UCase$(Right$(sTheFile, 3))

Case "WAV"

sType = "Waveaudio"

Case "AVI"

sType = "AviVideo"

Case "MID"

sType = "Sequencer"

Case Else

Exit Sub

End Select

sAlias = Right$(sTheFile, 3) & Minute(Now)

If InStr(sTheFile, " ") Then sTheFile = Chr(34) & sTheFile & Chr(34)

nReturn = mciSendString("Open " & sTheFile & " ALIAS " & sAlias _

& " TYPE " & sType & " wait", "", 0, 0)

End Sub

First the mmOpen routine checks a class level/module level variable called sAlias.

If sAlias <> "" Then

mmClose

End If

Whenever you deal with the MCI, it's a good idea to assign aliases to each file you have open. Here, the MMedia class works out a name for the alias for you and stores it in sAlias. When you next go to open a file with mmOpen, or set the file name property, the code can check this and call another routine which closes the first file down. Closing multimedia files down when they are no longer needed frees up memory and speeds up playback, so it's always a good idea.

A familiar Select Case construct is then used to figure out the file type. When you open a file with the MCI, you need to tell it what type of data the file holds. The Select Case construct here does that, storing the MCI type name in the sType variable declared at the start of the routine.

Select Case UCase$(Right$(sTheFile, 3))

Case "WAV"

sType = "Waveaudio"

Case "AVI"

sType = "AviVideo"

Case "MID"

sType = "Sequencer"

Case Else

Exit Sub

End Select

At this point, any previously opened file has been closed, and the type name of the new file has been stored in sType. All that remains is to decide on an alias for the file, and then open it. Aliases must be unique, since the MCI can cope with having more than one file open-and even playing. Since we don't want to force unnecessary complexity on the user of the class, it makes sense if the class decides on its own alias.

sAlias = Right$(sTheFile, 3) & Minute(Now)

It does this by taking the right three characters of the file name and appending the minutes part of the current system time. So if it was 16:15 when you opened c:\video.avi, then the alias the class would come up with would be AVI15. The newly calculated alias is then stored in the sAlias module level variable that we checked when the procedure first started. The value in sAlias is then used throughout the rest of the module whenever we need to do something to the open file-like play it.

So, armed with the file type in one variable, the alias in a second, and the file name in a third, we can finally send the Open command to the MCI. The only fly in the ointment is the need to surround file names which contain spaces with quotation marks (") first:

If InStr(sTheFile, " ") Then sTheFile = Chr(34) & sTheFile & Chr(34)

nReturn = mciSendString("Open " & sTheFile & " ALIAS " & sAlias & " TYPE " & sType & " wait", "", 0, 0)

The command sent is Open, followed by the file name, followed by ALIAS then the new alias (held in sAlias), followed by TYPE and the type, and finally, the word wait.

The wait statement on the end here tells the API not to let our VB code continue running until it has finished loading the file. Without this, on a fast machine with a slow hard disk, problems can occur-you might try to play the file before it has loaded, simply because the code is running a lot faster than the hard disk. Notice that this isn't the same as the Wait property that we looked at earlier, which controls whether our program continues to run while the file is playing, and not while it's loading.

So, now, we can set up the progress bar ready to show the progress of the file's playback. But we need to know how long it is, so that we can set the progress bar's Max value. And of course, we'll need to know the relative position in the file at regular intervals so that we can update it while the file is playing. To recap, here's the Command1_Click event again. So far we've only managed to open the file that we want to play:

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

The highlighted code above is the bit that looks after setting up the progress bar. We set it's current Value to 0, which resets it from the last time it was used (i.e. if another file has been played previously). Then we set its Max value to length of the file we've just opened, using the Length property of our Multimedia object.

Getting the File Length

We can use mciSendString to return values, as well as to set them. The Length property of our MMedia class is read-only, because we haven't provided a Property Let routine. We wouldn't expect to be able to set the length of the file anyway. However, we do have a Property Get which returns the length of the currently open file:

Property Get Length() As Single

Dim nReturn As Long, nLength As Integer

Dim sLength As String * 255

If sAlias = "" Then

Length = 0

Exit Property

End If

nReturn = mciSendString("Status " & sAlias & " Length", sLength, 255, 0)

nLength = InStr(sLength, Chr$(0))

Length = Val(Left$(sLength, nLength - 1))

End Property

First sAlias is checked to see if a file has been opened. If it hasn't, then the value returned from the property procedure is 0. If a file has been opened, then the MCI Status Length command is used to find out how long it is. You needn't worry about how the length of the file is measured, since any unit of measurement will be suitable for setting up the progress bar. Just for your interest, though, you can set the unit of measurement to various values, ranging from frames in a video clip to milliseconds in a sound file. However, a complete rundown of the options is a little beyond the scope of what we are trying to achieve here.

The Status command is a rather special MCI command and can be used in conjunction with keywords like Length, Position, and Mode to find out a great deal of information about the current file. It returns this information in a fixed length string variable which is passed to mciSendString after the MCI command. In this example, the return string is called sLength and is declared to be 255 characters long.

Of course, you're not always going to get 255 characters back from the Status command, especially if you only want to know the length of the file. The unused space in the string is filled with the character 0, making it easy for us to use VB's InStr function to find out exactly how long the returned data is, and pull it out.

nReturn = mciSendString("Status " & sAlias & " Length", sLength, 255, 0)

nLength = InStr(sLength, Chr$(0))

Length = Val(Left$(sLength, nLength - 1))

Here, the position of the first Chr(0) is stored in nLength, since InStr returns the character position at which the search data is located, or 0 if it can't find what you are looking for. This now gives us enough information to pull the characters from the left side of the fixed length string, convert them to a number (rather than a string) and return that value ready to go into the Length property.

Getting the Current Position

When the file is actually playing, the Status Position command can be used repeatedly to find out exactly where in the file playback has reached. The code to return the value of the Position property looks strangely familiar:

Property Get Position() As Single

Dim nReturn As Integer, nLength As Integer

Dim sPosition As String * 255

If sAlias = "" Then Exit Property

nReturn = mciSendString("Status " & sAlias & " Position", sPosition, _

255, 0)

nLength = InStr(sPosition, Chr$(0))

Position = Val(Left$(sPosition, nLength - 1))

End Property

The only real difference this time is that instead of sending Status Length to the MCI, we are sending Status Position.

Getting the Current Status

To get the text message for the status, sometimes called the mode, we query the class' Status property. This is done using another Property Get routine, almost identical to the Position property we've just seen. The only differences are that we send the command Status Mode instead of Status Length or Status Position to the mciSendString function. And, of course, we don't need to convert the result to a number, because it's supposed to be a text string:

...

nReturn = mciSendString("Status " & sAlias & " Mode", sStatus, 255, 0)

nLength = InStr(sStatus, Chr$(0))

Status = Left$(sStatus, nLength - 1)

...

OK, let's go back to that Command1_Click event again, and see where we are. So far, we've set the Wait property, opened the file, and set up the progress bar. Before we actually start playing the file, we'll set our Timer control ticking. You'll see what it does in just a moment. Then, finally, we'll play the file by calling the mmPlay method of our Multimedia object:

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

Starting the File Playing

Here, again, is the complete code for the mmPlay method of our MMedia class. To execute it, we just need to call it in our Multimedia object. It first checks to see we have a file open by examining the sAlias variable, then if all is well it executes the MCI Play command. We've already seen the way we set and use the Wait property, which controls whether the command is actually Play Wait or just Play.

Public Sub mmPlay() 'in the MMedia class

Dim nReturn As Long

If sAlias = "" Then Exit Sub

If bWait Then

nReturn = mciSendString("Play " & sAlias & " Wait", "", 0, 0)

Else

nReturn = mciSendString("Play " & sAlias, "", 0, 0)

End If

End Sub

Updating the Progress Bar and Label Controls

Our final task is to update the progress bar and the label on the form, as the file is playing. We've already covered all the stuff we need to do this with. Before we set the file playing, we enabled a Timer control with an Interval of 500. So it will fire every half a second. Each time it does, the code in the Timer1_Timer() event routine runs:

Private Sub Timer1_Timer()

ProgressBar1.Value = Multimedia.Position

Label1 = "Status: " & Multimedia.Status

If ProgressBar1.Value = ProgressBar1.Max Then

Multimedia.mmClose

Timer1.Enabled = False

End If

End Sub

You're probably well ahead of me by now. All this code does is take the value of the Multimedia object's Position property and assign it to the ProgressBar control's Value property, updating the display on the form. Then it gets the Multimedia object's current Status property, which is a text string, and pops it into the Label1 control on the form. Remember, this is happening twice a second.

Simply retrieving these properties, of course, runs the Property Get routines we wrote in the class, which in turn use the MCI Status Position and Status Mode commands each time. The only other thing is to stop it all happening when we get to the end of the file. This is done by comparing the progress bar's current and maximum values. When they're the same, it's time to close the file using the Multimedia object's mmClose method, then disable the timer to prevent this routine running again until another file is opened.

Our MCI Commands Summary

To end, here's a list of the MCI commands used in our MMedia class:

 

Command

Description

Play

Plays a file.

Pause

Pauses playback, ready to start up again at any time.

Stop

Stops a file-you need to seek to a position to continue playback.

Seek

Followed by a number, seeks to that position in the file.

Status Mode

Returns a string indicating what the file is doing (i.e. Stopped, Paused, Playing, Ready).

Status Position

Returns a number indicating the position through the file that playback has reached.

Status Length

Returns the length of the file and helps to put the number returned from Status Position into some meaningful context.

Close

Closes the file and frees up the memory it previously occupied.

The MCI supports a few more commands than this, and also a number of specialized ones for each file format. What I hope to have shown you is how even apparently complex topics, such as multimedia, really aren't that hard if you drop down to the API. Also, that the API itself is not the daunting mother of all nightmares that many VB programmers make it out to be.

Summary

This chapter should have provided you with a glimpse of the power that lies beyond the strict limits of Visual Basic, as defined in the language itself. This is a huge subject, and so what we've done is touch on the main principles of using the Windows API. With power comes responsibility, because once you choose to operate outside the confines of Visual Basic, you have to look after yourself. It pays to develop a deep understanding of all the components of Windows, so you can write Visual Basic programs that are truly well-behaved members of the Windows desktop community.

In our brief tour we covered:

    How Windows and Visual Basic fit together
    The Windows API
    How to use the Windows API to extend the power of Visual Basic
    How to write well-behaved Windows applications