Channels ▼


Windows CE Device Driver Development, Part II

Source Code Accompanies This Article. Download It Now.

Nov98: Windows CE Device Driver Development, Part II

Jim is director of software engineering for a consulting company that provides CE development services. He may be reached at

Sidebar: The Parallel-Port Shell Utility

When the confused disciples of a resident philosopher asked for guidance in understanding the meaning of the fish in the order of aquatic life, the philosopher simply replied, "study the fish." The frustrated disciples returned to a local pond to study the fish more closely. After a while, they began to understand the behavior of the fish, but were no closer to achieving an understanding of its true meaning. Upon returning to the philosopher and pleading for answers the philosopher replied, "The meaning of the fish matters not, only the act of learning."

Windows CE driver development with its ample documentation, is like this fish -- never quite imparting its true meaning in the pond of real-world-embedded systems development. This two-part article focuses on Windows CE device driver development. This month, I'll identify the basic skills you need to develop CE device drivers, and will implement a generic device driver for an onboard (hardwired to your device's main board) peripheral device.

Device Driver Development

Out of the box, the Microsoft Embedded Toolkit (ETK) lets you create various "Demo Builds," each of which includes progressively more features to demonstrate the modularity of the CE kernel and its complex build process. These builds may be targeted for either a PC or the ODO development platform. The PC platform is currently defined to be a Pentium class system (at this writing, Pentium II class systems are not supported), with a bidirectional parallel port for uploading code from a separate host development system, an RS-232 serial port for communication with WinDBG running on the host development system, and at least 16 MB of RAM (to support all available DEMO builds). The ODO platform, otherwise known as a Hitachi D9000, was specially commissioned by Microsoft to support development for multiple CPU targets from one platform using a removable CPU adapter card (hence the reference to the "changling" alien of the same name on Star Trek: Deep Space Nine). See http:// pmntxpr001d1.html for more information regarding this board.

If you are developing your own CE device, I recommend you spend the extra resources to create a custom development platform that includes a bidirectional parallel port, RS-232 serial port with support for data rates of 115,200, and additional RAM. If you are using the CE 2.1 ETK, the serial and parallel ports may be replaced with a network interface controller (NIC). Even if your production board includes some amount of ROM, you should plan on including enough extra RAM to account for the quantity of ROM. The extra RAM will come in handy for debug builds of the CE ROM image, which are significantly larger than the corresponding retail builds, (due to the extensive use of assertions and inline debug messages transmitted to the host). Fortunately, each module's symbol table remains on the host as a PDB file, so this, at least, will not cause an increase in the size of the build image.

The parallel port is used to upload your code changes to the target platform in the form of a new build of the CE ROM image, and the RS-232 serial port is used for remote debugging and transmission of debug messages from the CE target. Even though you could burn a new image into ROM with each build, you still need the parallel port to run the CE remote command-line tool (PPSH.EXE). The NIC can be used as a faster alternative for code uploads, and is demonstrated in the ODO platform with a sample NE2000 driver. With the release of the CE 2.1 ETK, debugging in addition to code uploads, is supported over the NIC, allowing devices that lack parallel ports or RS-232 serial ports to also be used for development.

Access to Physical Resources

No driver would be complete without accessing the physical resources of your device in some way. Because CE implements memory protection, this can only be accomplished if you communicate your intentions to the CE kernel. To this end, CE provides a limited implementation of Windows Driver Model (WDM) services for access to CPU registers, I/O ports, and memory-mapped I/O regions. If your device can tolerate the associated overhead, this kind of hardware abstraction promises to make your drivers both platform and CPU independent. In the ETK 2.1 Preview Release, these functions are defined in the CEDDK.H file located in the PUBLIC\ COMMON\OAK\DRIVERS directory. For drivers implemented using the NDIS driver model, NDIS-specific functions are available for access to physical resources and should be used in place of those mentioned here.

For example, if you require direct access to a region of physical memory, you can use the MmMapIoSpace WDM memory-management function to return a nonpaged virtual address, mapped to the physical memory region specified in the parameter list. Listing One (at the end of this article) demonstrates the use of this function on a CEPC platform. Note how the physical address must be ORed with the ROM offset defined in the CONFIG.BIB file for your platform.

Processing Interrupts

In all likelihood, your CE device will have some way of generating interrupts that you will need to process in your device driver. CE processes all incoming interrupts in its Exception Handler, which ultimately results in the activation of an Interrupt Service Thread (IST) that resides in your driver. Along the way to your IST, the interrupt must pass through a few other modules; see Figure 1.

The Exception Handler disables all interrupts until the Interrupt Service Routine (ISR) assigned to this interrupt has returned. This is an obvious bottleneck in system performance, so it's a good idea to limit the amount of work done in the ISR to the identification of the logical interrupt ID (discussed further in the next section). When the ISR returns, all interrupts, except the one just received, are reenabled and the IST is activated. This one interrupt remains disabled until the IST returns, assuring that your driver will never be required to process nested interrupts.

Setting up the Logical Interrupt ID

CE provides a mechanism, known as the "Logical Interrupt ID," to expose the interrupt of your onboard peripheral to your driver's IST. The IST is then directly associated with the physical interrupt line in a platform-independent manner. The Logical Interrupt ID could also be used for the association of multiple logical interrupts with a single physical interrupt line, thus allowing different ISTs to be associated with different functions on a single device; see Figure 2. Support for the latter would require that a custom ISR be developed for the physical interrupt.

The first step in setting up a Logical Interrupt ID requires a small modification to the file OALINTR.H (introduced in the CE 2.1 Preview Release available in the $(_TARGETPLATOOT)\INC directory of the ETK source tree). For the CE 2.0 ETK, the same change would be made in the file NKINTR.H (available in the $(_PUBLICROOT)\COMMON\OAK\INC directory). In Listing Two, I have created a Logical Interrupt ID called SYSINTR_ MYDEVICE_ FUNCTION1 to demonstrate these modifications. From this point forward, whenever you see a dollar sign in the path name, substitute the value associated with the environment variable that appears in the parentheses.

Now that you have created the constant for the logical interrupt, you need to associate it with the actual physical interrupt by modifying the interrupt's ISR. In the example CEPC implementation of x86 HAL code supplied with the ETK, the file CFWPC.C (located in the PLATFORM\CEPC\KERNEL\HAL directory) contains the OEMInit() function. The SETUP_INTERRUPT_MAP macro is used in this function to update an array indexed by the physical interrupt number in the PIC device, with the value of an associated Logical Interrupt ID. Because a single ISR services all the interrupts generated by the CEPC platform, insertion of the macro is all that's required for the ISR to return your logical interrupt ID to the Interrupt Support Handler. The file PLATFORM\ CEPC\INC\PC.H must also be modified to include a constant for the physical interrupt ID; see Listing Three.

If your device requires that you process its interrupt without the latency of an IST, it might be necessary to develop a custom ISR in the HAL (also referred to as the OAL) for your particular platform. This has the advantage of reducing latency, but also impacts performance of the system because all interrupts are disabled while the ISR code is active. You may also want to develop a custom ISR to allow the physical interrupt to be associated with multiple logical interrupts that correspond to separate functions of your device (which could be supported with a separate IST, or even a separate driver). Listing Four contains an example ISR for the x86 HAL included in the ETK. The modifications shown in Listings Two and Three are also required.

The Interrupt Service Thread (IST)

An IST is a user-mode thread that is activated by the Interrupt Support Handler in the CE kernel to process an interrupt already received by an ISR in your platform's HAL. ISTs, as opposed to ISRs, have the advantage of allowing other interrupts to be serviced while the IST code is executing. Listing Five contains an example IST.

Passing Pointers to Drivers

Say that the interface to your driver specifies a structure that contains a series of pointers to various buffers that are dynamically allocated by the calling process. For the purpose of this discussion, assume that the structure is similar to Listing Six.

If your driver is a stream I/O driver, chances are the caller will be passing a pointer to MYDRIVERSDATA in the lpInBuffer parameter of the DeviceIOControl function. Through the implementation of DeviceIOControl, the CE kernel automatically maps the pointer to MYDRIVERSDATA to the calling process' address space, allowing access to any of the MYDRIVERSDATA fields. Access to the memory referenced by the pointers pBuffer1 and pBuffer2 will cause an access violation, however, since the memory is mapped to the slot 0 address space of the calling process and has not been automatically set by the CE kernel as was the pointer to the MYDRIVERSDATA structure (for more information, see "The Windows CE SDK: The Tools You Need to Program the Handheld PC," by Neil Fishman and Jeffrey Richter, Microsoft Systems Journal, April 1997). The function MapPtrToProcess is designed to address this issue.

The function MapPtrToProcess accepts two parameters: the address you wish to map, and the ID of the process that owns the address. The name of the function was confusing to me at first, since it implies that the second parameter represents the ID of the process requiring access to the address (in this case, the process ID of the called driver). But when you consider that the upper seven bits of a CE address indicate which slot owns that address, you realize the name really indicates the process slot that the address actually appears in. So, when your driver attempts to access the data at pBuffer1 or pBuffer2, the virtual-memory-management code in the CE kernel knows which process slot it comes from. For the example structure MYDRIVERSDATA, the calling process could initialize the value of pBuffer1 and pBuffer2 with Example 1(a). The call to GetCurrentProcess correctly identifies the owner of the address as the calling process.

You could also relieve the calling process of this burden by calling MapPtrToProcess in your driver using Example 1(b). The call to GetCallerProcess still identifies the owner of the address as the calling process, but from the perspective of your driver.

Passing Pointers to Driver Threads

If you plan on using a pointer (passed to your driver through one of its APIs) in your driver's child threads, the caller of the API will need to expose this memory as a memory-mapped file. You might be tempted to simply pass this pointer to a child thread in the lpParameter of the CreateThread function, or copy the pointer value into a global variable. Both alternatives cause an access-violation exception when the pointer is dereferenced. Using MapPtrToProcess will not work either, because your driver's thread will be activated by the CE kernel in the address space of the driver itself. Even though the address might have been correctly fixed up with a call to MapPtrToProcess, the process slot of the calling process will not be accessible when your driver's child thread is activated. In Listing Seven, the name of the memory-mapped file is passed to MyDriverThread in its LPVOID parameter. The file is opened and subsequently mapped with read-only options, since the data being passed to MyDriverThread is only available for inspection.

Building Your Device Driver

At some point in the development process, you will want to build your driver. I'll assume that you are using the ETK and that you will want to test your device driver by including it in the CE ROM image. The steps you must follow are:

  1. Decide where in the ETK source tree your driver's source will live. If your driver is platform- or peripheral-dependent (meaning that your driver provides a service that requires the presence of a certain feature built-in to the CPU or provided by an onboard peripheral), then your code will live under the PLATFORM directory tree.
  2. For this example, the source for a driver named MYDRIVER lives under a directory that should be referenced as $(_PLATFORMROOT)\$(_TGTPLAT)\ DRIVERS\MYDRIVER. The environment variables required for this pathname are set up using the batch file WINCE.BAT, which is generally used to start the command shell (CMD.EXE /K).
  3. Modify the DIRS file located under $(_PLATFORMROOT)\$(_TGTPLAT)\DRIVERS by adding the directory MYDRIVER to the DIRS macro. Be certain to include a backslash at the end of the line if MYDRIVER is not the last directory name assigned to DIRS.
  4. Copy the file MAKEFILE from another source directory under DRIVERS into the MYDRIVER subdirectory. You rarely need to modify this file so in most cases simply copying it is acceptable.
  5. In the MYDRIVER subdirectory, create a file called SOURCES that defines a value for various macros to be used by the build tool BUILD.EXE to control how the driver is built and where the resulting executable will appear. Listing Eight presents the SOURCES file for MYDRIVER. The TARGETNAME macro defines the root file name for the executable generated by the build process. The SOURCES macro lists the source files in the MYDRIVER directory. The INCLUDES macro provides a list of the directories to search when locating include files referenced in the MYDRIVER source files. The macro RELEASETYPE determines the destination path name for the executable. The value PLATFORM for the RELEASETYPE macro is most commonly used for drivers and causes the executable to be copied into the path $(_TARGETPLATROOT)\ TARGET\$(_CPUINDPATH). The macro TARGETLIBS refers to import or static libraries, which are generally provided as SDK components. The macro SOURCELIBS refers only to static libraries, which generally live in the source tree as source files and are combined as object modules at build time into a static library. The source library for the MDD layer of a layered device driver, for example, is a possible value for the SOURCELIB macro. The TARGETTYPE macro determines the form of the executable (.EXE, .DLL, static .LIB.). The value DYNLINK for the TARGETTYPE macro in the MYDRIVER example automatically generates both a DLL and an import library. The value for the DLLENTRY macro provides the name of the function acting as the primary entry point of the MYDRIVER DLL, and it is applicable only if the TARGETTYPE macro equals DYNLINK. Several other options exist for the SOURCES file, and I encourage you to read the "Microsoft's Windows CE EDK Appendix" for a more comprehensive list.
  6. In order to get the ROM image builder tool to include the MYDRIVER executable, the file PLATFORM.BIB located in $(_TARGETPLATROOT)\FILES must be modified. Listing Nine is an example modification of the PLATFORM.BIB file. In this example, the file mydriver.dll will be included in the ROM image only if the environment variable MYPLATFORM_NOMYDRIVER is not defined. The syntax of this notation is unique to a build tool called CEFILTER, which preprocesses all .BIB files producing a combined file of CE.BIB. Only those lines in a BIB file where the conditional evaluates to True will appear in CE.BIB. The letters "NK" that appear after the file name refer to the memory area in the ROM image where MYDRIVER will be located. The file CONFIG.BIB located in $(_TARGETPLATROOT)\FILES defines the address range of the memory area NK and all other memory areas. The next two letters determine the attributes of the MYDRIVER.DLL file in the CE file system. In this example, the file attributes are system (S) and hidden (H).
  7. The next step requires the build utility build.exe to be run in the MYDRIVER source directory. How this is accomplished will depend on the organization of the overall build for your platform. For this example, I have simply modified the batch file BLDDEMO.BAT located in $(_PROJECTOAKROOT)\MISC; see Listing Ten.
  8. The actual build is now ready to be run. You need a command shell to run the build with all of the many environment variables properly defined. Executing the batch file WINCE.BAT with the parameters CPU type, CPU, target OS, target project, and target platform will accomplish this arduous chore. Copy one of the icons appearing in the ETK program group, and modify the parameter list as appropriate. At the command prompt, type BLDDEMO, as directed by WINCE.BAT. If everything goes well, a file containing the totality of your CE ROM image (called NK.BIN) is generated in the $(_FLATRELEASEDIR) directory. This file may be uploaded to your target platform using the PPSH tool and subsequently started by the target's boot loader.

Device Driver Registration

CE drivers require registry entries to support loading and configuration of the driver. The entries for display drivers, PC Card drivers, and stream I/O drivers all occur under the HKEY_LOCAL_MACHINE\ Drivers\BuiltIn registry key, while many layered device driver entries occur under the HKEY_LOCAL_ MACHINE\Hardware\ DeviceMap registry key. I'll focus on the registry entries required for the stream I/O driver MYDRIVER.DLL to demonstrate how to automatically load this driver at system startup. Additional registry keys are available to allow the manual loading of a stream I/O driver.


The file PLATFORM.REG located in the directory $(_PLATFORMROOT)\ $(_TGTPLAT)\ FILES contains a representation of the CE registry in an editable text file. This file (along with other files containing the extension .REG) are processed at build time to form an executable representation of the registry contained in the file DEFAULT.FDF, which is used to initialize the registry in the object store during system startup. To automatically load the stream I/O driver of MYDRIVER.DLL at system startup, the file PLATFORM.REG must be modified as in Listing Eleven.

The key dll indicates the file name of the driver to be loaded. The key Order controls the order in which all other drivers under the BuiltIn key are loaded. Drivers with lower values are loaded first. If two drivers have the same value, then they are loaded in the order of their appearance in the registry. The key Prefix is used by the caller to form a "device filename" that may be used later to open the driver with the CreateFile function and the Device Manager. A valid device filename consists of the three characters in the prefix key, a single digit index, and a colon character (MYD1: is a valid device file name for the registry entry presented in Listing Eleven). The index allows multiple callers to open the driver with the same three-character prefix, while using varying indexes. The index key is optional and indicates that the caller may not use a device filename with an index value lower than the value of this key. An index key of 1, as in Listing Eleven, will prevent access by multiple callers, because only one valid index is defined for the device filename.

Device Manager Load Sequence

When debugging your stream I/O device driver, you may notice that your debug messages are interspersed with the debug messages of other stream I/O device drivers. This occurs because the Device Manager loads each DLL from a separate thread, providing a clear performance advantage over a lengthy initialization sequence that is forced to wait for the initialization of one driver before proceeding to the next. Consequently, if you are developing multiple drivers with initialization dependencies, you must be certain to synchronize the execution of their respective initialization functions. Do not assume that if one of your drivers appears last in the registry, its initialization will not be started until after the initialization of a driver that appears first in the registry.


By now, you should have enough information to get a running start on CE device driver development. In the first part of this article, we began with a discussion of the Windows CE architecture. This provided a solid foundation for a more practical exploration of device driver development, as discussed in this month's article.


Listing One

// This constant contains a value which is defined in // $(_TARGETPLATROOT)\FILES\CONFIG.BIB as ROMOFFSET.  Physical addresses
// must be or'ed with this value or they will be considered invalid.  See
// below for more information.
#define PLATFORM_OFFSET 0x80000000

// This macro or's the 'address' with PLATFORM_OFFSET to generate
// a valid physical address.
#define ROMOFFSET(address) (address | PLATFORM_OFFSET)

#define MY_DEVICE_FRAME_BUFFER        0xB0000

// Call this function to obtain a virtual address which is bound to the
// physical address specified in the PhysicalAddress parameter.
PVOID pMappedMemory =
                 FALSE);      // Disable caching on this memory address.
ASSERT(pMappedMemory != NULL);
// Access to this physical memory is no longer required, so free up the 
// virtual address.

Back to Article

Listing Two

// Modifications to the ETK sample source file// $(_TARGETPLATOOT)\INC\OAKINTR.H (in the CE 2.1 Preview Release ETK)
#define SYSINTR_NETWORK             (SYSINTR_DEVICES+19)
#define SYSINTR_IDE                 (SYSINTR_DEVICES+20)
// Add additional logical interrupt IDs here, not to exceed SYSINTR_MAXIMUM

Back to Article

Listing Three

// Modifications to the ETK sample source file// PLATFORM\CEPC\KERNEL\HAL\CFWPC.C:
#include "nkintr.h"
#include "oalintr.h"
#include "pc.h"
// Map additional logical interrupt IDs to physical interrupt IDs and 
// remember that the physical interrupt ID constant, prefixed with INTR_, can 
// appear more than once.

// Modifications to the ETK sample source file PLATFORM\CEPC\INC\PC.H:
#define INTR_AUDIO                          11
#define INTR_MOUSE                          12
#define INTR_MYDEVICE_FUNCTION1             13

Back to Article

Listing Four

// Modifications to the ETK sample source file: // PLATFORM\CEPC\KERNEL\HAL\X86\FWPC.C. 
// This represents the most basic of ISRs for the physical interrupt 
ULONG MyDeviceFunction1ISR(void)
    // Disable the physical interrupt line to prevent reentering the IST.  
    // When IST has completed interrupt processing it will call InterruptDone 
    // which will cause the INTR_MYDEVICE_FUNCTION1 interrupt to be reenabled.
    // Perform high-priority, low-latency interrupt processing here. Leave
    // as much processing as possible for IST since all interrupts at this
    // point are disabled. If you determine that no additional processing is
    // needed you may return the reserved logical interrupt ID SYSINTR_NOP,
    // which will prevent activation of the IST.
    // Issue EOI command to the 8259A interrupt controller to let it process
    // next pending interrupt.  Send EOI to both interrupt controllers.
        mov al, 020h    ; Nonspecific EOI
        out 0A0h, al
        mov al, 020h    ; Nonspecific EOI
        out 020h, al
    // Return the logical interrupt ID to the Interrupt Support Handler to
    // allow activation of the IST for this physical interrupt.
// This function is provided by Microsoft in the sample ETK source file: 
// It is used to initialize the 8259A compatible interrupt controllers and to
// associate ALL physical interrupts with one ISR.  Note the code added
// for the custom ISR MyDeviceFunction1 ISR.
void InitPICs(void)
    for (i = 64; i < 80; i++)
        /* Setup the PeRP interrupt handler and enable the PeRP interrupt
         * in the BasePSR */
        HookInterrupt(i, (void *)PeRPISR);
    // Add ISR for the physical interrupt INTR_MYDEVICE_FUNCTION1. Be certain 
    // to remove PeRPISR ISR which was setup in the previous for loop.
    UnhookInterrupt(INTR_MYDEVICE_FUNCTION1, (void *)PeRPISR);
    HookInterrupt(INTR_MYDEVICE_FUNCTION1, (void *)MyDeviceFunction1ISR);
    // Enable interrupts from cascaded PIC
    PICEnableInterrupt(INTR_PIC2, TRUE);

Back to Article

Listing Five

// Provides an example implementation of an Interrupt Service Thread for the // logical interrupt ID SYSINTR_MYDEVICE_FUNCTION1. Assume this function is 
// activated by a call to CreateThread from the main function of MyDriver.
DWORD MyDeviceFunction1IST(LPVOID pvarg)
    HANDLE hevInterrupt;  // event signaling a received interrupt
    // Begin by elevating the priority of this thread to be 
    // certain that it preempts the execution of application threads.
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    // Create an unnamed event for use by the Interrupt Support 
    // Handler to signal this thread that an interrupt has been received.
    if ((hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
        return (ERROR_INVALID_HANDLE);
    // Inform the Interrupt Support Handler of the event handle
    // associated with the logical interrupt ID SYSINTR_MYDEVICE_FUNCTION1
    if (!InterruptInitialize(SYSINTR_MYDEVICE_FUNCTION1,hevInterrupt,NULL,0))
        return (ERROR_GEN_FAILURE);
    // Remain in this loop indefinitely, and assume that this driver will
    // always be loaded. 
    while (TRUE)
// Wait for the Interrupt Support Handler to indicate that an
        // interrupt has been received by the ISR.
        WaitForSingleObject(hevInterrupt, INFINITE);
        // We are now ready to access Function1 of MyDevice. The next few
        // lines of code, for example, might read MyDevice registers or
        // data from a predefined port address.
        // All processing related to this interrupt is complete. Inform
        // the Interrupt Support Handler so that the physical interrupt
        // associated with the logical interrupt ID will be reenabled.   
    return (ERROR_SUCCESS);

Back to Article

Listing Six

typedef struct _MYDRIVERSDATA{
    UINT uStructLength;    // length of this structure, new fields at end
    UINT uBuffer1Length;   // length of data referenced by pBuffer1
    UINT uBuffer2Length;   // length of data referenced by pBuffer2
    PVOID pBuffer1;        // points to data allocated by caller
    PVOID pBuffer2;        // points to data allocated by caller

Back to Article

Listing Seven

DWORD MyDriverThread(LPVOID pTempFileName){
    LPTSTR pszTempFileName = (LPTSTR)pTempFileName;
    ASSERT(pszTempFileName != NULL);
    // Open the existing file name provided in the pTempFileName parameter
    // of this thread.
    HANDLE hFileFlashBuffer =
        CreateFileForMapping(pszTempFileName, GENERIC_READ, FILE_SHARE_READ,
    // Create a mapping object from the file handle just created.
    HANDLE hMapFlashBuffer =
        CreateFileMapping(hFileFlashBuffer, NULL, PAGE_READONLY, 0, 0, NULL);
    // Now use this mapping object to obtain a pointer into the file, within
    // the address space of this thread. 
    LPVOID pData = MapViewOfFile(hMapFlashBuffer, FILE_MAP_READ, 0, 0, 0);
    ASSERT(pData != NULL);
    // The address in pData is now available for read only access by the 
    // by the driver's thread. Additional code accessing the data referenced
    // by pData would normally follow.
    // Shut down all of the resources allocated.
    // Close the mapping object, but do NOT close the file handle.  
    // Experience has shown that in the CE ETK 2.0 release, this handle is closed automatically when 
    // the mapping object is closed.  Any attempt to close the file handle
    // will cause an exception in CE 2.0.

    return (ERROR_SUCCESS);

Back to Article

Listing Eight

    Api.cpp \
INCLUDES=..\..\inc; \
    $(_COMMONDDKROOT)\inc; \

Back to Article

Listing Nine

    mydriver.dll     $(_FLATRELEASEDIR)\mydriver.dll               NK  SH

Back to Article

Listing Ten

BUILD -cfs
if not exist build.err goto ELSE_10
    echo !!!! Build in %_PLATFORMROOT%\%_TGTPLAT%\DRIVERS\MYDRIVER had errors.

Back to Article

Listing Eleven

;I/O Driver MYDRIVER.DLL ...
;Add custom keys for this driver here...
    "FriendlyName"="Sample Stream I/O Driver"

Back to Article

Copyright © 1998, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags


<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags


<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

