The source code can be downloaded: 
In Windows CE: Creating a Control Panel Applet, I wrote about creating a Control Panel Applet that displays OEM versions including the OEM Build Number, Bootloader Version and a CPLD version. In this article, I will discuss the code that makes that Applet run.
It might be good to first discuss a little about Control Panel applets and what make them unique. The following are some facts about Control Panel Applets:
·         Control Panel Applets are Dynamically Linked Libraries (DLL), but the extension is changed to CPL. This means that to build a Control Panel Applet, it must be built as a DLL.
·         Control Panel Applets expose the function CPlApplet() which the Control Panel uses to identify features of the Applet and start it up when the user wants to use the Applet.
·         To add an Applet to the Control Panel, place the Applet in the \Windows folder.  When the Control Panel runs it will then find and load the Applet.
Now let’s look at the code. To do this, I am going to break apart the source code and look at the pieces.  Also note that to keep this article reasonable, I am going to leave some of the code out. The entire source code is available to download at the end of this article. 
The following are the header files that I used:
#include <windows.h>
#include <commctrl.h>
#include <cpl.h>
#include <aygshell.h>
#include "resource.h"
I note these so that I can discuss why these files are included:
·         Windows.h is included because, well it is a Windows application
·         Commctrl.h is included because this is hardcore C/C++ code for creating and managing a dialog box. No fluffy MFC or .NET CompactFramework here. Before you ask, I don’t know if you could use the .NET CF for a Control Panel Applet, but I suspect not.
·         Cpl.h includes the definitions that we need to implement CPlApplet()
·         Aygshell.h was used by the code that I cloned and I don’t see a good reason to remove it.   Don’t worry about license, it only uses aygshell if it exists in the OS.
·         Resouce.h defines constants that define the dialog. If you don’t already know that, I recommend picking up a Windows programming book before going too far with trying to create an Applet.
Again, a Control Panel Applet must implement the function CPlApplet(). The Control Panel loads the Applet, then gathers information about the Applet by calling CPlApplet(). When the user, or an application, request that the Applet be run, CPlApplet() is called to run the Applet.
 
 
extern "C" LONG CALLBACK CPlApplet(HWND hwndCPL, UINT message, LPARAM lParam1, LPARAM lParam2)
{
    TCHAR wzBuf[MAX_PATH];
    LPTSTR wzUrl = NULL;
    NEWCPLINFO *lpNewCplInfo;
    HANDLE    hMutex;
    TCHAR s_wzMutex[] = _T("CEOEMVerPnlMutex");
    BOOL bSuccess;
 
    switch (message)
    {
Nothing too exciting about the first part of the code, but now the switch statement handles the message parameter.
CPL_INIT is called to tell the Applet that it has been loaded by the Control Panel. This is the Applets opportunity to do some global initialization. In this case, the Applet is quite simple so it doesn’t do anything in response to CPL_INIT.
    case CPL_INIT:
        return 1;
CPL_GETCOUNT is used to determine how many applets exist in this CPL file. In this case, there is only one Applet, but CPLs like cplmain contain many Applets.
    case CPL_GETCOUNT:
        return 1;
CPL_NEWINQUIRE is called to get information about each Applet in the CPL file. The information is returned in a NEWCPLINFO structure.
    case CPL_NEWINQUIRE:
        ASSERT(0 == lParam1);
        ASSERT(lParam2);
 
        lpNewCplInfo = (NEWCPLINFO *) lParam2;
        if (lpNewCplInfo)
        {
            lpNewCplInfo->dwSize = sizeof(NEWCPLINFO);
            lpNewCplInfo->dwFlags = 0;
            lpNewCplInfo->dwHelpContext = 0;
            lpNewCplInfo->lData = IDI_OEMVER;
            lpNewCplInfo->hIcon = LoadIcon(g_hModule, MAKEINTRESOURCE(IDI_OEMVER));
            LoadString(g_hModule, IDS_TITLE, lpNewCplInfo->szName, lengthof(lpNewCplInfo->szName));
            LoadString(g_hModule, IDS_INFO, lpNewCplInfo->szInfo, lengthof(lpNewCplInfo->szInfo));
            _tcscpy(lpNewCplInfo->szHelpFile, _T(""));
            return 0;
        }
        return 1; // Failure
Some of the members of NEWCPLINFO are ignored, like dwFlags, dwHelpContext and szHelpFile. The other members are:
·         dwSize – a common feature of Microsoft structures that helps identify it
·         lData – the identifier constant from resource.h for the icon that the instance of the Applet should display
·         hIcon – the icon that should be displayed in the Control Panel. Keep in mind that the Control Panel can display a large or a small icon, so you should provide both in the icon file.
·         szName – is the string that will be displayed with the icon in the Control panel
·         szInfo – is a string that will be displayed in the Details view. It may also be displayed when the mouse hovers over the icon, but I couldn’t prove or disprove that.
CPL_DBLCLK is the most important case.  CPL_DBLCLK is called to start display and run the Applet. This doesn’t return until the Applet closes.
    case CPL_DBLCLK:
        // keep one instance
        hMutex = CreateMutex(NULL, FALSE, (LPCTSTR)s_wzMutex);
        if (hMutex)
        {
            if (GetLastError() == ERROR_ALREADY_EXISTS)
            {
                HWND hWnd;
                CloseHandle(hMutex);
                LoadString(g_hModule, IDS_TITLE, wzBuf, MAX_PATH);
                if (hWnd = FindWindow(NULL, wzBuf))
                {
                    SetForegroundWindow(hWnd);
                }
                return 1;
            }
        }
       
       {
            CVersionCPLDialog *psd = new CVersionCPLDialog;
            if (psd)
            {
                bSuccess = DialogBoxParam(g_hModule, MAKEINTRESOURCE(IDD_OEMVER),
                    hwndCPL, VersionsCPLProc, (DWORD)psd);
                delete psd;
            }
        }
        CloseHandle(hMutex);
        break;
To handle CPL_DBLCLK, this does:
1.       Use a MUTEX to prevent the Applet from running multiple instances. This is, of course, optional if you want to run multiple instances.
2.       Create and display an instance of the dialog using DialogBoxParam(). DialogBoxParam() doesn’t return until the dialog is closed. There are other ways to display and run a dialog.
3.       Close the MUTEX so that the Applet can run again now that it has closed.
CPL_STOP and CPL_EXIT are very similar in that both indicate that the CPL file is about to be unloaded. That means that it is time to de-initialize anything that was initialized on CPL_INIT.
    case CPL_STOP:
    case CPL_EXIT:
    default:
        return 0;
    }
 
   return 1;
} // CPlApplet
The next function, VersionsCPLProc(), is called when the Dialog is running to handle incoming Windows messages. No magic here, we passed in a function pointer when we called DialogBoxParam().
extern "C" BOOL CALLBACK VersionsCPLProc(
    HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    CVersionCPLDialog *psd;
 
    switch (message)
    {
    case WM_INITDIALOG:
        psd = (CVersionCPLDialog *)lParam;
        SetWindowLong(hDlg, DWL_USER, (LONG)psd);
 
        psd->InitVersionCPLDialog(hDlg);
        return TRUE;
 
    case WM_COMMAND:
        psd = (CVersionCPLDialog *)GetWindowLong(hDlg, DWL_USER);
        psd-> VersionCPLMsgHandler (LOWORD(wParam));
        break;
 
    }
    return FALSE;
}
VersionCPLProc() simply handles a message to initialize and display the dialog, or handles Windows messages received while the dialog is running. To handle Windows messages, it passes the message on VersionCPLMsgHandler ().
InitVersionCPLDialog() does the dirty work of filling the edit controls with the versions, disabling the edit controls (don’t want the user to change the versions do we?) and showing the dialog to the user.
void CVersionCPLDialog::InitVersionCPLDialog(HWND hDlg)
{
    DWORD dwEnableEditServer = FALSE;
 
   InitDlgStyle(hDlg);
 
    m_hDlg = hDlg;
 
    SetDlgItemText(hDlg, IDC_OEMOSVER, TEXT("1.45.6"));
    SetDlgItemText(hDlg, IDC_CPLDVER, TEXT("5.06"));
    SetDlgItemText(hDlg, IDC_BOOTVER, TEXT("2.20"));
 
    EnableWindow(GetDlgItem(hDlg, IDC_OEMOSVER), FALSE );
    EnableWindow(GetDlgItem(hDlg, IDC_CPLDVER), FALSE );
    EnableWindow(GetDlgItem(hDlg, IDC_BOOTVER), FALSE );
 
    VersionCPLMsgHandler (IDC_OEMOSVER);
    ShowWindow(GetDlgItem(m_hDlg, IDC_SERVER_TT), TRUE);
}
You can see that for this example code I have hardcoded the versions. The implementation of code to actually retrieve the versions would be very OEM specific. My implementation might give you some valuable hints, but I consider that code to be proprietary. I will provide you with some suggestions:
·         OEM OS version – you can put this almost anywhere that fits your needs. I have seen this in the registry, but that is kind of security risk for some systems. I prefer to build the version into the kernel and expose it via KernelIoControl()
·         CPLD version – hopefully that is contained in a register on the CPLD itself. So you will need to implement a way to read it. In Windows CE 5.0 and prior you might have even put a pointer in this code and read the register. But starting with Windows CE 6.0, you will need to expose that register, or the value, via a kernel mode driver or the kernel.
·         Bootloader version – for the bootloader version you will need to have your bootloader write its version to some fixed location in your system for the OS to then read.
InitVersionCPLDialog() calls InitDlgStyle() which is included in the source code download, but I will leave the understanding of that function to you if you want to dig that deep. It does things like associate an icon with the Applet for display on the Start bar and fiddles with aygshell.
The last function is VersionCPLMsgHandler() which handles incoming Windows messages. In this case, the only messages of interest are OK and Cancel. Certainly, if you create a more complicated dialog you would need to put more code in here.
void CVersionCPLDialog:: VersionCPLMsgHandler (WORD wCmd)
{
    switch (wCmd)
    {
    case IDOK:
        EndDialog(m_hDlg, TRUE);
        break;
 
    case IDCANCEL:
        EndDialog(m_hDlg, FALSE);
        break;
    }
}
This should get you started with writing a Control Panel Applet. You may need to pull out a dusty Windows programming book to deal with the dialog controls, but at least you have a start.
The source code can be downloaded: 
Copyright © 2009 – Bruce Eitman
All Rights Reserved