Writing Reliable Native Code
Nat Frampton
Microsoft Embedded MVP
President, Real Time Development Corp.
January 2003
Applies to:
Microsoft® Windows® CE .NET
Microsoft® eMbedded Visual C++®
Microsoft® .NET Compact Framework
Summary: Learn about the development process and capabilities of the eMbedded Visual C++ environment by developing an MFC histogram display and real-time control application. You will also learn the various strengths of Visual C++ native code and the .NET Compact Framework.
Download the source code for the sample applications discussed in this article.
Contents
Overview
Native vs. .NET Compact Framework Development
Interrupt Architecture
Application Interrupt Handling
Application Architecture
Controller Development - DemoControl.exe
Visualization Code - DemoView.exe
Conclusion
Overview
There are a variety of development choices for the Microsoft® Windows® CE .NET Embedded developer. Native Microsoft eMbedded Visual C++® and the new .NET Compact Framework provide two distinct paths to application development. Native code developed in eMbedded Visual C++ 4.0 will remain the most appropriate environment for developing high-speed code that takes advantage of the real-time capabilities of a Windows CE .NET platform. This session explores, through the development of an MFC histogram display and real-time control application, the development process and capabilities of the eMbedded Visual C++ environment. Visual C++ native code versus .NET Compact Framework issues are explored. The control application development will leverage Microsoft Win32 thread and synchronization objects, interrupt interfacing, shared memory interface techniques, and real-time priority strategies. MFC application topics will include display development, real-time data interfacing techniques and histogram library integration. Both applications will be developed from scratch, leveraging a few libraries available in source code format.
Native vs. .NET Compact Framework Development
The selection of native versus .NET Compact Framework is a very complex issue. Application tools, platform requirements, and development expertise work together to provide the selection criteria. The following section provides high-level guidance in this choice to provide an introduction to the process
Application Development Tool Choices
There are many application development tools available to the Microsoft Windows CE .NET Embedded developer. The integration of the .NET Compact Framework into Windows CE .NET has extended these choices.
Platform Builder is the obvious choice for operating system, driver, and service development. It serves the embedded platform developer, providing a tightly integrated for OS configuration, driver development, kernel level debugging, and many other features, just to name a few. Platform Builder is not intended as an environment for large-scale application development.
Native applications are applications that have been compiled directly for a particular processor. When you select Build MyApp.exe from the Microsoft Visual C++ build menu, the compiler creates a native application. Microsoft eMbedded Visual C++ and Microsoft Visual Basic® have served as the base for all Windows CE applications development up to the introduction of the Compact Framework. Within Visual C++, there are two main choices for application development. Windows CE .NET is architected around the Microsoft Win32® APIs. The Win32 APIs can be programmed directly from native applications. The repetitive application development process resulted in the creation of a set of class libraries encapsulating most of the mundane tasks. These class libraries are called the Microsoft Foundation Class (MFC) libraries. eMbedded Visual C++ allows embedded developers to leverage the MFC libraries, providing a graphical development environment for visual applications.
With the introduction of the Compact Framework, applications can now be developed within the Visual Studio .NET environment, targeting both the desktop and Windows CE .NET Common Language Runtimes (CLRs). This allows Visual Basic .NET and C# .NET development for Windows CE .NET.
Development API Choices
The Window CE .NET developer can choose from native Win32, native MFC, or Compact Framework application development. The following tables provide a simple overview of the choices:
- Footprint—provides expected platform requirements
- Strengths and weaknesses—enumerates the basic selection choices
Table 1. Footprint
| Footprint Requirement | Windows CE .NET | Windows XP |
|---|---|---|
| Win32 | - | - |
| MFC | 280 KB | 1.25 MB |
| .NET Framework | 1.5 MB | 34 MB |
Table 2. Strengths and weaknesses
| API | Strengths | Weaknesses |
|---|---|---|
| Microsoft Win32
(C / C++) |
|
|
| MFC
(C++) |
|
|
| .NET Framework
(C# and Microsoft Visual Basic .NET) |
|
|
In the case of a high-speed control application on a resource-constrained platform, the selection of native Win32 for the controller and native MFC for the visualization are obvious choices. In many cases, the choice of development environment can be simple; in other cases, it may be very complex.
Interrupt Architecture
The following illustration provides an application view of Windows CE .NET's interrupt architecture, with a representation of the hardware, kernel, OAL, and thread interactions during an interrupt.

Figure 1. Windows CE .NET interrupts
Time increases to the right of the illustration. The bottom layer of the illustration is the state of the hardware. The next layer is the Windows CE OEM adaptation layer (OAL), which describes the board support package (BSP) responsibilities. The top layer represents the application or driver thread interactions needed to service and interrupt.
The activity starts with an interrupt represented by the line at the left section of the chart. An exception is generated, causing the kernel interrupt service routine (ISR) vector to be loaded onto the processor. The kernel ISR interacts with the hardware, disabling all equal and lower priority interrupts on all processors except for the ARM and Strong ARM architectures. The kernel then vectors to the OAL ISR that has been registered for that particular interrupt. The OAL ISR can then examine the hardware to determine whether it has caused the interrupt. This is usually done by checking an interrupt state register. If the hardware has caused the interrupt the ISR return the SYSID of that hardware.
Upon completion of the ISR, the kernel re-enables all interrupts on the processor except for the identified interrupt. The kernel then signals the event that has been associated with the SYSID value.
The Interrupt Service Thread (IST) of a driver or application is then able to run, assuming that it is the highest priority thread that is ready to run. The IST communicates with the associated device, and reads any necessary data from the device, completing its interrupt interaction. The IST then signals its completion with a call to InterruptDone( ) with the associated SYSID value.
The kernel, upon receiving the InterruptDone for the SYSID value, re-enables the designated interrupt. This is the first point at which another interrupt for this device can be received.
This is a quick look through the interrupt sequence of activities inside Windows CE .NET. The illustration represents the interactions during a single interrupt and does not show the shared interrupts feature of Windows CE .NET. For more information about this, please refer to the Interrupt Architecture in Windows CE .NET.
Application Interrupt Handling
Dealing with interrupts from either an application or driver requires a two-step process. First, the interrupt must be initialized with an associated event. Second, the IST must wait for the interrupt event in response to interrupts from the kernel.
Interrupt Initialization
The following is example code for setting up and associating an IST with a particular interrupt. The key steps in initializing an interrupt are:
- Creating an event
- Getting the System Interrupt number for your IRQ
- Creating an interrupt service thread (IST) that is suspended
- Calling InterruptInitialize to create an association of the IRQ->Event
Creating an IST that is not suspended may cause InterruptInitialize to fail because the event is already being waited on.
- Setting the thread priority to the appropriate priority
- Resuming the IST
Void SetupInterrupt( void )
{
// Create an event
//
g_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_hevInterrupt == NULL)
{
RETAILMSG(1, (TEXT("DEMO: Event creation failed!!!\r\n")));
return;
}
// Have the OAL Translate the IRQ to a system irq
//
fRetVal = KernelIoControl( IOCTL_HAL_TRANSLATE_IRQ,
&dwIrq,
sizeof( dwIrq ),
&g_dwSysInt,
sizeof( g_dwSysInt ),
NULL );
// Create a thread that waits for signaling
//
g_fRun = TRUE;
g_htIST = CreateThread(NULL, // Security
0, // No Stack Size
ThreadIST, // Interrupt Thread NULL, // No Parameters
CREATE_SUSPENDED, // Create Suspended
&dwThreadID // Thread Id
);
// Set the thread priority - arbitrarily 5
//
m_nISTPriority = 5;
if( !CeSetThreadPriority( g_htIST, m_nISTPriority ))
{
RETAILMSG(1,(TEXT("DEMO: Failed setting Thread Priority.\r\n")));
return;
}
// Initialize the interrupt
//
if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) )
{
RETAILMSG (1, (TEXT("DEMO: InterruptInitialize failed!!!\r\n")));
return;
}
// Get the thread started
//
ResumeThread( g_htIST );
}
It is important to note that the call to InterruptInitialize takes only the SYSINTR value and the event. The kernel does not know or care about the thread that will be waiting for the event. This allows for a variety of application and driver architectures. A simple main loop of an application could initialize an interrupt and then immediately wait for the event. Only a single event can be associated with an interrupt. The event can not be used in a call to WaitForMultipleObjects. We will look at a simple thread to service the interrupt. This is the standard solution for most implementations.
Application Interrupt Service Routine:
Example code for an Interrupt Service Thread (IST) is presented below. The key components to this IST interrupt handling thread are:
- Waiting for the interrupt event
- Confirming that we have a pulsed event from the OS
- Handling the interrupt in the shortest time possible
- Calling InterruptDone()
The OS will not provide another interrupt on this IRQ until InterruptDone is called.
- Waiting for the interrupt event again
DWORD WINAPI ThreadIST( LPVOID lpvParam )
{
DWORD dwStatus;
// Always chec the running flag
//
while( g_fRun )
{
dwStatus = WaitForSingleObject(g_hevInterrupt, INFINITE);
// Make sure we have the object
//
if( dwStatus == WAIT_OBJECT_0 )
{
// Handle Interrupt Here
//
g_dwInterruptCount ++;
// Finish the interrupt
//
InterruptDone( g_dwSysInt );
}
}
return 0;
}
Priorities
A key Win32 API call in the initialization code was a call to CeSetThreadPriority. This function takes two parameters. The first parameter is the thread handle, and the second value, from 0-255, describes the desired priority level. The choice of which thread priorities to use is very critical. It is important to be able to diagram your application's use of priorities to ensure proper performance. The thread priorities from 0-247, with 0 being the highest priority, are the real-time thread priorities requiring a call to CeSetThreadPriority to access them. The normal thread priorities from 248-255 are access with SetThreadPriority. The following table provides a quick guide to Windows CE .NET standard priority implementations.
Table 3. Real-time thread priorities: CeSetThreadPriority
| Priority | Component |
|---|---|
| 0-19 | Open—Real Time Above Drivers |
| 20 | Permedia Vertical Retrace |
| 21-98 | Open—Real Time Above Drivers |
| 99 | Power management Resume Thread |
| 100-108 | USB OHCI UHCI, Serial |
| 109-129 | Irsir1, NDIS, Touch |
| 130 | KITL |
| 131 | VMini |
| 132 | CxPort |
| 133-144 | Open—Device Drivers |
| 145 | PS2 Keyboard |
| 146-147 | Open—Device Drivers |
| 148 | IRComm |
| 149 | Open—Device Drivers |
| 150 | TAPI |
| 151-152 | Open—Device Drivers |
| 153-247 | Open—Real Time Below Drivers |
Table 4. Normal thread priorities: SetThreadPriority
| Priority | Component |
|---|---|
| 248 | Power Management |
| 249 | WaveDev, TVIA5000, Mouse, PnP ,Power |
| 250 | WaveAPI |
| 251 | Power Manager Battery Thread |
| 252-255 | Open |
The general first decision point is to determine if your critical thread requires any drivers. If the critical thread requires drivers to function, then placing it at a priority above the driver's priority will not create performance gains. In general, time-critical applications are places in the "Real-Time Above Drivers Category" ranging from 0-98.
Application Architecture
The DemoControl and View applications work together to provide both control and visualization of the interrupt state of our platforms hardware. An SH4-based platform from NMI will be leveraged for this demonstration. The demonstration platform contains a simple button that is attached to an interrupt. An operating system interrupt is fired on a change of state of the button. There is an interrupt received for both the up and down transition of the hardware switch. The DemoControl application, upon receiving its first interrupt, starts counting every 5 milliseconds (ms) and increments the count in one of 8 memory bins. Each time the button is pressed, the current bin index is incremented by one. When the bin index reaches 8, it is reset to 0. The histogram of the DemoView application automatically resizes to show the relative counts of each of the 8 bins. In effect, the histogram shows the amount of time spent at each bin location. The DemoView also displays the average bin count, total bin counts, and the current bin number.
The display of high-speed information is a classic real-time interfacing problem. If the real-time controller updates the bin counts in the middle of drawing, the display may show bin counts, averages, and totals that may be partially incorrect. It is critical in real-time applications to get all the data in a single snapshot, so that the individual items are all consistent. This is referred to as temporal consistency. The most common way of getting the data in a single snapshot, or temporally consistent, is to synchronize with the actual control thread to deliver this snapshot when needed.
The demonstration strategy involves leveraging two of the most commonly-used synchronization objects in Win32: named events and memory mapped files. The event provides a mechanism for one application to signal another application that it has reached a certain state. Applications can wait for these events until it is signalled. Because these events can be named, they can be referenced between applications. Memory mapped files, referenced by name, provide a way for two separate applications two view a single area of memory.
Synchronization Strategy
The following illustration outlines the synchronization strategy.

Figure 2. Synchronization strategy
The synchronization happens cyclically with the following steps:
- DemoView application sets the Copy Event to request a fresh copy of the statistics into Demo Memory Area and then waits for the Finished Event.
- The DemoControl application checks every control system to see if the Copy Event has been set. If it has been set, it goes to Step 3; otherwise, it continues cycling.
- DemoControl updates the data in the Demo Memory Area.
- DemoControl sets the Finished Event.
- DemoView is woken back up, after waiting for the Finished Event, redraws the screen, sleeps for 100 ms, and then returns to step 1.
Minimizing interactions and requirements on the control threads is of highest priority. The controller spends very little time on either checking on the state of the Copy Event or copying the data to the memory area. You might ask if you should just go ahead and copy the memory every control cycle. The problem with this is that DemoView may be in the middle of copying the data out to its local memory and then end up with an inconsistent snapshot.
Demo Control Architecture
In order to support the synchronization and controller requirements, the following DemoControl architecture will be implemented:

Figure 3. Demo control architecture
The DemoControl application uses two main threads: the IST and the main control thread.
A simple Interrupt Service Thread (IST) will respond to a push button interrupt. The IST will ensure that interrupt is for the button up state. The IST will then increment the bin index and flash the LED.
The control thread will loop check for a finished flag indicating shutdown. If the finished flag is not set, the control thread will increment the bin value for the current bin index. The control thread then calculates the current statistics. The control thread checks for the copy request event from DemoView. If it is set, it copies the data to the Demo Memory Area. The control thread then sets the finished event, sleeps for 5 ms and repeats the loop.
The interrupt service thread must be serviced as quickly as possible, and should not be held up by the Control thread, and will be placed at priority 50. The demo control thread does not depend on any drivers and will therefore be placed at priority 60.
Controller Development - DemoControl.exe
Directory Structure
It is recommended that you create the following directory structure:
\DEMOTop Directory anywhere you want
\DEMO\DemoControlPlace for DemoControl Project
\DEMO\DemoViewPlace for DemoView Project
\DEMO\LibPre Existing SharedMemory and History Libraries
\DEMO\IncPre Existing DemoMemory.h File
Application Setup
DemoControl.exe is a Win32 application. Creating a Win32 application is a two-step process. You will select a new WCE application and ask for a simple Windows CE .NET application as shown below.

Figure 4. New WCE application dialog box
To create the application, select WCE Application and then click OK.

Figure 5. WCE Application dialog box
Some libraries and memory definitions have already been created. A class that encapsulated creating a memory mapped file is contained in the SharedMemory.cpp & SharedMemory.h files. The Demo Memory Area is defined in DemoMemory.h. Add these three files to your project, which will result in the following DemControl files list.

Figure 6. DemControl files list
The libraries and include files are placed into an Include and Lib sub directory. Map these into the include directory search path in the Project Settings dialog box.

Figure 7. Libraries and include files in the project settings dialog box
In order to access the platform hardware, leverage the CEDDK library and add that to the Object Modules list in the Project Settings dialog box.

Figure 8. Adding to the Object Modules list in the Project Settings dialog box
Demo Memory Area—DemoMemory.h
The global memory structure for both applications has been defined in DemoMemory.h. The DEMO_MEMORY_STRUCTURE contains:
nButton Current Button Index from 0 -7
ulButtonCount Array of 8 Bins worth of Tick Counts
ulAverage Average Bin Value
ulTotal Total Tick Counts
ulNumInts Number of Interrupts
fFinished Terminate Controller Flag
// New File DemoMemory.h
//
// Defines
//
#define DEMO_MEMORY_NAME _T("DEMO_MEMORY")
#define DEMO_COPY_EVENT_NAME _T("DEMO_COPY")
#define DEMO_COPY_FINISHED_EVENT_NAME _T("DEMO_COPY_FINISHED")
#define DEMO_NUM_BINS 8
#define DEMO_IST_PRIORITY 50
#define DEMO_CONTROL_PRIORITY 60
// Global Memory Structure
//
typedef struct
{
int nButton;
ULONG ulButtonCount[ DEMO_NUM_BINS ];
ULONG ulAverage;
ULONG ulTotal;
ULONG ulNumInts;
BOOL fFinished;
}DEMO_MEMORY_STRUCT,*DEMO_MEMORY_PTR;
The remainder of the code in the Initialization and Control Code sections will replace the DemoControl.cpp default code generated by eMbedded Visual C++.
Initialization
// DemoControl.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "SharedMemory.h"
#include "DemoMemory.h"
#include <pkfuncs.h>
#include "celog.h"
#include "ceddk.h"
#include "Platform.h"
// Global Memory
//
CSharedMemory* g_SM;
DEMO_MEMORY_PTR g_pDM;
DEMO_MEMORY_STRUCT g_CM;
HANDLE g_hevInterrupt;
HANDLE g_htIST;
DWORD g_dwSysInt;
HANDLE g_htControl;
HANDLE g_hevCopyRequest;
HANDLE g_hevCopyFinished;
PVBYTE g_pLEDPORT;
PVBYTE g_pButtonPort;
// Defines
//
#define BUTTON_MASK 0x20000000
#define LED_ON 0x0C
#define LED_OFF 0x00
// Prototypes
//
DWORD SetupMemory ( void );
DWORD SetupInterrupt ( void );
DWORD SetupControl ( void );
DWORD LED ( BYTE ucPort, BOOL fState );
DWORD WINAPI ThreadIST ( LPVOID lpvParam );
DWORD WINAPI ThreadControl ( LPVOID lpvParam );
// MAIN
//
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
DWORD dwRet;
dwRet = SetupMemory();
if( dwRet ) return dwRet;
dwRet = SetupInterrupt();
if( dwRet ) return dwRet;
dwRet = SetupControl();
if( dwRet ) return dwRet;
// Update the local memory
//
memcpy( (PVOID)&g_CM, (PVOID)g_pDM, sizeof( DEMO_MEMORY_STRUCT ) );
// Get the Threads going
//
ResumeThread( g_htIST );
ResumeThread( g_htControl );
while( !g_pDM->fFinished )
{
Sleep( 500 );
}
return 0;
}
DWORD SetupMemory( void )
{
// Create the Shared Memory Area
//
g_SM = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
if( !g_SM ) return 1;
// Get the memory from the shared memory
//
g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
if( !g_pDM ) return 2;
// Clear the memory
//
g_pDM->fFinished = FALSE;
// Map the LED and Button registers using the CEDDK function
//
LARGE_INTEGER liAddress;
liAddress.LowPart = NET_LED_BASE;
liAddress.HighPart = 0;
g_pLEDPORT = (PVBYTE) MmMapIoSpace ( liAddress, 0x10, FALSE );
if( !g_pLEDPORT ) return 3;
liAddress.LowPart = PCTRA;
liAddress.HighPart = 0;
g_pButtonPort = (PVBYTE) MmMapIoSpace ( liAddress, 0x4, FALSE );
if( !g_pButtonPort ) return 4;
return 0;
}
DWORD SetupInterrupt( void )
g_htIST = CreateThread( NULL, // CE Security
0, // Default Size
ThreadIST, // IST
NULL, // No Parameters
CREATE_SUSPENDED, // Suspended
&dwThreadID // Thread Id
);
if( !g_htIST )return 12;
// Set the thread priority to real time
//
if( !CeSetThreadPriority( g_htIST, DEMO_IST_PRIORITY ))return 13;
// Initialize the interrupt
//
if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) )return 14;
return 0;
}
DWORD SetupControl( void )
{
DWORD dwThreadID;
// Create the events
//
g_hevCopyRequest = CreateEvent(NULL, FALSE, FALSE,
DEMO_COPY_EVENT_NAME );
if(!g_hevCopyRequest) return 20;
g_hevCopyFinished = CreateEvent(NULL, FALSE, FALSE,
DEMO_COPY_FINISHED_EVENT_NAME );
if(!g_hevCopyFinished) return 20;
// Create the High Priority Thread
//
g_htControl = CreateThread( NULL,
0,
ThreadControl,
NULL,
CREATE_SUSPENDED,
&dwThreadID );
if( !g_htControl ) return 20;
// Set the thread priority to real time
//
if( !CeSetThreadPriority( g_htControl, DEMO_CONTROL_PRIORITY )) return 21;
return 0;
}
DWORD LED( BYTE ucPort, BOOL fState )
{
if( !g_pLEDPORT )return 0;
if( fState ) WRITE_REGISTER_UCHAR( g_pLEDPORT, LED_ON | ucPort ); // On
else WRITE_REGISTER_UCHAR( g_pLEDPORT, LED_OFF | ucPort ); // Off
return 0;
}
The key steps in initializing the controller from WinMain are:
- Call SetupMemory:
- Creates a new memory mapped file of the demo memory area.
- Maps in through the CEDDK.lib the input/output (I/O) space for the LEDs and buttons.
- Call SetupInterrupt:
- An Interrupt Event and IST are created and InterruptInitialize is called to hook up the interrupt.
- Set the IST priority to 50.
- Call SetupControl:
- Create or attach to a named event for the Copy Request.
- Create or attach to a named event for the Copy Finished.
- Create the Control Thread.
- Set the control thread priority to 60.
- Copy in the global state of the Demo Memory.
- Resume both the IST and control threads.
- Wait for the fFinished flag to be set.
Control Code
Interrupt Thread
The ThreadIST follows the simple example from above and adds hardware specific code to handle the button and button index.
DWORD WINAPI ThreadIST( LPVOID lpvParam )
{
DWORD dwStatus;
BOOL fState = TRUE;
BOOL fFirstTime = TRUE;
while( !g_pDM->fFinished )
{
dwStatus = WaitForSingleObject(g_hevInterrupt, INFINITE);
// Check to see if we are finished
//
if(g_pDM->fFinished ) return 0;
// Make sure we have the object
//
if( dwStatus == WAIT_OBJECT_0 )
{
// Only look for button ups. Get get an interrupt on up and down
//
if (!( READ_REGISTER_ULONG(g_pButtonPort) & BUTTON_MASK))
{
// Increment the Button
//
g_CM.ulNumInts++;
if( !fFirstTime )
{
g_CM.nButton ++;
if( g_CM.nButton == DEMO_NUM_BINS )
g_CM.nButton = 0;
}
else
fFirstTime = FALSE;
// Store our count out the the CELOG
//
CELOGDATA( TRUE,
CELID_RAW_LONG,
&g_CM.ulNumInts, (WORD) (sizeof(DWORD)),
1, CELZONE_MISC);
// Flash the LED
LED( 0, fState );
fState = !fState;
}
// Finish the interrupt
//
InterruptDone( g_dwSysInt );
}
}
return 0;
}
The applications-specific steps to this IST are:
- Upon receiving the interrupt, the IST checks to see whether there is a Button Up.
- Increments the number of interrupts and clears the first time flag.
- Reports the number of interrupts to CELOG.
- Toggles the LED state.
- Reporting InterruptDone.
Control Thread
The main controller thread can be seen below.
DWORD WINAPI ThreadControl( LPVOID lpvParam )
{
int i;
DWORD result;
while( !g_pDM->fFinished )
{
// Increment the current Bin Bucket
//
if( g_CM.ulNumInts )
{
g_CM.ulTotal ++;
g_CM.ulButtonCount[ g_CM.nButton ] ++;
g_CM.ulAverage = g_CM.ulTotal / DEMO_NUM_BINS;
}
// Get Request to update
//
result = WaitForSingleObject( g_hevCopyRequest, 0 );
// See if signaled
//
if( result == WAIT_OBJECT_0 )
{
g_pDM->ulAverage = g_CM.ulAverage;
g_pDM->ulNumInts = g_CM.ulNumInts;
g_pDM->ulTotal = g_CM.ulTotal;
g_pDM->nButton = g_CM.nButton;
i = 0;
while( i < DEMO_NUM_BINS )
{
g_pDM->ulButtonCount[i] = g_CM.ulButtonCount[i];
i ++;
}
// Were done
//
SetEvent( g_hevCopyFinished );
}
Sleep( 5 );
}
return 0;
}
The key steps performed by the ThreadControl are:
- Checking the finished flag each cycle.
- Incrementing the bin count pointed to by the current button.
- Calculating the average.
- Checking to see if the Copy Request Event is set. If it is, the following steps are performed:
- Copies the average, number of interupts, total interrupts, and button bndex to the Demo Memory Area.
- Copies the bin counts to the Demo Memory Area.
- Sets the Copy Finished Event.
- Sleeping 5 ms and repeating the preceding steps.
Note The DemoControl application can now be built and executed. Without the DemoView, you will be able to see the LEDs toggling on the NMI Platform based on button presses.
Visualization Code - DemoView.exe
Application Setup
DemoView.exe is an MFC application. Creating an MFC application is a two-step process. Select the New WCE MFC AppWizard (exe) dialog box, and then click OK, as shown in the following illustration.

Figure 9. New project dialog box
Click OK.

Figure 10. Appwizard dialog box
Click Finish.
Some libraries and memory definitions have already been created. A class that encapsulated creating a memory mapped file is contained in the SharedMemory.cpp & .h files. The demo memory area is defined in DemoMemory.h. A histogram class library is contained in HistoryLib.cpp and .h that add these five files to your project, resulting in the following DemoView files list.

Figure 11. DemoView files list
The libraries and include files are placed into an Include and Lib sub directory. Map these into the include directory search path in the Project Settings dialog box.

Figure 12. Project Settings dialog box
Dialog Setup
Open the IDD_DEMOVIEW_DIALOG dialog box and place the following items resulting in the following illustration. The DemoView dialog box should be in the range of 330 x 190 pixels.
Table 5. Dialog setup items
| Item | Resource ID | Properties |
|---|---|---|
| Average Edit Box | IDC_AVG_EDIT | Disabled, Visible |
| Total Counts | IDC_TOTAL_EDIT | Disabled, Visible |
| Number of Interrupts | IDC_NUM_INTS_EDIT | Disabled, Visible |
| Stop Button | IDC_STOP_BUTTON | Caption—Stop |

Figure 13. DemoView dialog box
Double-click the Stop button to create an OnStopButton message in the dialog box. In the MFC ClassWizard dialog box, add the following member variables to the class CdemoViewDlg:

Figure 14. MFC ClassWizard dialog box
In the MFC ClassWizard dialog box, add WM_TIMER and WM_PAINT message handlers to the dialog box.

Figure 15. Adding WM_TIMER and WM_PAINT message handlers
Dialog Header- DemoViewDlg.h
Add the following bold definitions to the protected definitions in DemoViewDlg.h under the definitions for m_hIcon. They add member variables for the history library, the plot RECT, and the statistics.
// Implementation protected: HICON m_hIcon; CHistoryLib m_HistoryLib; CRect m_PlotRect; ULONG m_ulLastAverage; ULONG m_ulLastTotal; ULONG m_ulLastNumInts; CString m_Output;
The remainder of the code in the Dialog Initialization and Dialog Message Handlers sections will replace the DemoViewDlg.cpp default code generated by eMbedded Visual C++.
Dialog Initialization
The dialog initialization is demonstrated in the following code example:
// DemoViewDlg.cpp : implementation file
//
#include "stdafx.h"
#include "DemoView.h"
#include "DemoViewDlg.h"
#include "HistoryLib.h"
#include "DemoMemory.h"
#include "SharedMemory.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CSharedMemory* g_SM;
DEMO_MEMORY_PTR g_pDM;
HANDLE g_hevCopyRequest;
HANDLE g_hevCopyFinished;
/////////////////////////////////////////////////////////////////////////////
// CDemoViewDlg dialog
CDemoViewDlg::CDemoViewDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDemoViewDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CDemoViewDlg)
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CDemoViewDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDemoViewDlg)
DDX_Control(pDX, IDC_NUM_INTS_EDIT, m_NumIntsEdit);
DDX_Control(pDX, IDC_TOTAL_EDIT, m_TotalEdit);
DDX_Control(pDX, IDC_AVG_EDIT, m_AvgEdit);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CDemoViewDlg, CDialog)
//{{AFX_MSG_MAP(CDemoViewDlg)
ON_WM_PAINT()
ON_WM_TIMER()
ON_BN_CLICKED(IDC_STOP_BUTTON, OnStopButton)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDemoViewDlg message handlers
BOOL CDemoViewDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
CenterWindow(GetDesktopWindow()); // center to the hpc screen
// Setup the Histogram
//
GetClientRect( &m_PlotRect );
m_PlotRect = m_PlotRect;
m_PlotRect.right -= 10;
m_PlotRect.bottom -= 70;
m_HistoryLib.Init( HistoryLeft, // Type
&m_PlotRect, // Area
_T("Button-Histogram"), // Title
_T("(Button)" ), // H Title
_T("(Counts)" ), // V Title
0.0, // Left
8, // Bins
1.0 // Bin Size
);
// Setup the Edits
//
m_ulLastAverage = 0;
m_ulLastTotal = 0;
m_ulLastNumInts = 0;
// Create the Shared Memory Area
//
g_SM = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
if( !g_SM ) return FALSE;
// Get the memory from the shared memory
//
g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
if( !g_pDM ) return FALSE;
// Get the Events
//
// Create the events
//
g_hevCopyRequest = CreateEvent(NULL, FALSE, FALSE,
DEMO_COPY_EVENT_NAME );
if(!g_hevCopyRequest) return FALSE;
g_hevCopyFinished = CreateEvent(NULL, FALSE, FALSE,
DEMO_COPY_FINISHED_EVENT_NAME );
if(!g_hevCopyFinished) return FALSE;
// Get the Timer Going
//
SetTimer( 10000, 200, NULL );
return TRUE; // return TRUE unless you set the focus to a control
}
They key steps in to the Dialog Initialization are contained in the OnInitDialog Message handler:
- Calculating the plot rectangle for the histogram
- Initializing the m_HistoryLib library
- Creates a new memory mapped file of the Demo Memory Area
- Create or attach to a named event for the Copy Request
- Create or attach to a named event for the Copy Finished
- Start a Timer at 200 ms to update the display
Dialog Message Handlers
The following message handlers OnPaint, OnTimer and OnStopButton do all the work.
void CDemoViewDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
m_HistoryLib.PlotBackground( &dc );
// Do not call CDialog::OnPaint() for painting messages
}
void CDemoViewDlg::OnTimer(UINT nIDEvent)
{
static int i = 0;
// Update the display
//
CDC* dc;
dc = GetDC();
// Request the controller info and wait until its done
//
SetEvent( g_hevCopyRequest );
WaitForSingleObject( g_hevCopyFinished, 500 );
// Update the histogram
//
m_HistoryLib.SetBinData( DEMO_NUM_BINS, &g_pDM->ulButtonCount[0] );
m_HistoryLib.Update( dc );
// Update the Edits
//
if( g_pDM->ulAverage != m_ulLastAverage )
{
m_Output.Format( _T("%ld"), g_pDM->ulAverage );
m_AvgEdit.SetWindowText( m_Output );
m_ulLastAverage = g_pDM->ulAverage;
}
if( g_pDM->ulTotal != m_ulLastTotal )
{
m_Output.Format( _T("%ld"), g_pDM->ulTotal );
m_TotalEdit.SetWindowText( m_Output );
m_ulLastTotal = g_pDM->ulTotal;
}
if( g_pDM->ulNumInts != m_ulLastNumInts )
{
m_Output.Format( _T("%ld"), g_pDM->ulNumInts );
m_NumIntsEdit.SetWindowText( m_Output );
m_ulLastNumInts = g_pDM->ulNumInts;
}
// Give this dc back!!!
ReleaseDC( dc );
CDialog::OnTimer(nIDEvent);
}
void CDemoViewDlg::OnStopButton()
{
g_pDM->fFinished = TRUE;
}
The key steps to the message handlers are:
- OnPaint
- Plot the background of the histogram library.
- OnTimer
- Get the current drawing device context.
- Set the Copy Request event.
- Wait for the Copy Finished event to come from the controller and time out at 500 ms to ensure that it does not lock up.
- Set the Bin Data for the histogram library from the Demo Memory Area.
- Have the histogram library update the display.
- Update the Average, Total, and Number of Interrupts edits if they are different from the last redraw.
- Release the current drawing device context.
- Return.
Note The DemoView application can now be built and executed. To exercise the controller, press the button on the hardware platform. The histogram bins basically measure the time you spend on each button push. The histogram library will automatically resize itself if necessary.
Conclusion
There are several development tool choices for the Windows CE .NET Embedded developer. Both native applications and the .NET Compact Framework have their place in application development. Factors such as performance, footprint and portability of code all come together to help guide the decision on which one to choose. Complex applications can be rapidly developed in eMbedded Visual C++, which allows for high performance access to the Win32 features native to Windows CE .NET. Windows CE .NET provides a rich real-time environment for a variety of application architectures. eMbedded Visual C++ is a powerful tool for full-strength applications.
For More Information
eMbedded Visual C++ 4.0 Web site
Microsoft .NET Compact Framework Web site
Free Downloads:
Microsoft Windows CE .NET Emulation Edition
Microsoft .NET Compact Framework Update to Platform Builder 4.1