Show and Tell - Enhancing your Applications with the Windows MCI

by Gary O'Donnell

Published 1998-11-01    Printer-friendly version

Download the code here

If you’re reading this article you probably don’t need anyone to convince you that Clarion is a pretty cool language when it comes to focusing your time and efforts on achieving your programming goals instead of the drudgery of general Windows housekeeping. And, of course, you’re so much more productive than if you were working in C++, which means that new Time & Expense system they aren’t expecting in Finance until Q2, 1999 is actually ready to run. Meanwhile you’ve run out of fun things to do between providing those regular project status updates to the CFO indicating painfully slow increments in the ‘% Complete’ field. So what next?

Well, what about all those times when you’ve thought how nice it would be to have the computer shriek a "Don’t press that button, stupid!" when they select ‘Purge YTD Data’, or to play a little video showing how to use the mouse for those less familiar users? It turns out that Clarion is well equipped to support this functionality without anything more than a passing familiarity with, yes you guessed it, the Windows Application Programming Interface (API).

It’s barely surprising that we have to drop into the Windows API to do what we want, but what is surprising is the ease with which the good old boys at Microsoft allow us to twiddle with our multimedia bits ‘n’ pieces. There is a section of the API, called the Media Control Interface (or MCI), which is devoted to accessing the multimedia devices. It is this section that we will be concentrating on in a series of articles that will build an interesting real-world application.

Well, OK, so if you’ve surfed the shareware archives on the Internet you probably already have more CD players than you have CDs, but how many of them have you written yourself in your favorite programming language? And, for the icing on the cake, how many of them allow you to do all the fancy stuff that the Windows 98 Plus CD Player does, which is to access an Internet-based CD archive and download the CD disc, artist and track information? All of this, and more, is to come in this column over the next few months.

Overview

The MCI provides us with a method of controlling the multimedia devices attached to the system. Multimedia devices are those peripherals that provide or accept audio or visual feedback. Supported multimedia devices include waveform-audio devices, MIDI sequencers, CD audio devices, and digital-video (video playback) devices.

The beauty of the MCI is that it is device-independent, meaning that any application you write that works with an XYZ CD player will work with the model from ABC also (assuming they have the same capabilities, which can be determined through the MCI). The MCI therefore both shields the programmer from the intricacies of individual device drivers, as well ensuring that the application can be released with confidence into the real world without worrying about how it will function on different hardware.

Additionally, the Microsoft developers who designed the MCI have provided two methods of accessing the functionality that it offers. The first, a command-oriented approach, is ideal for C and C++ coders, since those commands that return data do so in structures that are easy for these languages to manipulate. This is known as the command-message interface.

The second approach is much simpler for Clarion to use, since it accepts strings, and any data that is to be returned is contained inside a string. Both methods support all MCI functions and therefore this latter method, the command-string interface, is the approach that we will use. For more information on the command-message interface, I suggest you consult the Windows Platform SDK.

The MCI is contained inside WINMM.DLL, which should reside in your WINDOWS\SYSTEM directory. If you have not installed the multimedia functions on your computer then this file may not exist, but I would think this would be an extremely rare occurrence.

"Oi! - Don’t touch me there!!"

I’m pretty sure it’s been the desire of every developer with a sense of humor, and too much time on his hands, to slip a little wave file into his software to see if the user is awake. There are, however, serious applications as well as the fun ones. Like when you want to alert your user to an error and know that he will not be focussing on the screen, or if an event is triggered in the background and you want to inform the user of it without him losing focus of his foreground task. Therefore, let us make this the first stop on our tour of the MCI.

We already mentioned that to interact with the MCI we will be sending command strings, so the first thing to look at is the mechanism which we will use to pass these commands to the MCI devices. The function we will call to do this is mciSendString(), whose prototype is as follows:

OMIT('***',_WIDTH32_)
mciSendString(*LPCSTR,*LPSTR,UINT,HWND),|
              DWORD,PASCAL,RAW
***
COMPILE('***',_WIDTH32_)
mciSendString(*LPCSTR,*LPSTR,UINT,HWND),|
              DWORD,PASCAL,RAW,|
              NAME('mciSendStringA’)
***

where:

DWORD       EQUATE(ULONG)
UINT        EQUATE(UNSIGNED)
LPSTR       EQUATE(CSTRING)
LPCSTR      EQUATE(CSTRING)
HANDLE      EQUATE(UNSIGNED)
HWND        EQUATE(HANDLE)

Notice that we have equated the Windows standard types to the relevant Clarion native types. This makes it easier to prototype Windows API functions inside of Clarion. Use the WINAPI program that comes with Clarion to determine the Clarion versions of function prototypes and Windows Data types.

The syntax of mciSendString() is:

mciSendString(Command, ReturnString, ReturnStringSize, Callback)

Command: A CSTRING that specifies the MCI command string
ReturnString: A CSTRING that will receive returned information.
ReturnStringSize: Size in bytes of the ReturnString
Callback: Handle of a callback window if the "notify" flag was set in the command string.

Return Value

Returns zero if successful or an error otherwise. The low-order word (least significant, or rightmost, 16 bits) of the returned ULONG value contains the error return value. If the error is specific to a given device, the high-order word (most significant, or leftmost, 16 bits) of the return value is the driver identifier; otherwise, the high-order word is zero.

The value returned by mciSendString() can be passed to the mciGetErrorString() function in order to return a text description of the error.

A call to mciSendString() would therefore take the following form:

ErrCode = mciSendString(CmdStr, |
          Reply, Size(Reply), 0)

Since we get an error code back to indicate success or failure, we should really act upon the error. In this case it would be easiest if we just inform the user of what the error is by getting the text description from the mciGetErrorString() function. The prototype and syntax of this function follow:

OMIT('***',_WIDTH32_)
mciGetErrorString(DWORD,*LPSTR,UINT),|
                  BOOL,PASCAL,RAW
***
COMPILE('***',_WIDTH32_)
mciGetErrorString(DWORD,*LPSTR,UINT),|
                  BOOL,PASCAL,RAW,|
                  NAME('mciGetErrorStringA')
***
mciGetErrorString(Error, ErrorString, ErrorStringSize)

Command: A ULONG that contains the MCI error code
ErrorString: A CSTRING that will receive the returned error text
ErrorStringSize: Size in bytes of the ErrorString

With just these two commands it is possible to control multimedia devices using the MCI. Consider this small code fragment:

CmdStr = 'play c:\windows\media\chord.wav'
ErrCode = mciSendString(CmdStr, Reply, Size(Reply), 0)
IF ErrCode
  IF mciGetErrorString(ErrCode, Err, Size(Err))
    MESSAGE(''''&CmdStr&''' failed with error:||' & Err)
  ELSE
    MESSAGE(''''&CmdStr&''' failed with unknown error: '|
            & ErrCode)
  END !If
END !If

This code will load the file wavefile ‘chord.wav’, which exists in the MEDIA subdirectory of your main Windows directory, and play it. Note that if your Windows directory is located somewhere other than C:\WINDOWS you will need to modify the path accordingly. The file is located in \WINNT\MEDIA on Windows NT machines, for example.

This, then, is a remarkably easy method of playing any wavefile that exists on your computer. However, notice that the actual command string is a CSTRING that, according to Clarion syntax, can not be passed in the form:

ErrCode = mciSendString('play c:\windows\media\chord.wav',|
                         Reply, Size(Reply), 0)

Also, we should perform our error checking every time we send an MCI command. This command is therefore an ideal candidate for a function that we will call as a wrapper to the MCI’s mciSendString() function. By defining a function as shown in the following program we now have a single-line method of executing any MCI command. I have trimmed some of the fat to keep the example clear. The full copy of this program is available by clicking the "Download the code here" link above.

PROGRAM

!Windows type equates go here
  MAP
    SendMCI(STRING, HWND=0), STRING, PROC
    MODULE('Windows MCI')
      !MCI prototypes go here
    END !MODULE
  END !MAP
  CODE
SendMCI('play c:\windows\media\chord.wav')

SendMCI     PROCEDURE(PRM:Str, PRM:Hwnd)
mdStr       CSTRING(256)
Reply       CSTRING(256)
Err         CSTRING(256)
ErrCode     ULONG
  CODE
  CmdStr = CLIP(PRM:Str)
  ErrCode = mciSendString(CmdStr, Reply, |
                          Size(Reply), PRM:Hwnd)
  IF ErrCode
    IF mciGetErrorString(ErrCode, Err, Size(Err))
      MESSAGE(''''&CmdStr&''' failed ' & |
      'with error:||' & Err)
    ELSE
      MESSAGE(''''&CmdStr&''' failed with'&|
      &' unknown error: ' & ErrCode)
    END !If
  END !If
  RETURN(Reply)

So now that we know how to interact with the MCI, we will spend next month looking at how to control devices that offer a little more complexity than the wavefile player. A CD-ROM drive which doubles as an audio CD player, as almost all of them do, both accepts command from the user and returns information. Our next task will be to tame this beast and investigate just what can be achieved with it.

Until then have fun experimenting with the above code in your own applications, or expand on the above program to create a wavefile archiving system.

Printer-friendly version