Adding New MCI Commands

This section applies to your driver only if you need to add new MCI commands or provide extensions to the MCI command set. Do the following to add or extend a command set:

1 Define the new MCI messages you plan to support with your driver.

2 Define new structures and flags for this command to use with mciSendCommand.

MCI messages use lParam2 as a pointer to a structure, and use lParam1 as a bitmember for the flags associated with the message. A flag exists for each member in the structure that accepts data from the calling application. The application sets the flag in the bitmember of lParam1 to indicate a value is assigned to a particular member. Flags also specify options without parameters and these flags do not correspond to a member in the structure.

3 Create a command table that MCI will use to translate string commands sent through mciSendString into the command messages and structures used by your driver.

4 Support the new messages in your driver's DriverProc.

5 Register the new command table with MCI in your driver's DRV_LOAD procedure.

Define the New Messages for Your Driver

Suppose you are writing a driver for a videodisc player and this player supports a new command, "reset." This command is invoked by the string "reset program_number." The command sets all player parameters to their default settings or one of several predefined states indicated by the program_number.

You would add the following message to the list of messages you must support in DriverProc:


#define    MCI_VDISC_RESET    1000

MCI_USER_MESSAGES is the first integer you can use for custom messages. Since the resource compiler does not accept mathematical expressions in the RCDATA resource type, you must indicate the specific value you want to use.

Now that you have defined the message ID, you must define the structure and the command table, and create the code to handle this message in DriverProc.

Defining the Structure

The members of the structure for an MCI message are always 32 bits long - the size of doubleword values. The number of members in the structure depends on the particular message. The first member must be reserved for a handle to a window function used with the MCI_NOTIFY flag. The next members in the structure depend on the type of data returned for the message:

Assigning Flag Values

In addition to indicating the members used in a structure, flags can indicate an option that does not use any parameters. For example, the wait flag does not use any parameters.

When you add new flags, you must ensure they do not conflict with the flags already used. Bits 0 through 15 of the 32-bit DWORD are reserved for MCI. Bit 16 (0x00010000) is the first bit that a driver can use for its flags. If your command is a new command, you can start with this bit position. If your command extends a command, you must choose bit positions that do not conflict with the flags already defined for that command. Any unused bits in the new flag must be set to zero. For example, if a new command for videodisc players uses flags 16 through 20, a custom extension to the new command could use flags 21 through 31.

To continue the example for adding a "reset" command to a videodisc driver, the following code fragment defines a structure, a corresponding pointer for it, and the flag corresponding to the program member of the structure:


typedef struct {
    DWORD    dwCallback;
    DWORD    dwProgram;
} MCI_VDISC_RESET_PARMS;

typedef      MCI_VDISC_RESET_PARMS FAR * LPMCI_VDISC_RESET_PARMS;

#define      MCI_VDISC_RESET_PROGRAM        0x00010000L

When the application sets the MCI_VDISC_RESET_PROGRAM flag in lParam1, it indicates that a value is assigned in the dwProgram member.

Now that you have created the message command, flags and structure, you need to create the command table that tells MCI how to translate the equivalent string command into the message command form. Creating the command table is explained in the following section.

Creating the MCI Command Tables

The MCI parser used by mciSendString uses the command tables to tell it how to translate an MCI command string into parameters used by DriverProc. Most of these tables are stored as resources within the device driver. The resource type is RCDATA.

To allow applications to use mciSendString with any new or extended commands that your driver supports, you must create a command table to indicate how to map the string commands to the message commands.

Contents of the Command Table

A command table consists of three columns:

The following example shows a partial command table:


/*************************************************************/
/*   Sample command table for the MCI core command set       */
/*************************************************************/
core RCDATA
BEGIN
"open\0",              MCI_OPEN, 0,                  MCI_COMMAND_HEAD,
"\0",                MCI_INTEGER, 0,                 MCI_RETURN,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG,
"test\0",            MCI_TEST,                       MCI_FLAG,
"type\0",            MCI_OPEN_TYPE,                  MCI_STRING,
"element\0",         MCI_OPEN_ELEMENT,               MCI_STRING,
"alias\0",           MCI_OPEN_ALIAS,                 MCI_STRING,
"shareable\0",       MCI_OPEN_SHAREABLE,             MCI_FLAG,
"\0",                0L,                             MCI_END_COMMAND,

"close\0",             MCI_CLOSE, 0,                 MCI_COMMAND_HEAD,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG ,
"test\0",            MCI_TEST,                       MCI_FLAG,
"\0",                0L,                             MCI_END_COMMAND,


"play\0",              MCI_PLAY, 0,                  MCI_COMMAND_HEAD,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG ,
"test\0",            MCI_TEST,                       MCI_FLAG,
"from\0",            MCI_FROM,                       MCI_INTEGER,
"to\0",              MCI_TO,                         MCI_INTEGER,
"\0",                0L,                             MCI_END_COMMAND, 
                     .
                     .
                     .
"capability\0",        MCI_GETDEVCAPS, 0,            MCI_COMMAND_HEAD,
"\0",                MCI_INTEGER, 0,                 MCI_RETURN,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG ,
"test\0",            MCI_TEST,                       MCI_FLAG,
"\0",                MCI_GETDEVCAPS_ITEM,            MCI_CONSTANT
"can record\0",      MCI_GETDEVCAPS_CAN_RECORD,      MCI_INTEGER,
"has audio\0",       MCI_GETDEVCAPS_HAS_AUDIO,       MCI_INTEGER,
"has video\0",       MCI_GETDEVCAPS_HAS_VIDEO,       MCI_INTEGER,
"uses files\0",      MCI_GETDEVCAPS_USES_FILES,      MCI_INTEGER,
"compound device\0", MCI_GETDEVCAPS_COMPOUND_DEVICE, MCI_INTEGER,
"device type\0",     MCI_GETDEVCAPS_DEVICE_TYPE,     MCI_INTEGER,
"can eject\0",       MCI_GETDEVCAPS_CAN_EJECT,       MCI_INTEGER,
"can play\0",        MCI_GETDEVCAPS_CAN_PLAY,        MCI_INTEGER,
"can save\0",        MCI_GETDEVCAPS_CAN_SAVE,        MCI_INTEGER,
"\0",                0L,                             MCI_END_CONSTANT,
"\0",                0L,                             MCI_END_COMMAND,
"resume\0",          MCI_RESUME, 0,                  MCI_COMMAND_HEAD,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG, 
"\0",                0L,                             MCI_END_COMMAND,
"\0",                0L,                             MCI_END_COMMAND_LIST

END

When you build your command table, ensure that the entries in the second column are DWORDs, and the entries in the third columns are WORDs. If you use any WORDs in the second columns, pad them with a comma and a zero. For example, the MCI_PLAY message in the example is followed by a comma and a zero. The zero is used as a filler to keep the command list properly aligned. Forgetting the zero after a WORD causes the remainder of the command table to be misaligned by one WORD.

A command table consists of command lists. A command list defines how to parse a particular command. For example, the following is a command list for the play command:


"play\0",            MCI_PLAY, 0,                    MCI_COMMAND_HEAD,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG ,
"test\0",            MCI_TEST,                       MCI_FLAG,
"from\0",            MCI_FROM,                       MCI_INTEGER,
"to\0",              MCI_TO,                         MCI_INTEGER,
"\0",                0L,                             MCI_END_COMMAND,

This command list tells the parser which flags are valid and how to create the associated structure for the message command.

Command List and Command Table Delimiters

The first entry in a command list must have the MCI_COMMAND_HEAD delimiter in the third column. The MCI_COMMAND_HEAD delimiter designates the verb portion of the string command. For example, the play command list starts with this entry:


"play\0",            MCI_PLAY, 0,                    MCI_COMMAND_HEAD,

The last entry in a command list must have the MCI_END_COMMAND delimiter. For this last entry, the first and second columns contain "\0" and 0L. For example, all command lists end with this entry:


"\0",                0L,                             MCI_END_COMMAND,

The last entry of the command table must have the MCI_END_COMMAND_LIST delimiter. The first and second columns for this entry also contain "\0" and 0L. For example, the example command table ends with this entry:


"\0",                0L,                            MCI_END_COMMAND_LIST
Return Values

If your want to return data in your structure, you can reserve a member by having an entry in the command list with the MCI_RETURN in the third column. If you use MCI_RETURN, you must use it for the entry immediately following the MCI_COMMAND_HEAD. You can use MCI_RETURN only once in a command list. For example, the MCI_GETDEVCAPS command list starts with the following entries:


"capability\0",      MCI_GETDEVCAPS, 0,              MCI_COMMAND_HEAD,
"\0",                MCI_INTEGER, 0,                 MCI_RETURN,

The string in the first column of an entry with MCI_RETURN is "\0". (This column is ignored when you use MCI_RETURN.)

The second column of the MCI_RETURN entry specifies the type of data returned. The parser recognizes the MCI_INTEGER, MCI_STRING, and MCI_RECT keywords for the data type returned.

If you want to return an integer value, use MCI_INTEGER,0 in the second column. For integers, MCI reserves the second DWORD in the structure for the return value.

If you want to return a string value, use MCI_STRING,0 in the second column. For strings, MCI reserves the second and third DWORDs in the structure for the return value. These DWORDs correspond to a pointer to a buffer for the zero-terminated return string and the size of the buffer supplied by the application.

If you want to return a RECT value, use MCI_RECT,0 in the second column. For rectangles, MCI reserves the second and third DWORDs in the structure for the return value.

The following example adds an integer return value to the reset command:


"reset\0",           MCI_VDISC_RESET, 0,             MCI_COMMAND_HEAD,
"\0",                MCI_INTEGER, 0,                 MCI_RETURN,   
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG,
"test\0",            MCI_TEST,                       MCI_FLAG,
"program\0",         MCI_VDISC_RESET_PROGRAM,        MCI_INTEGER,
"\0",                0L,                             MCI_END_COMMAND

The corresponding structure for the reset command using a return value is as follows:


typedef struct {
   DWORD   dwCallback;
   DWORD   dwReturn
   DWORD   dwProgram;
} MCI_VDISC_RESET_PARMS;
Command Flag Keywords

MCI interprets an entry with MCI_FLAG in the third column as a flag that is not associated with any member of the structure. For example, all commands have the following entry for the MCI_NOTIFY flag:


"notify\0",          MCI_NOTIFY,                     MCI_FLAG

The first column of this entry contain the string command used for the flag. The second column contains the value used for the flag.

Associating Command List Entries to Structures

MCI associates a command list entry with a structure member when the command list entry has MCI_INTEGER, MCI_STRING, or MCI_RECT in the third column. The order of the entries in the command list implies the order of the members in a structure. That is, the command list does not explicitly name the members of a structure.

When assigning members, MCI reserves the first member in the structure for the handle to a callback function. Thus, MCI uses the second member of the structure for data associated with the first entry needing space in the structure. MCI then assigns the third data member to the second entry needing it. This continues for the rest of the list. This association assumes that all the data members in the structure are DWORDS. You can intersperse entries that are not modified with values with those that are. For example, a command designated as an MCI_FLAG will not be associated with a member in a structure. Instead, it will designate a bit flag used in lParam1. If the MCI_FLAG entry separates two entries designated MCI_INTEGER, it would not affect the structure member assignment.

For example, the "reset" videodisc player command example would have the following form:


"reset\0",           MCI_VDISC_RESET, 0,             MCI_COMMAND_HEAD,
"notify\0",          MCI_NOTIFY,                     MCI_FLAG,
"wait\0",            MCI_WAIT,                       MCI_FLAG,
"test\0",            MCI_TEST,                       MCI_FLAG,
"program\0",         MCI_VDISC_RESET_PROGRAM,        MCI_INTEGER,
"\0",                0L,                             MCI_END_COMMAND

If you specify "\0" in the first column, it designates that entry a default entry. Any flags entered as part of a string command that does not match any entry in the command list will assume the default value. Only one default value can exist in each command list.

Using Constants

MCI uses the MCI_CONSTANT and MCI_END_CONSTANT keywords to associate a range of constants to a flag. The MCI_CONSTANT keyword reserves a DWORD in the associated structure and delimits the start of a list of constants. The following entries contain the constants. Each constant entry has the MCI_INTEGER keyword in the third column. The list of constants is ended with an entry with the MCI_END_CONSTANT keyword. For example, the set command has the following entries for the audio channel constants:


"audio\0",           MCI_SET_AUDIO,                  MCI_CONSTANT,
"all\0",             MCI_SET_AUDIO_ALL,              MCI_INTEGER,
"left\0",            MCI_SET_AUDIO_LEFT,             MCI_INTEGER,
"right\0",           MCI_SET_AUDIO_RIGHT,            MCI_INTEGER,
"\0",                0L,                             MCI_END_CONSTANT,

The constant list is actually defining a list of pre-defined integers that can be used in a string command. These pre-defined integers will be passed to your device driver in the member reserved for the flag in the structure. If the parser finds an integer following a flag rather than one of flag's pre-defined constants, the parser will assign the integer to the structure member. This lets your device driver use both constants and integers as data for a flag.

The Types of Entries

The following table summarizes the keywords used to identify the type of entry in the command table:

Keyword

Description

MCI_COMMAND_HEAD

Indicates the zero-terminated string in the first column corresponds to the verb portion of the string command. The DWORD following the command corresponds to the message command passed to DriverProc as uMsg. This entry must be the first entry in the command list and it must be unique to the table.

MCI_CONSTANT

Indicates an MCI flag validating constant data. The second column contains the flag name. The structure must reserve a DWORD to hold the integer values representing the constants. A list of constants defined for the integer data immediately follows this entry.

MCI_END_COMMAND

Indicates the end of a command list. The first and second columns for this entry must contain "\0" and 0L.

MCI_END_COMMAND_LIST

Indicates the end of the command table. The first and second columns for this entry must contain "\0" and 0L.

MCI_END_CONSTANT

Indicates the end of a list of constants. The first and second columns for this entry must contain "\0" and 0L.

MCI_FLAG

Indicates an MCI flag that does not validate data. The second column contains the flag name.

MCI_INTEGER

Indicates an MCI flag validating integer data. The second column contains the flag name. The structure must reserve a DWORD to hold the integer value.

MCI_RECT

Indicates an MCI flag validating RECT data. The second column contains the flag name. The structure must reserve two DWORDs to hold the RECT value.

MCI_RETURN

Indicates a structure associated with a command has data members reserved for return information. If used, the data entry must be the second entry in the command list. The first column of the entry contains "\0". The second column contains the type of return value. If MCI_INTEGER is specified as the return type, the second DWORD in the structure is reserved for an integer return value. If MCI_STRING is specified, the second DWORD is reserved for a pointer to the return string. The third DWORD is reserved for the length of the string. If MCI_RECT is specified, the second and third DWORDs are reserved for the RECT data.

MCI_STRING

Indicates an MCI flag validating string data. The second column contains the flag name. The structure must reserve a DWORD to hold a pointer to the string data.


How the Parser Uses the Command Tables

The following description tells how the MCI parser uses the command table entries to interpret the command string "play videodisc1 notify from 10 to 20." The MCI parser reads the string to obtain the command and its destination device. The parser uses the destination to determine the device ID and assigns it to dwDeviceID of DriverProc. The parser also uses this information to determine which command table to search.

When the parser searches the command tables for "play," it searches the command tables in this order: the device-specific table (your custom command table), then the device-type table (such as the cdaudio command table), and finally the core command table.

When searching the command table, the parser searches the tables for the MCI_COMMAND_HEAD keyword. When found, the parser compares the command listed in the first column with the command from the input string. If the two commands match, the parser uses the DWORD in the second column (in this case MCI_PLAY) as the uMsg parameter for DriverProc.

The parser tries to match each subsequent parameter in the command string with a corresponding entry in the current command list. For the example, the next parameter is "notify." If the parser does not find this, it reports an error. If the parser does find it, it looks in the third column and finds MCI_FLAG. This tells the parser to use the bitwise or operation to add the second column flag MCI_NOTIFY with the bitmember for the lParam1 parameter.

If the example command contained "wait" rather than notify, the parser would behave the same as for the "notify" example except that it would combine MCI_WAIT with the bitmember for the lParam1 parameter.

When the parser interprets the "from" parameter, the MCI_INTEGER keyword indicates that additional data is available from the command string. The parser obtains the string value, converts it to an integer, and stores it in the DWORD of the structure corresponding to the MCI_FROM flag. The parser also combines the MCI_FROM flag in the bitmember for the lParam1 parameter.

The parser processes the "to" parameter exactly as the "from" parameter except that the integer modifier is stored in DWORD of the structure corresponding to the MCI_TO flag.

The parser knows it has finished translating a command when it has reached the end of the input command string. The parser can now call DriverProc with the proper parameters.

Because the parser maps the string commands to message commands and structures passed to the driver, the order of the flags in a command string is irrelevant. The command string "play videodisc1 to 20 from 10 notify" maps exactly to the same command derived from "play videodisc1 notify from 10 to 20".

The parser checks only for syntax errors. It does not check the input string for invalid combinations of flags. Before handling any MCI command, your device driver should determine if there are invalid combinations of flags.

Providing Support for New Messages in DriverProc

When you have finished your command table, you must prepare DriverProc to handle the messages you have created. The procedures for this are the same as preparing it for existing messages.

Registering the Command Tables with MCI

The final checklist item lets the MCI parser know that you have a custom command table. Do this by making the following call in your driver's DRV_LOAD message handler:


wTableEntry = mciLoadCommandResource (hInstance, "pioneer", 0);

This example loads the resource named "pioneer," registers it with MCI, and returns a handle to that table.

If you have created a custom command table, your device driver must free the table when your device driver terminates. To free the table, include the following call in your device driver's DRV_FREE message handler:


fResult = mciFreeCommandResource (wTableEntry);

Use the table index number returned from mciLoadCommandResource as the argument to this function. This function returns TRUE if the table index number is valid.

Search Order for MCI Command Tables

MCI supports three types of tables: core, device-type, and device-specific. While parsing, the search order is from device-specific to device-type to core. When the command being searched for is found in a table, searching for that command is terminated. This means that if you have a play command in your device-specific command table, then the parser will use that one instead of the one in the device-type or core command tables.

Your command tables can reside in external files or you can bind them to your device driver. The external files are simply DLLs with only the compiled command table. A table for a device-specific command set must use the same table name used by the device driver in its mciLoadCommandResource call. They must use MCI for the extension. Avoid using the names of MCI device types as filenames. Using a device type name will prevent other devices from using the default MCI command tables.

If your device driver is a new device type and the command table it uses is type- specific (that is, it is not a custom or device-specific command table), you do not need to register the command table with mciLoadCommand Resource. When you return MCI_DEVTYPE_OTHER in the wType member of the MCI_OPEN_DRIVER_PARMS structure, MCI will automatically search for the type-specific command table. To obtain the filename, MCI searches the SYSTEM.INI file for the name of your device driver and will use this name to load your type-specific command table.

MCI gives priority to the command tables contained in external files. Thus, you can use the command tables in external files to supersede the bound command tables. For example, the command table bound with your driver could be the most commonly used table. For a foreign language version of the device driver, you could supersede the internal table with a foreign language command table by simply including it in an external file. Command tables must be in directory accessible by the Windows OpenFile function.

If you are using external files for the command tables, you might also place string resources in the file. MCI will also search for string resources in an external command files before searching for it in a bound resource.