Microsoft & .NET.NETThe Anatomy of a CE Database Record

The Anatomy of a CE Database Record content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

In this lesson, you’ll finish examining the RemoteDBScan example program, devising a strategy by which to interpret and display the retrieved records. The first step is to get a handle on exactly what a CE database record is and how it is constructed.

The CE Database Record

A CE database record is a compact and self-documenting block of variable size data, comprised of an array of CEPROPVAL structures. Here is the typedef for CEPROPVAL:

typedef struct _CEPROPVAL {
        CEPROPID propid;    //LOWORD is CE value type
                            //HIWORD is property ID
        WORD wLenData;      //unused
        WORD wFlags;        //flags: CEDB_PROPNOTFOUND returned
                            //if prop isn't found
                            //passing in CEDB_PROPDELETE causes
                            //property to be deleted from record
        CEVALUNION val;     //Union includes members for all CE
                            //database types
        } CEPROPVAL;

There are a couple of important things to note about the way CE treats its database records. First, a CE database record may be “sparse.” Put another way, a record may or may not contain a value for each property. Also, a given CE database may contain only one record type. There is no explicit support for hierarchically structured data. Having said this, it is important to note that the member of the CEPROPVAL that stores record data is a union, one of whose members is of type BLOB. This means that should you need to create hierarchical data relationships, you can implement your own mechanisms for doing so, inside the record’s data.

Interpreting CEPROPVAL Structures

Here are the steps required to interpret an array of CEPROPVAL structures to create and display a relational style “record” from the property value array:

  • Set up an iteration of the CEPROPVAL array, using the count of record properties returned by CeReadRecordProps().
  • For each CEPROPVAL, retrieve the property index by taking the LOWORD of the propid and use it to set the column index for the property.
  • Next, determine the CE value type for the data stored in the record by taking the HIWORD of the propid. Use the CE data type to select the correct member of the CEVALUNION and proper type of formatting for the data.

In CRecordListDialog::PublishRecord(), you discriminate the property ID and data type of each property. For the sake of brevity, you insert them serially in a list box rather than laying them out as rows in a separate list control.

At the top the list of each record’s set of properties, you insert the record OID, the number of properties returned for this record, and the total size of the record in bytes. These data were passed to PublishRecord() as parameters. They were reported by CeReadRecordProps() in the caller.

void CRecordListDialog::PublishRecord( CEOID oid, WORD wProps,
                                       PCEPROPVAL pRecord,
                                       DWORD dwRecSize)
    char szInsertString[124];

    sprintf(szInsertString, "%s: 0x%x", "Record CEOID", oid );
    m_RecordListCtrl.InsertString( -1, szInsertString );

    sprintf(szInsertString, "%s: %i", "Properties Returned", wProps );
    m_RecordListCtrl.InsertString( -1, szInsertString );

    sprintf(szInsertString, "%s: %i", "Record Size", dwRecSize );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    m_RecordListCtrl.InsertString( -1, "" );

Here, you set up an iteration of the array of CEPROPVAL structures. You use the HIWORD macro to recover the property ID from the propid member. The property ID is then formatted and inserted in the list box.

    WORD iPropId;
    int iPropDataType;

    for( int i = 0; i < wProps; i++ )
        iPropId = HIWORD(pRecord[i].propid );
        sprintf(szInsertString, "%s: %i", "Property Id", iPropId );
        m_RecordListCtrl.InsertString( -1, szInsertString );

        iPropDataType = LOWORD(pRecord[i].propid );

Now, you have to retrieve and format the record’s value data. The way in which you do this depends on the data’s type. Here are the type constants for the data in a CE database record, along with the name of the structure member in the CEVALUNION in which that type is stored.

Table 1: CE Database Types and Corresponding CEVALUNION Members

CE Database Value Type Constant _CEVALUNION Member Type And Name
CEVT_I2 short iVal;
CEVT_I4 long lVal;

You declare a variable to handle translation of FILETIME data outside the switch, and then use the CE value type as a switch test. Notice that with the integer and file time types, all of the record’s data is stored within the CEPROPVAL structure.

CTime tFileTime;
CString csFileTime;
switch( iPropDataType )
    //ce type
case CEVT_I2:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_I2" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%i", pRecord[i].val.iVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
case CEVT_UI2:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_UI2" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%ui", pRecord[i].val.uiVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
case CEVT_I4:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_I4" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%li", pRecord[i].val.lVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );
case CEVT_UI4:
    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_UI4" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    sprintf(szInsertString, "%uli", pRecord[i].val.ulVal );
    m_RecordListCtrl.InsertString( -1, szInsertString );

To format the FILETIME for insertion in the list box, you wrap the raw data in the tFileTime object, and use the CTime Format() member to create a printable string.

    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_FILETIME" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    tFileTime = pRecord[i].val.filetime; 
    csFileTime = tFileTime.Format("%A, %B %d, %Y" );
    m_RecordListCtrl.InsertString( -1,
                     csFileTime.GetBuffer(csFileTime.GetLength()) );

The CEVT_LPWSTR member represents a slightly different case than the previous types. A pointer to the Unicode string is stored in pRecord[i].val.lpwstr, but the actual string data is stored elsewhere. This isn’t of that much importance if you are reading records. However, if you are writing records, remember that when you allocate memory to hold record data, you must allocate a block of memory large enough for the array of CEPROPVAL structures and for the string data to which pRecord[i].val.lpwstr points. (The actual strings are stored in the memory beyond what is occupied by the CEPROPVAL array.)

    sprintf(szInsertString, "%s: %s", "Data Type", "CEVT_LPWSTR" );
    m_RecordListCtrl.InsertString( -1, szInsertString );
    wcstombs( szInsertString, pRecord[i].val.lpwstr,
    m_RecordListCtrl.InsertString( -1, szInsertString );

The CEVT_BLOB data type is useful for typical types of binary data, but also as a way to implement application-defined data types and one-to-many record relationships. The CEVALUNION member blob is actually yet another structure, CEBLOB. Here’s the typedef for CEBLOB:

typedef struct _CEBLOB {
        DWORD dwCount;    //size of the BLOB data in bytes
        LPBYTE lpb;       //address of byte stream
        } CEBLOB;

You insert the the data type, the size in bytes of the binary data, and the first byte of the datum in the list box.

        case CEVT_BLOB:
            sprintf(szInsertString, "%s: %s", "Data Type",
                    "CEVT_BLOB" );
            m_RecordListCtrl.InsertString( -1, szInsertString );
            sprintf(szInsertString, "%s: %li", "Size in bytes",
                    pRecord[i].val.blob.dwCount );
            m_RecordListCtrl.InsertString( -1, szInsertString );
            sprintf(szInsertString, "%s: 0x%x", "Buffer Address",
                    pRecord[i].val.blob.lpb );

        //blank line between properties for readability
        m_RecordListCtrl.InsertString( -1, "" );



Whew! That’s remote database access. Once you are connected to the remote database via RAPI, a cut and paste of the sample code shown in the earlier article on CE database management will take you most anywhere you want to go. The function names and parameter lists are identical for the RAPI and CE side versions of the following functions:


A final reminder: Any string parameters passed into or returned from these functions are in Unicode. The functions will fail if you forget to translate between character formats.

Looking Ahead

In the next example, you explore the most powerful RAPI capability of all: the ability to remotely invoke a function on the CE device. You’ll pair this sophisticated capability with the Windows CE HTML viewer, to create a flexible and dynamic means of communicating with the user.

About the Author

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation.

In addition to writing for, she has written several books, including Making Win 32 Applications Mobile.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories