Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

C Programming


C Programming

Al is a DDJ contributing editor. He can be contacted at [email protected].


I have one of those little Windows CE hand-held computers called HPCs. Mine is from the first generation of Windows CE HPCs. It is a Casiopeia A-11, which differs from the A-10 only in that it has four MB instead of two. I've been wanting to write programs to run in HPCs ever since the technology was announced a couple of years ago but there were a few obstacles -- the development tools were released only last year, and to use them to their fullest you need a Windows NT system, which I do not have.

A short time ago, I remedied the NT situation when I built a computer specifically to install the beta version of Windows 98. Windows 98 needed its own computer for two reasons. First, I don't trust my main development and writing environment to a beta operating system. Second, I needed Windows 98 installed to write a book specifically about Windows 98 and the screen shots needed to be as generic as possible without all the clutter that typically crowds my desktop.

Not having thought about a new computer for over two years, I was delighted at how Moore's Law holds up even today. A P-300 with 128 MB RAM, a six-GB hard drive, and a 24X CD-ROM was surprisingly inexpensive to build. After installing Windows 98 and a copy of Word to do the writing, I had a lot of untapped computer on my hands. Windows NT Workstation 4.0 had been sitting on the shelf gathering dust. Often I gazed at it longingly. Many of my developer friends use NT as their main development platform because of its reliability. You can't crash NT as easily as you can Windows 95. (In my experience, the first release candidate version of Windows 98 is much more reliable than Windows 95, but my nondisclosure agreement prohibits me from telling you so. Shh.) I had never installed NT because my old Turtle Beach Multisound sound card has no NT drivers, and no other sound card within my budget has acoustic piano sounds as realistic as the old turtle (nicknamed "Churchy" after the turtle in the old Pogo comic strip). One day, whilst cruising the Web, I happened upon the Sonic Foundry site (http://www.sonicfoundry.com/) and found a software bundle that includes NT drivers for Churchy.

Like a William Hailey novel, all these random coincidences converged on the day when I added NT to my new PC in a dual-boot configuration with Windows 98. There were some glitches. I now have three different file systems installed on the six-GB hard drive. The old FAT16 system is the boot drive, and both operating systems have access to it. The D: drive is FAT32, which NT does not recognize. The E: drive is NTFS, which Windows 98 does not recognize. Windows 98 very nicely does not even display the E: drive as one of its drives. Windows NT does display the D: drive, but complains if you try to use it. If I did not have Windows 98 installed, the whole six GB could be one drive under NTFS and all that confusion would be eliminated.

Only three things keep me from trashing Windows 98 and using NT forevermore as my sole OS. First, I'm not finished writing that book. Second, Windows 98 is much easier to minister to, having more civilized administrator tools. Third, Windows 98 supports multiple monitors, a feature that you can't live without after you have it. Perhaps Windows NT 5.0 will have those improvements. Only one thing keeps me from trashing Windows NT and using Windows 98 exclusively -- the Windows CE SDK does not support CE emulation and remote debugging except under NT.

Windows CE

The Windows CE SDK installs as a plug-in to Visual C++ 5.0. You develop HPC programs in the same environment that you develop other Windows programs. The CE SDK adds four items to the list of targeted configurations. There are debug and release targets for the MIPS and SH microprocessors, which are the only two for which you can buy CE-based HPCs.

Installing the CE SDK is a challenge. It needs NT's Service Pack 3 (SP3) and Handheld PC Explorer installed, which is included with the SDK and is also included on the CD-ROM that came with the HPC. Handheld PC Explorer is a program that allows the user at the PC to explore the contents of the HPC, typically through a serial port connection. It includes the processes that synchronize documents between the two machines. Naturally, I installed SP3 first to get the OS up to speed. Then, when I installed Handheld PC Explorer, it told me that I would have to reinstall SP3. This bewildering procedure had me answering a lot of message boxes about whether to install files that were older than installed files, and there was no guidance about what to do. I don't remember exactly what I did, but it all works now.

To develop a program to run on an HPC, you must eventually upload the program to the HPC. To add to the confusion, the online information refers to this procedure as "downloading." The procedure for connecting the PC to the HPC is not clear and does not always work well. The way it is supposed to work, every time you rebuild an executable targeted to run on the HPC, the procedure uploads the executable to the HPC. (There is an option that turns automatic uploading off. It is called "Always Download" on the menu. Grrr.) If you have not made a prior connection, the PC initiates the connection dialog between itself and the HPC. It takes a while for the HPC to respond. The PC gives up after 30 seconds, which is usually less time than the HPC needs. Then the HPC is left trying and retrying to establish a connection when the PC has abandoned the effort. Sometimes the HPC times out and turns itself off just when you think you are about to succeed. Sometimes this automatic connection works. Usually it does not. I finally figured out a way. By running Handheld PC Explorer first to establish an uninterrupted connection, the upload procedure always (well, almost always) works.

Debugging

There are two ways to debug a CE program and both modes require Windows NT. The first way emulates the CE environment in the PC with a fixed window on the PC desktop. You build the program for the emulator target and use Developer Studio's debugger to run and debug the program. The other way, called "remote debugging," debugs the program as it runs in the HPC.

I have not yet tried remote debugging, but I have not done anything challenging, either. Sometimes a program compiles and executes fine under emulation only to fail to compile or link when the HPC is the target. The emulator permits some function calls even though the CE SDK's API subset does not support those functions.

CE and MFC

The CE SDK supports a healthy subset of the Win32 API. (For a discussion of the differences between the regular Win32 API and the Windows CE Win32 API, see "Windows CE Win32 API Programming," by Bruce Radtke, DDJ, April 1998.) Microsoft did a credible job of fixing MFC so that it supports the subset. My first CE program, to test the development environment, was the default single-document MFC application complete with a menu, common document dialogs, a help dialog, and nothing else. I won't bother publishing that source code because Developer Studio wrote all the code. What I learned from that experience was that in order to run such a program, you must upload a DLL to the HPC to support the MFC member functions. Actually, there are two DLLs, one for debug versions of the program and one for release versions. The release version, which you would distribute with an MFC application, is only 288K, but MFC applications themselves tend to be somewhat larger than those developed with the Win32 C API. Keep in mind that CE programs are permanently resident in the HPC's memory, which is a limited resource.

Hellowin.c

I wanted to get as close to the metal as possible for my first real program (as opposed to the one that Developer Studio wrote for me). What better program to start with than the first Windows program in Charles Petzold's classic Programming Windows 95 from Microsoft Press? I won't reprint Charles's program here, because you ought to have that book and can read his hellowin.c code on page 22 of that book. Besides, it bears a copyright notice. Instead, Listing One is my version of Petzold's program, recoded to work with Windows CE, and using my coding style for indents, braces, and variable names. When you compare the two programs, you'll find several differences, which emphasize a few of the porting issues between Windows 95 and Windows CE, and which I'll discuss here.

Petzold's program calls the PlaySound function to play a WAV file, but that function is not available in the CE API. The API does support the sndPlaySound function, which, among other things, plays WAV files, so I used that function instead.

Windows CE does not recognize ASCII strings. It is a Unicode platform, so all the string literals and character pointers had to be changed. For example, the PSTR argument becomes LPTSTR in the WinMain declaration. Also, character variables become type TCHAR, which the Windows header files typedef to wchar, and string literals are qualified with the TEXT("literal") macro.

Petzold's program uses the WNDCLASSEX structure, which the CE API does not define. I substituted the WNDCLASS structure, which does not support all the data members of WNDCLASSEX. I had to remove references to the cbSize and hIconSm data members.

The CE API does not define the IDI_APPLICATION global. My program had to define its own application icon file and provide a reference to it in a resource file. Instead of IDI_APPLICATION, I used LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)). The WS_OVERLAPPEDWINDOW style that Petzold used is not supported. I substituted WS_VISIBLE, which made the calls to ShowWindow and UpdateWindow unnecessary.

There is no RegisterClassEx function for Windows CE. I substituted a call to RegisterClass.

Charles's program loads a mouse cursor into the window style. Windows CE has no mouse cursor because it has no mouse. The pointing device is a stylus. I changed the initialization of the WNDCLASS structure's hCursor data member to zero.

When you consider that the simplest of all Windows programs, the Petzold equivalent of K&R's "hello, world" program, involves as many changes as I just described to port the program to Windows CE, you can imagine the complexity of differences between the platforms. Don't let anyone tell you that porting your killer Windows 95 app to Windows CE is going to be a snap. It might cause you to snap, but that's as close as you'll get.

HPC, So What?

What good are these machines anyway? I take mine on the road if all I'm going to do is type. It's worthless for Internet access. The heat of the PCMCIA modem card would cook a turkey, so it burns your hand; batteries last a minute or two when the modem card is inserted. Typing on the tiny keyboard is a challenge and would be if you had the hands of a two-year old. The screen is unreadable in all but the brightest of lighting conditions. The speaker is inaudible except to the neighbor's dog. There are few multimedia functions. The slow microprocessors deliver painfully slow window scrolls and screen refreshes. What good are HPCs anyway?

Their charm, of course, is found in the functionality that they pack into such a small package with such a long flashlight battery life. But there are tiny Windows 95 hand-held PCs now. What good is CE? Has it been overtaken by better technology before its potential is realized? Perhaps, perhaps not. Microsoft is trying to position Windows CE as the dominant embedded application operating system. I'm working with version 1, and version 2 has been released. HP has released a color version with a better keyboard. So, what can I do with the $600 doorstop that I am stuck with? Does it have any practical application other than to give me eyestrain and carpel tunnel syndrome at a time when I ought to be reclining and enjoying the inflight entertainment and a martini?

I have a unique machine, a development environment, and programmer skills. How can a programmer combine these things to make a useful product? The answer, of course, is in your imagination. The more small embedded applications you can pack into an HPC, the fewer other devices you might have to take on the road.

Metronome: An MFC Application

For example, Judy and I often go out on the road in our RV. I take as little extra stuff as possible to conserve storage space. Usually a small musical keyboard and either my trumpet or flügelhorn go along so I can practice. I thought that perhaps the HPC might host a simple MIDI sequencer if I could interface the serial port to a MIDI sound module. To test that theory, I decided to write an application that I do need -- a metronome. In case you are unfamiliar with musical tools, a metronome is a device that ticks like a clock but at an assigned frequency, typically measured in quarter notes (beats) per minute. Composers and arrangers specify a tune's tempo in beats per minute, and conductors and musicians use metronomes to establish that tempo precisely. Some folks can do it in their head and don't need metronomes. I cannot, so I wrote the accompanying Metronome application to go along in my HPC. Figure 1 is a screen shot of the application. You set the tempo with the combination Edit box and Spin button, click the Start button, and the metronome uses sndPlaySound to sound a tiny tick sound for each tick of the metronome. I made the WAV file by making a tongue-clucking noise into a microphone and recording and editing the effect with the NT Sound Recorder applet.

Listings Two through Five are the metronome application, which I implemented as a dialog-based MFC application. Listings Two and Three, Metronome.h and Metronome.cpp, implement the derived application class and are unremarkable. Listing Four, MetronomeDlg.h, declares the CMetronomeDlg class which implements the application. Listing Five, MetronomeDlg.cpp implements its member functions. To build the software, you will also need the clicking WAV file, some bitmaps, and the resource source files, which are included when you download the software (see "Resource Center," page 3).

Because of the lack of volume available from the HPC's speaker, I added an animated graphic to the dialog to represent the metronome's pendulum to provide a visual reference to the tempo. I can certainly play the trumpet louder than the HPC can make a ticking sound (much to the delight of my temporary neighbors in an RV park, I can assure you). The animation is implemented by two bitmap files that I built from within Developer Studio.

The Start button changes to a Stop button when you click it. The StartRunning member function gets the tempo from the dialog and converts it into a millisecond interval to be sent to the SetTimer function. This is where Windows CE is not all that real time. The lack of accuracy and low-resolution granularity of SetTimer makes for a somewhat erratic metronome, good enough for establishing a tempo, but indicative of a larger problem. A MIDI sequencer needs much finer accuracy, supporting MIDI messages of 24 ticks per quarter note at up to 280 quarter notes per minute (at least, those are my minimum requirements for what I do). Consequently, for Windows CE to qualify as a serious real-time embedded operating system, it will have to do better than what my experience revealed. There is more to the API than I have used, and there is a new version, so these concerns could be unfounded. By the time you read this, I will have attended the Windows CE Developer's Conference and will have more to report.

DDJ

Listing One

//   hellowin.c#include <windows.h>
#include <Commctrl.h>
#include <Mmsystem.h>
#include "resource.h"


</p>
HINSTANCE hInst;


</p>
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{


</p>
    switch(iMsg)    {
    case WM_CREATE:
            sndPlaySound(TEXT("hellowin.wav"), SND_ASYNC);
            return 0;
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT rc;
            HDC hDC = BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rc);
            DrawText(hDC, TEXT("Hello, Windows CE"), -1, &rc,
                DT_SINGLELINE | DT_CENTER | DT_VCENTER);
            EndPaint(hWnd, &ps);
            return 0;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, iMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                        LPTSTR szCmdLine, int iCmdShow)
{
    TCHAR szAppName[] = TEXT("HelloWin");
    HWND hWnd;
    MSG msg;
    WNDCLASS wc = {
        CS_HREDRAW | CS_VREDRAW,
        WndProc,
        0,0,
        hInstance,
        LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)),
        0,
        (HBRUSH) GetStockObject(WHITE_BRUSH),
        0,
        szAppName
    };
    hInst = hInstance;
    RegisterClass(&wc);
    hWnd = CreateWindow(szAppName,TEXT("First Windows CE Application"),
                  WS_VISIBLE | WS_CAPTION,
                  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
                  0,0, hInstance, 0);
    while(GetMessage(&msg, 0, 0, 0))    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

Back to Article

Listing Two

// Metronome.h#include "resource.h"
class CMetronomeApp : public CWinApp
{
public:
    CMetronomeApp(LPCTSTR lpszAppName);
    //{{AFX_VIRTUAL(CMetronomeApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL
    //{{AFX_MSG(CMetronomeApp)
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

Back to Article

Listing Three

// Metronome.cpp#include "stdafx.h"
#include "Metronome.h"
#include "MetronomeDlg.h"


</p>
BEGIN_MESSAGE_MAP(CMetronomeApp, CWinApp)
    //{{AFX_MSG_MAP(CMetronomeApp)
    //}}AFX_MSG
END_MESSAGE_MAP()
CMetronomeApp::CMetronomeApp(LPCTSTR lpszAppName)
    : CWinApp(lpszAppName)
{
}
CMetronomeApp theApp(_T("Metronome"));
BOOL CMetronomeApp::InitInstance()
{
    CMetronomeDlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    return FALSE;
}

Back to Article

Listing Four

// MetronomeDlg.hclass CMetronomeDlg : public CDialog
{
    bool m_bRunning;
    UINT m_nTimer;
    void StartRunning();
    void StopRunning();
public:
    CMetronomeDlg();
    //{{AFX_DATA(CMetronomeDlg)
    CStatic m_Pendulum;
    CEdit   m_Tempo;
    CButton m_StartStop;
    CSpinButtonCtrl m_TempoSpin;
    int     m_nTempo;
    //}}AFX_DATA
    //{{AFX_VIRTUAL(CMetronomeDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV support
    //}}AFX_VIRTUAL
protected:
    HICON m_hIcon;
    //{{AFX_MSG(CMetronomeDlg)
    virtual BOOL OnInitDialog();
    afx_msg void OnStart();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

Back to Article

Listing Five

// MetronomeDlg.cpp#include "stdafx.h"
#include "Metronome.h"
#include "MetronomeDlg.h"


</p>
static HBITMAP hLeft;
static HBITMAP hRight;
static const int LORANGE = 60;
static const int HIRANGE = 280;
static CStatic* m_pPendulum;
static bool Ticktock = true;


</p>
CMetronomeDlg::CMetronomeDlg() : CDialog(IDD_METRONOME_DIALOG, 0)
{
   //{{AFX_DATA_INIT(CMetronomeDlg)
    m_nTempo = 120;
    //}}AFX_DATA_INIT
    m_bRunning = false;
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    hLeft = LoadBitmap(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDB_LEFT));
    hRight = LoadBitmap(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDB_RIGHT));
}
void CMetronomeDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CMetronomeDlg)
    DDX_Control(pDX, IDC_PENDULUM, m_Pendulum);
    DDX_Control(pDX, IDC_TEMPO, m_Tempo);
    DDX_Control(pDX, IDC_START, m_StartStop);
    DDX_Control(pDX, IDC_SPIN1, m_TempoSpin);
    DDX_Text(pDX, IDC_TEMPO, m_nTempo);
    //}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CMetronomeDlg, CDialog)
    //{{AFX_MSG_MAP(CMetronomeDlg)
    ON_BN_CLICKED(IDC_START, OnStart)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


</p>
BOOL CMetronomeDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    SetIcon(m_hIcon, TRUE);             // Set big icon
    SetIcon(m_hIcon, FALSE);            // Set small icon
    CenterWindow(GetDesktopWindow());   // center to the hpc screen
    m_TempoSpin.SetRange(LORANGE,HIRANGE);
    return TRUE;
}
void CMetronomeDlg::OnStart() 
{
    if (m_bRunning)
        StopRunning();
    else
        StartRunning();
}
void CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime) 
{
    sndPlaySound(TEXT("tick.wav"), SND_ASYNC);
    Ticktock ^= true;
    m_pPendulum->SetBitmap(Ticktock ? hLeft : hRight);
}
void CMetronomeDlg::StartRunning()
{
    m_pPendulum = &m_Pendulum;
    UpdateData(true);
    if (m_nTempo >= LORANGE && m_nTempo <= HIRANGE) {
        m_bRunning = true;
        m_StartStop.SetWindowText(TEXT("Stop"));
        m_nTimer = ::SetTimer(0, 0, 60000 / m_nTempo, TimerProc);
   }
    else
        MessageBox(TEXT("Out of range"), 0, MB_ICONSTOP);
}
void CMetronomeDlg::StopRunning()
{
    m_bRunning = false;
    m_StartStop.SetWindowText(TEXT("Start"));
    ::KillTimer(0, m_nTimer);
}

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<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

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.