INF: Multimedia Group System and API Design Guidelines #2

ID Number: Q67693

1.00

WINDOWS

Summary:

The Microsoft Multimedia Systems Group is doing a large amount of

system design and implementation. This is the second of two articles

to comment on the areas of system design and to share the knowledge of

our past experience.

More Information:

Registering Drivers with Module

-------------------------------

Most of the systems designed by the Multimedia group allow device

drivers to be installed by the original equipment manufacturer (OEM)

or even by the end user (given an appropriate setup program). There

are two main ways for these drivers to communicate with the main

module.

The first is to place an entry in the SYSTEM.INI file. When the parent

module loads, it loads the child driver and initiates communication

with the child.

The other method is for the child driver to call the parent to

register itself as a client. This second method presumes that there is

a suitable method available to load the child. Windows provides such a

mechanism.

Requiring a driver to register itself with the handler module provides

four benefits:

1. Drivers can be installed by adding them to the "modules to load"

list. This is much easier than creating a line for the SYSTEM.INI

file.

2. The handler module is more general because it does not assume the

presence of certain drivers. This enhances system portability and

reduces interdependencies between drivers and handlers. This

advantage also applies to drivers loaded by a parent process.

3. A driver can pass information about itself, such as its name and

entry points, to its parent during registration. This further

separates the parent module from the driver. As long as the format

of the interface data is fixed, independent changes may be made to

both parent and driver.

4. Run-time installation of drivers is possible. The inherent nature

of registration makes installing new drivers while the system is

running much easier. This also simplifies implementing

virtualization.

Symmetry of Function Names

--------------------------

- Every Open function should have a Close function and every Get

function a Put function.

- Related functions in separate areas should work the same way. For

example, if the MIDI output has an Open, the MIDI input should also

have an Open. Additionally, the return values and parameters should

be as similar as possible. This eases the programmer's task of

learning the new APIs. This applies even if the current

implementation doesn't use API symmetry. See "Designing for

Implementation in Steps," below.

Symmetry in Naming Conventions

------------------------------

- Name defined constants and types for related areas should all be

named using the same conventions. For example, LPMIDICALLBACK and

LPWAVECALLBACK.

- If a naming convention already exists for a function type, adhere

to it. Example: use SEEK and TELL functions to move within a file

system.

- If any part of an existing convention is used, little deviation

from it is allowed. For example, a combination of SEEK and GET

functions to move within a file system would not be the product of

good design because it confuses an existing convention.

- If a convention does not already exist, create a new naming

convention to avoid confusing things. Example: KNOCK and ANSWER.

Design for Implementation in Steps

----------------------------------

Most implementations of any size must be done in incremental steps of

functionality. More and more features are added to the modules until

the entire design is completely implemented. For large or complex

modules, this process may occur over several years. However, the

original design must anticipate the complete, final functionality, not

just the short-term goals. For example, even if allowing multiple

users of a module will not be implemented in the first phase, this

capability should be designed into the API. That way, the impact on

users of the module will be minimal once implementation is complete.

Avoid placing arbitrary limits on functionality due to details of the

current implementation. For example, even if only one user can have a

resource allocated today, this may not always be true. Specifically,

the Open function should return a handle to the resource that is then

passed to functions that manipulate the resource. In the future, when

multiple users of the resource is implemented, it will not be

necessary to change other functions or applications.

In a message-based system, functions should return a "message not

recognized" code for unexpected messages that is distinct from the "an

error occurred" code. Then, when a future version of the driver

contains extended functionality, an application can determine if the

installed version of the driver supports the new features. If not, the

application can take appropriate alternative action.

A project designed to be built in phases has well defined progress

milestones. This makes it much easier to track progress while the

module is under construction.

Building a module in phases also makes it easier to verify that the

module is built correctly. Testing receives increments of

functionality instead of the entire product toward the end of the

development cycle.

Error Reporting

---------------

An function call can fail for many reasons. It is best if the call can

return the specific cause of the error in addition to noting that the

call failed. Functions that return a handle, structure, or other data

cause particular problems because there is a limited set of values

that are always invalid.

Three approaches to error reporting are:

1. Ignore it (not recommended).

2. Provide a separate "what was that error?" call. This is more

complicated than it sounds because, in a multitasking system, there

can be multiple users of the module at the same "time." This makes

determining what was the last error for a particular application

difficult.

3. Return the handle or structure in a parameter and return the error

code as the function return. This seems to be the best option, and

is the approach used by OS/2.

Now that the error code is available, what should be done with it? To

allow for internationalization and for additional error codes, the

application should not associate the error code with a message.

Instead, provide a function in each API that returns the text message

for a specified error code. This function might be named

GetTextErrorInformation, for example.

Client-Supplied Buffers

-----------------------

It is desirable for the client application to provide all buffers that

it will access. If a system module allocates and maintains buffers,

many implementation problems can arise when a buffer is made visible

to the client application. Three advantages of client-supplied buffers

are:

1. If the system software runs at a different privilege level or on a

different CPU, or is otherwise separate from the client

application, the system software can easily access the buffer.

However, at the client's lower privilege level, or if the client

and operating system are on different CPUs, it may be extremely

difficult (if not impossible) to make a system-supplied buffer

available to the client.

2. When the application supplies the buffers, the application has

complete control over how much memory the system module uses.

3. The application is responsible for reporting an out-of-memory

error. This removes an error condition from the system call.

Additional reference words: 1.00