Using DLLs and The
Windows API
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!
Page [<<
PREV] 1 2
3 4 [NEXT
>>]
Back to Tutorials - Main
|