Building DMA Drivers for the Windows CE .NET Emulator

 

Mike Hall
Microsoft Corporation

July 2002

Applies to:
    Microsoft® Windows® CE .NET 4.1 with Microsoft Platform Builder 4.1

Abstract

The Microsoft Windows CE .NET Emulator ships with a number of built-in drivers, including Display, Audio, Keyboard/Mouse, Network, and Serial. It is possible to extend the list of available drivers by using the Windows CE Emulator direct memory access (DMA) interfaces. For example, you may be building a device that has an attached GPS receiver, which on the final device would communicate using a stream driver on GPS1:. This paper details the steps needed to build desktop and Emulator code that communicates using the Emulator's DMA interface. (18 printed pages)

Contents

Introduction
The Emulator
Desktop COM Interfaces
Emulator Code
Conclusion
For More Information

Introduction

The Microsoft® Windows® CE .NET Emulator can assist developers with building a customized Windows CE .NET-based operating system image in two ways. It gives you the ability to configure, build, download and debug a customized Windows CE .NET-based operating system image without needing customized hardware. In fact, the selection of operating system components, building a customized shell, and writing applications can all take place while your hardware reference platform is being built. This reduces your development timeline. The Windows CE .NET Emulator also gives you the ability to build a customized operating system image and a custom Software Development Kit (SDK), which exposes the supported APIs for your platform. This can be shipped to your customers so that they can start writing applications for your device even before they have final hardware.

Emulated Hardware

The Windows CE .NET Emulator ships with support for a number of built in devices that you might find on a physical hardware platform. The following is a list of the drivers that are supported by the Windows CE .NET Emulator through Microsoft Platform Builder 4.0 and later:

  1. Parallel Port

    Provides a parallel port that you can map directly to an LPT port on your development workstation.

  2. Serial Port

    Provides two serial ports that you can map to communications (COM) ports on your development workstation or have the output redirected to a file.

  3. Ethernet Support

    If you enable Ethernet support, keep in mind that the Emulator supports a single Ethernet card using shared Internet Protocol (IP). You must use an Ethernet card that can serve as the Ethernet card for a physical Windows CE–based target device. The Emulator uses the Ethernet card on your development workstation and emulates a DEC 21040 Ethernet card. However, the card on the development workstation does not require a driver for Windows CE. There are two forms of networking supported by the Emulator that ships with Windows CE.NET 4.1 and later: NAT (outgoing only) connections and the Virtual Switch, which allows both incoming and outgoing connections.

  4. Display

    You can specify a size for the Emulator display that is as small as 120 by 80 pixels or as large as 1024 by 768 pixels. You can specify a color depth of 8, 16, or 32 bits per pixel.

  5. Keyboard and Mouse

    Supports a standard keyboard and a PS/2 mouse.

  6. Audio

    Provides basic support for audio, including audio input, audio output, microphone input, and line output. Full duplex audio is not supported at this time.

You may be interested in building and testing an application within the Windows CE .NET Emulator that would talk to a custom hardware device, perhaps a GPS unit. In this situation, it would be useful to build a device driver that exposes the same interfaces as the final hardware device within the Emulator and provides data to applications running within the Emulator. For example, a GPS device may provide a stream of latitude and longitude data or an application may use a stream driver that is exposed as "GPS1:" on the device to get at the GPS data. Using the Emulator direct memory access (DMA) driver architecture, it is possible to build the above scenario. An application running within the Emulator would not know (or need to know) whether it is talking to a real, connected device, or whether the data is being streamed from a desktop application over a DMA interface into the Emulator.

It is also possible to make use of your desktop computer's built in serial, parallel, and Ethernet connections within the Emulator. If you have a serial connect GPS unit, then you could easily talk to this using your desktop's built-in serial port from your Windows CE .NET-based Emulator application.

Using the GPS scenario outlined above, there are two or three components you need to build, depending on how you want to implement the solution:

  1. Desktop application: The desktop application is responsible for spooling captured or live GPS data into the Emulator using the Emulator's DMA interface.
  2. Windows CE .NET Emulator Driver: This is an optional component. You can build an application that talks directly to the DMA interfaces within the Emulator. However, it may be preferable to build a driver that hides the DMA interfaces from a calling application. In this case, the application code will not need to change when moved to the final device.
  3. Windows CE .NET Emulator Application: This is the application that will make use of the GPS data. It could talk directly to the DMA interface, if required. It is better to hide the DMA calls in a driver so that the application will be portable between the Emulator and the final hardware device.

The interface to the DMA transport is exposed through COM interfaces on the desktop. These can also be used to start or stop an emulation session and to enumerate Emulator sessions.

The Emulator

This section examines how you can use the Emulator on a development computer. This includes the location of Emulator files and how to start the Emulator from the command line.

The Windows CE .NET Emulator (Emulator.exe) is located by default in the following folder: C:\Program Files\Common Files\Microsoft Shared\Windows CE Tools\Platman\bin.

The following table shows the command line options that the Emulator supports. You can, for example, set the screen resolution and color depth used by the Emulator or enable support for serial or parallel desktop passthrough.

Option Parameters Default Value
/Video <pixelwidth> x <pixelheight> x <bitdepth> 480x300x16
/MemorySize <memory size in megabytes> 32
/Ethernet 'true' or 'false' false
/SerialPort1 'none' or 'com1' to 'com4' or 'file:'<filename> none
/SerialPort2 'none' or 'com1' to 'com4' or 'file:'<filename> none
/ParallelPort 'none' or 'lpt1' or 'lpt2' none
/CEImage <pathname of CE image, such as NK.BIN> NK.bin
/Skin <pathname of skin file> -
/VMID <guid> If no GUID is specified, a new GUID will be created and assigned.
/VMName <window name> Virtual CEPC
/Help -  

Microsoft eMbedded Visual C++® 4.1 ships with support for the STANDARDSDK platform. This is an Emulator image that can be used to build and test C, C++, Microsoft Foundation Classes (MFC), and Active Template Libraries (ATL) for Windows CE-based applications. The STANDARDSDK operating system image is located in the C:\Program Files\Windows CE Tools\wce410\STANDARDSDK\Emulation folder. This image is typically called NK.BIN when built from Platform Builder.

You will notice that the STANDARDSDK image file is called NK.CEM (CE Emulator). This is simply a renamed NK.BIN file.

You can launch the STANDARDSDK Emulator image from the command line, as shown below. This assumes that you are launching the Emulator image from C:\Program Files\Common Files\Microsoft Shared\Windows CE Tools\Platman\bin, the folder containing Emulator.exe.

Emulator /CEImage "C:\Program Files\Windows CE Tools\wce410\STANDARDSDK\Emulation\nk.cem" /Video 800x200x16

This command line will launch the Emulator with the following options:

  1. /CEImage "C:\Program Files\Windows CE Tools\wce410\STANDARDSDK\Emulation\nk.cem". This will load the STANDARDSDK image. The quotes are used because the command line includes spaces. This assumes that the standard SDK components have been installed to the default location. This can be used as an example of how to load an Emulator NK.BIN from any location on your development computer.
  2. /Video 800x200x16—This will load the Windows CE image at 800x200 resolution. At 16-bit color depth, 800x200 is not a typical device resolution. However, this does prove the point that you can set the resolution of the Emulator.

You may have built a Windows CE .NET-based platform that targets the Emulator, and are able to download and debug this image using Platform Builder. However, the image does not appear to load correctly when loaded using the Emulator command line, as shown earlier, or when launched from an image created using the Windows CE .NET Emulation Kit Creator. The image does not load because it is paused waiting for the Platform Builder debugger to connect using the Kernel Independent Transport Layer (KITL). The following illustration shows how to work around this issue using Platform Builder by simply selecting Platform | Settings | Build Options, disabling "Enable KITL", and rebuilding your operating system image.

Figure 1.

Desktop COM Interfaces

This section provides an overview of the COM interfaces exposed by the Emulator on your development computer. The sample code used in this paper has been built using Microsoft Visual Studio® .NET and the code samples use console-based applications with ATL support.

These are interfaces exposed by the Windows CE .NET Emulator:

  1. IVirtualMachineManager
  2. IVirtualTransport

The IVirtualMachineManager interface gives desktop applications the ability to control the Emulator. Virtual machines can be enumerated, started, stopped, reset, and saved.

The IVirtualTransport interface gives desktop applications the ability to communicate with the Windows CE operating system running inside the Emulator.

The location of VCEComInterfaces.idl, VCEComInterfaces_i.c, and VCEComInterfaces.h, which contain the definitions and globally unique identifiers (GUIDs) of the IVirtualTransport and IVirtualMachinemanager interfaces can be found in the Platform Manager SDK folder, which is installed with Windows CE .NET, and is located here:

C:\Program Files\Common Files\Microsoft Shared\Windows CE Tools\Platman\sdk\platman\include

Enumerating Emulator Sessions

It is important to understand the difference between a session, and a running Emulator. Using the IVirtualMachineManager interfaces, you can enumerate all Emulator sessions. This can include both running (currently executing code), and saved state (not executing code) sessions. You can query each session to determine the name of the session (Emulator window, caption bar text), and whether the session is currently running. Each session is uniquely identified by a GUID. The session GUID is used in subsequent calls to IVirtualTransport functions. This gives you the ability to work with multiple Emulator sessions from a single desktop application.

IVirtualMachineManager

Here is an outline of the steps used to enumerate Emulator sessions. This is achieved using the IVirtualMachineManager interface.

  1. CoInitialize—Initializes COM
  2. GetVirtualMachineCount—Returns the number of sessions
  3. EnumerateVirtualMachines—Returns an array of session GUIDs
  4. For each session GUID, calls—
    • IsVirtualMachineRunning—Determines whether a session is running (executing code)
    • GetVirtualMachineName—If you started the session using the command line (/VMID option), you could compare against the GUID you used to start the session rather than the Emulator window caption text
  5. CoUninitialize—Closes the COM library on the current thread

You would typically save the GUID for the session you are interested in working with. This will be used in subsequent calls to the IVirtualTransport interface.

The following code shows how the IVirtualMachineManager APIs may be used to enumerate and display session names.

// NOTE: you need to #include the following files - VCEComInterfaces.h
  VCEComInterfaces_i.c

boolean  bIsVMRunning;  // check to see if session is running
CComPtr < IVirtualMachineManager > piVirtualMachineManager;
ULONG  ulTotalVM;  // Count of total Sessions
GUID *  GUIDEnumVM;  // Pointer to array of session GUID's
HRESULT  hr;

LPOLESTR pszBuffer=(LPOLESTR)CoTaskMemAlloc(MAX_PATH*2);

char pszAnsiBuffer[MAX_PATH]; // used to display the Session name.

piVirtualMachineManager.CoCreateInstance(CLSID_VirtualMachineManager);

// Get a count of the number of VM Sessions
// NOTE:  GetVirtualMachineCount does not tell us the number of RUNNING VM 
  sessions,
// rather it tells us the number of sessions existing on the PC.
hr = piVirtualMachineManager->GetVirtualMachineCount ( &ulTotalVM );

if ( SUCCEEDED ( hr ) )
 {
  if ( ulTotalVM )
  {
   GUIDEnumVM = new GUID [ulTotalVM];  // create space for the GUID's
   if (!GUIDEnumVM) // check memory allocation
    return FALSE;

   // fill in the GUID array – we then walk the array getting the session
     name
   piVirtualMachineManager->EnumerateVirtualMachines ( &ulTotalVM, 
     GUIDEnumVM );

   // Traverse through all the VM sessions
   //
   for ( int i = 0; i < (int) ulTotalVM; i++ )
   {
    bIsVMRunning = FALSE;
    // Get bIsVMRunning status for this instance of the emulator
    hr = piVirtualMachineManager->IsVirtualMachineRunning( 
&GUIDEnumVM[i], &bIsVMRunning );
    if ( SUCCEEDED ( hr ) && TRUE == bIsVMRunning )
    {
     // Get the sessions caption text
     piVirtualMachineManager->GetVirtualMachineName(&GUIDEnumVM[i], 
       &pszBuffer);
     // Convert to ANSI, and output to the user.
     wcstombs(pszAnsiBuffer,pszBuffer,MAX_PATH);
     printf("Emulator Found - %s\n",pszAnsiBuffer);
     // we could store the GUID GUIDEnumVM[i] for later use.
    }
   }
   delete [] GUIDEnumVM;
   CoTaskMemFree(pszBuffer);
  }
}

IVirtualMachineManager—Functions

The following APIs are exposed from the IVirtualMachineManager interface:

HRESULT GetVirtualMachineCount( 
[out, retval] ULONG* numberOfVMs );

GetVirtualMachineCount—Gets a count of all virtual machines, whether they are running or not.

HRESULT EnumerateVirtualMachines(
    [in, out] ULONG* numberOfVMs,
    [out, size_is(*numberOfVMs)] GUID virtualMachineID[] );

EnumerateVirtualMachines—Gets an array of GUIDs of all virtual machines available, including both virtual machines that are running and those that have been saved.

HRESULT IsVirtualMachineRunning(
    [in]     GUID*   virtualMachineID,
    [out, retval] boolean*   isRunning );

IsVirtualMachineRunning—Tests if a virtual machine is running or not and returns an error, if an unknown machine ID is found. Running means the virtual machine is in a powered-up state and is executing code. The virtual machine might be booting an operating system, but it is running. It is also possible for a virtual machine to exist, but not yet be powered up. In that case, this function will return false. Use 'EnumerateVirtualMachines' to determine if a machine exists or not and if the virtual machine is running code.

HRESULT ResetVirtualMachine(
    [in] GUID* virtualMachineID,
    [in] boolean hardReset );

ResetVirtualMachine—Resets a virtual machine, either a hard or soft reset. A soft reset does not clear memory, and it does not reload the Windows CE image. The Emulator will jump to the Emulator entry point and start executing after resetting the hardware devices.

HRESULT CreateVirtualMachine( [in] LPOLESTR commandLine );

CreateVirtualMachine—Starts a new virtual machine using command line parameters. See the description of the command line options.

HRESULT ShutdownVirtualMachine(
    [in] GUID*   virtualMachineID,
    [in] boolean   saveMachine );

ShutdownVirtualMachine—Shuts down a virtual machine, optionally saving the state.

HRESULT RestoreVirtualMachine(
     [in] GUID*      virtualMachineID );

RestoreVirtualMachine—Restores a previously saved virtual machine.

HRESULT DeleteVirtualMachine(
     [in] GUID*      virtualMachineID );

DeleteVirtualMachine—Deletes a previously saved virtual machine.

HRESULT GetVirtualMachineName(
    [in] GUID*      virtualMachineID,
    [out] LPOLESTR*   virtualMachineName );

GetVirtualMachineName—Returns the name of a virtual machine's window.

HRESULT SetVirtualMachineName(
    [in] GUID*      virtualMachineID,
    [in] LPOLESTR   virtualMachineName );

SerVirtualMachineName—Sets the name of a virtual machine's window (the Emulator's caption bar).

IVirtualTransport

The IVirtualMachineManager interface is primarily used to get the GUID for a running Emulator session. Once you have the GUID, you can then make use of the IVirtualTransport interface to send data to, and receive data from, an application or driver that is running within the Emulator. You could store multiple GUIDs from the IVirtualMachineManager::EnumerateVirtualMachines( ) function, and therefore communicate with multiple running Emulator sessions.

Here is an outline of the steps used to write data to an Emulator session using the IVirtualTransport interface. This assumes that COM has been initialized, and that the IVirtualMachineManager interface has been used to obtain a GUID for a running Emulator session.

  1. Initialize( );—Initializes the interface.
  2. Create( GUID, DmaChannel, &TransportID );—Creates a TransportID used in the calls to Send and Receive.
  3. Send( TransportID, Buffer, ByteCount, Timeout );—Sends data from the desktop application to the Emulator DMA channel.
  4. Receive ( TransportID, buffer, &ByteCount, Timeout);—Receives data back from the Emulator.
  5. Delete( TransportID );
  6. TearDown( );

The calls that are interesting from the above list are Create( ), Send( ), and Receive( ).

The call to Create( ) requires the following parameters:

  1. A valid GUID, perhaps obtained from a call to IVirtualMachineManager::EnumerateVirtualMachines( )
  2. DmaChannel, valid parameter range between 1 and 7, dma channel 0 is reserved by KITL. It is possible for multiple applications to be using the same DMA channel. The DMA channel used within the desktop application should match the DMA channel used witching the Windows CE application or driver.
  3. TransportID—This is a return value from the Create( ) call. The Transport ID is used in subsequent calls to the Send( ) and Receive( ) functions.

The Send( ) function uses the following parameters:

  1. TransportID—Obtained from a call to Create( ).
  2. LPBYTE ( Buffer )—Pointer to the data buffer (maximum size 4 KB).
  3. byteCount—The number of bytes to be sent.
  4. timeout—Milliseconds before the Send function fails.

The Receive( ) function uses the following parameters:

  1. TransportID—Obtained from a call to Create( ).
  2. LPBYTE ( Buffer )—Pointer to the data buffer (maximum size 4 KB).
  3. &byteCount—Initial size of the buffer. This will reflect the number of bytes returned from the Receive( ) call. Receive is not a blocking call. The Receive function will timeout after 'timeout milliseconds' have passed. You need to check the number of bytes returned to be sure of having data from the Emulator.
  4. timeout—Milliseconds before the Receive function fails.

The transport is capable of transmitting 4 KB packets of data at a time. The transport is much like other hardware transports. It does not guarantee packet delivery or in-order delivery. Packets will generally arrive in order unless the throughput becomes very large.

Because the desktop application will not know whether the Emulator application or driver is running, it is useful to have the desktop application wait on an initial message from the Emulator application or driver before spooling data. You will see how this works in a code sample below.

The following sample assumes that you have a valid GUID from a call to IVirtualMachineManager::EnumerateVirtualMachines( ) stored in the variable m_GUIDVirtualMachineGuid, and further assumes that the desktop application will be started first. If you examine the code below, you will notice that you initially wait on an 'ACK' message from the Emulator application or driver to acknowledge that the Emulator application or driver has been started. You then write a buffer back to the Emulator 'Start' that could be the initial packet within a data stream of GPS data.

static const int DMA_CHANNEL = 6;
static const int BUFFER_SIZE = 8;

CComPtr < IVirtualTransport > m_piVirtualTransport; // interface pointer
GUID                          m_GUIDVirtualMachineGuid; // GUID for the 
  emulator session
ULONG    m_ulTransportId; // Transport ID that is assigned in 
  m_piVirtualTransport->Create( )
HRESULT    hr;     // HRESULT
char     cAck[BUFFER_SIZE]; // Ansi buffer to convert 'CE' string
BYTE     inBuffer[BUFFER_SIZE]; // Used to receive data from the emulator 
USHORT     usLenRead;  // Num bytes read from DMA interface

// use IVirtualMachineManager::EmumerateVirtualMachines( ) to populate 
  m_GUIDVirtualMachineGuid
hr = m_piVirtualTransport.CoCreateInstance( CLSID_VirtualTransport );
// initialize the IVirtualTransport Interface
hr = m_piVirtualTransport->Initialize( );
// Create returns a TransportID for the given GUID and DMA_CHANNEL (in 
  this example, DMA_CHANNEL == 6)
// the TransportID is used as the first parameter in calls to Send( ) and 
  Receive( ).
Hr = m_piVirtualTransport->Create(&m_GUIDVirtualMachineGuid,
  DMA_CHANNEL,&m_ulTransportId);

// Waiting on Emulator Application to start…
// or, receiving data from the Emulator application/driver
while ( 1 )
{
  inBuffer[0] = '\0';
  cAck[0] = '\0';
  usLenRead = BUFFER_SIZE;

  // Call Receive, passing the TransportID and Buffer, timeout is 5000ms
  hr = m_piVirtualTransport->Receive( m_ulTransportId, inBuffer, 
    &usLenRead, 5000 );

  // convert results to Ansi string (Windows CE is Unicode).  
 wcstombs(cAck, (WCHAR *) inBuffer, BUFFER_SIZE );
  // Compare with 'ACK', used in this example to initiate the sending of 
    data to the emulator
  if ( !strncmp(cAck, "ACK",3) )
    {
    // Emulator application/driver is now running – we can start sending 
      data to the emulator
    break;
  }
 Sleep( 500 );
}

// Write data to Emulator application/driver
strcpy((char*)inBuffer,"Start");
// use Send( ), passing the TransportID, buffer, number of bytes, and
  timeout
hr = m_piVirtualTransport->Send(m_ulTransportId,inBuffer,
  BUFFER_SIZE,5000);
// We could wait on additional 'ACK' messages to control data flow from
  the desktop application

IVirtualTransport—Functions

The following APIs are exposed from the IVirtualTransport interface.

HRESULT Initialize( );

Always call Initialize() to prepare the virtual transport for use.

HRESULT TearDown( );

When done with the transport, call TearDown. Among other actions, TearDown will call Delete() for any transports you created using the Create() member function.

HRESULT Create(
    [in] GUID*   virtualMachineID,
    [in] ULONG   dmaChannel,
    [out] ULONG*   transportID );

Creates a new transport channel and returns an ID used in subsequent calls to the transport.

The 'virtualMachineID' is the GUID of the virtual machine to which you want to connect. For example, if you start up a virtual machine with IvirtualMachineManager::CreateVirtualMachine(), you would pass the command line option /VMID with a GUID to identify that virtual machine. Pass the same GUID in here to connect to that specific virtual machine. You can also use a value returned by EnumerateVirtualMachines(). The connection will fail if the virtual machine is not running.

The 'dmaChannel' indicates which dmaChannel you want to use. Valid options are 0–7. Channel 0 is reserved for KITL. This is used by Platform Builder to communicate directly with the Windows CE kernel. The Emulator side implementation operates on IRQ 14 and resides in the file HALDMAKITL.C located here: C:\WINCE400\PLATFORM\EMULATOR\KERNEL\HAL.

Channels 1 through 7 are implemented on the Emulator side using the DMA driver operating on IRQ 15. This is a stream-interface driver available to applications and drivers running in the Windows CE .NET Emulator.

HRESULT Delete( [in] ULONG transportID );
Deletes a transport channel.

HRESULT Advise( [in] IVirtualTransportSink* transportSink );
Installs a callback for receiving data from the Emulator. Every time a
  packet arrives the sink will be called.

HRESULT Send(
   [in]   ULONG   transportID,
   [in, size_is(byteCount)] const BYTE* dataBuffer,
   [in]    USHORT   byteCount,
   [in]    ULONG   timeout );
Sends data across the transport into the Emulator.

HRESULT Receive(
   [in]   ULONG    transportID,
[out, size_is(*byteCount), length_is(*byteCount)]                 BYTE*
  dataBuffer,
   [in, out]   USHORT* byteCount,
   [in]   ULONG   timeout );

Polls for data received from the transport. On input, byteCount should contain the size of the buffer pointed to by dataBuffer. On output, byteCount will contain the actual number of bytes copied into the buffer. This member function can be used instead of installing a callback sink. However, it will degrade the performance of the Emulator because every time it is called, it takes cycles away from the time the Emulator has to run the virtual machine.

Emulator Code

The Emulator exposes a DMA device driver that Windows CE-based applications can use to access the DMA transport. The device driver is exposed as a stream interface driver called Dmatrans.dll. Using Dumpbin.exe, which is a command line tool that ships with Windows CE .NET, you can list the exposed functions for dmatrans. These are listed below.

Dump of file dmatrans.dll
    ordinal hint RVA      name
          1    0 000018D2 DMA_Close
          2    1 0000183D DMA_Deinit
          3    2 00001ACE DMA_IOControl
          4    3 000016DD DMA_Init
          5    4 00001853 DMA_Open
          6    5 00001ADA DMA_PowerDown
          7    6 00001AD5 DMA_PowerUp
          8    7 000019D8 DMA_Read
          9    8 00001ADF DMA_Seek
         10    9 0000191D DMA_Write

The DMA driver is exposed through the Platform Builder catalog. This feature is called DMA trans and is automatically added to a Windows CE .NET Emulator platform.

Figure 2.

Applications or drivers that use the DMA driver use the standard Microsoft Win32® file I/O functions (CreateFile, ReadFile, WriteFile, CloseHandle) to read and write data to the Windows-based desktop application. The DMA transport provides event-driven notification when data is available to be read. In order to use this, a named event must be created with a name corresponding to the proper DMA channel, for example, channel6. The event is set when data is ready to be read. A thread can be created to wait until data is ready and then consume it.

The following code shows how the DMA driver is used by an application or driver running within the Emulator to communicate with a running desktop application, and it shows both read and write operations being used. The DMA channel number and named event being used in this sample must match the channel number being used by the desktop application. Here is what you are doing in the code below:

  1. CreateFile(L"DMA6:", . . . );—Gets a file handle that can be used for subsequent read/write operations.
  2. CreateEvent( . . . ,L"channel6");—Gets a named event handle. This is used to wait for data available notification from the desktop.
  3. WriteFile(hFile,L"ACK", . . . );—Informs the desktop application that you are running by sending an 'ACK' message. This could be any string. In this case, you are sending an acknowledgement. This sample assumes that the desktop application is started first.
  4. WaitForSingleObject( . . . );—Waits for notification that data has been sent by the desktop application and is therefore available to be read.
  5. ReadFile(hFile, . . . );—Reads the data from the desktop application.
  6. CloseHandle(hFile);—You are done with the DMA transport.

static const int BUFFER_SIZE = 8;

{
  TCHAR tcBuffer[10];  // used in MessageBox, and Ansi conversion
  BYTE buffer[10];  // receive buffer for desktop data
  DWORD dwBytes;  // number of bytes 'read' from desktop application
  DWORD dwWritten;  // number of bytes 'written' to desktop application

  // DMA Interface is exposed as a Stream driver within emulation
  // use the same DMA Channel on the desktop and emulator
  HANDLE hFile=CreateFile(L"DMA6:", GENERIC_READ | GENERIC_WRITE, 0, NULL,
  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
  // check for valid File Handle – return if invalid
  if (INVALID_HANDLE_VALUE == hFile) {
    OutputDebugString(L"Failed to open DMA6:\n");
    return;
  }

  // create an event - we wait on this to get data from the desktop
  // channel must match the DMA channel number used in the CreateFile call
  HANDLE g_hDataReadyEvent = CreateEvent( NULL, FALSE, FALSE, L"channel6" 
    );

  // Send notification to Desktop application that we're running...
  // Should start the desktop application first !.
  WriteFile(hFile,L"ACK",6,&dwWritten,NULL);

  // wait for data to be available
  WaitForSingleObject( g_hDataReadyEvent, INFINITE );
  // now read the data from the desktop application...
  // note that DMA transfers are always 0x1000 bytes
  ReadFile(hFile,buffer,0x1000,&dwBytes,NULL);

  // convert the desktop string to Unicode
  mbstowcs( tcBuffer, (char*) buffer, BUFFER_SIZE );
  // and display to the user
  OutputDebugString(tcBuffer);
 
  // Close the file handle
  CloseHandle(hFile);
}

The sample code above could easily be wrapped into a driver, perhaps "GPS.DLL", which handles all read and write operations to the underlying DMA transport. A host Windows CE application would not need to know that the GPS data it is requesting is actually being spooled by a desktop Win32-based application rather than a physically connected GPS device. As shown above, it is possible to write a Windows CE-based application that talks directly to the DMA interface.

Conclusion

This paper describes the DMA interfaces exposed by the Windows CE .NET Emulator. The interfaces can be used to simulate additional hardware within the emulation environment and can be used by application developers to accelerate development of a product while hardware is being developed. The Windows CE .NET Emulator can also be configured to use a development computer's serial, parallel, and Ethernet connections, which provides a flexible development platform for Windows CE .NET-based designs.

For More Information

For the latest information about Windows CE .NET, visit https://www.microsoft.com/windows/embedded.

The online documentation and context-sensitive Help included with Windows CE .NET also provides comprehensive background information and instructions for using Windows CE .NET.

Related Reading:

Understanding COM+ (ISBN 0-7356-0666-8)

Developer's Workshop to COM and ATL 3.0 (ISBN 1-55622-704-3)

Mr. Bunny's Guide to ActiveX (ISBN 0-201-48536-2)

To access the online documentation for Windows CE .NET

  1. Start Platform Builder.
  2. Select Contents on the Help menu to view the documentation.

You can also visit the product documentation for Windows CE .NET on MSDN®.