March 8, 2021
Hot Topics:

Adding Records to an Open Database in Windows CE

  • By Nancy Nicolaisen
  • Send Email »
  • More Articles »

Creating and Populating the CEPROPVAL Array

Whew! Let's take a look at how the AddBirthdayReminder() function adds records to the CE database. The first part of the AddBirthdayReminder () function is devoted to gathering data from the user interface controls of the dialog box. The one important part of this is where we retrieve the name of the lucky recipient of our largess from the edit control:

//Find out, from the edit control, who gets the loot
hwndWho = GetDlgItem(hDlg,IDC_WHO);
iTextLen = SendMessage( hwndWho, WM_GETTEXTLENGTH, 0, 0);

//require user input
if( !iTextLen )
   {return FALSE;}

//allocate for wide character count plus terminal null
lpwszWho = (TCHAR* )LocalAlloc(LPTR, (iTextLen + 1)
         * sizeof(WCHAR) );
//get the input string
SendMessage( hwndWho, WM_GETTEXT, (iTextLen + 1),

The code for interrogating the dialog controls is fairly self explanatory, but notice two things: First, we have found the exact length of the name string, and allocated space to hold it, plus the terminating NULL. Second, the birth date that we retrieve from the DateTime control is returned to us in a SYSTEMTIME structure, which is not among the supported CE data types.

Now, we'll skip ahead to the real work of structuring and populating the CE database record.

There are three items of data in a BirthdayReminder record: a name, a birth date, and an integer that indicates the type of gift we'll be sending. In this example, we require the input of all three for each record. Therefore, to create a record, we need to allocate enough space to store three CEPROPVAL structures, plus enough additional space to store the name string and a SYSTEMTIME structure. Here's how we do this:

//allocate an array of CEPROPVAL structures,
//plus storage space for strings
pRecord = (PCEPROPVAL)LocalAlloc( LPTR, 
                                * NUMBER_BIRTHDAY_PROPS)
                                + ((iTextLen + 1) * sizeof(WCHAR)
                                + sizeof(SysTime)));
if(!pRecord )
    {goto FAIL;}

We now have a single block of space large enough to hold the CEPROPVAL descriptor structures and all of the string and SYSTEMTIME data. The CEPROPVAL structures occupy the beginning of this data block; the name string and the SYSTEMTIME are stored in the remainder of the block. In order to write them into the proper location, we initialize a pointer to the byte immediately following the CEPROPVAL array.

//init pointer to record's string & SYSTEMTIME storage
pStringStore = (PBYTE)pRecord + (sizeof(CEPROPVAL)
                              * NUMBER_BIRTHDAY_PROPS);

Now, we begin populating the array of CEPROPVAL structures. The wLenData and wFlags fields are unused, and by convention always set to zero.

      //populate record
      //init the unused prop val members
for( i = 0; i < NUMBER_BIRTHDAY_PROPS; i++ )
       pRecord[i].wLenData = 0;
       pRecord[i].wFlags = 0;

We step through the array of property value structures by indexing the CEPRPOVAL pointer, pRecord.

//first we write the "who" string into a property
//write the prop id : ce type + index
pRecord[0].propid = MAKELONG(CEVT_LPWSTR, 0) ;
//copy the string to the data buffer at the end of the propval array
wcscpy((TCHAR*)pStringStore, lpwszWho );
//set the property union member to this ptr
pRecord[0].val.lpwstr = (LPWSTR)pStringStore;

To populate the name property, first we set its CEPROPID. The data type is CEVT_LPWSTR, "pointer to Unicode string", and its index in the array of record properties is 0. Next, we copy the data from the buffer we allocated when we got the edit control input to the data storage area allocated for this record. Finally, we set the CEVALUNION member to the offset in the record's data storage space where we copied the string. It is absolutely critical that you copy string data into the record allocation, and set the CEVALUNION member "lpwstr" to the correct offset in the record's data storage area. If you set "lpwstr" to an address outside the record allocation, (the buffer from which we copied the string, for example) when the application exits, that memory will be freed and the database will be corrupted.

Next, we'll populate the birthdate property, which is a SYSTEMTIME value. Because this type isn't directly supported by CE, we must save it as a byte string, or BLOB (Binary Large OBject).

//next we write the "when" value into a property
//write the prop id : ce type + index
pRecord[1].propid = MAKELONG(CEVT_BLOB, 1) ;

First, we synthesize its CEPROPID, by combining its type and the index of this property in the record's property array. A CEBLOB is yet another structure, typedef'ed like this:

typedef struct _CEBLOB {DWORD dwCount;
                        LPBYTE lpb;
                       } CEBLOB;

The size in bytes of the object is specified by dwCount, and the address of the first byte of the string is given by lpb. As with the name string, we must copy the actual data for this property into the data area of the record allocation.

//move the data buffer pointer past the "who" string
   blobSystime.lpb = pStringStore + (iTextLen + 1) * sizeof(WCHAR);

To do this, we move the pointer past the region into which we copied the name string. Notice that we don't use LocalSize(lpwszWho) to advance the pointer, but instead, calculate the new location in the record's data storage area using the string length returned by WM_GETTEXTLENGTH. LocalSize() returns the size of the allocation that was actually made in our behalf, which may have been larger than our request. Moving the buffer pointer using LocalSize() could cause us to write beyond the end of the record allocation, corrupting the database, and possibly crashing the application as well.

//copy the system time structure to the buffer as a byte string
memcpy(blobSystime.lpb, (PBYTE)&SysTime,
                   sizeof(SysTime) );
//set the size of the object in the CEBLOB structure
blobSystime.dwCount = sizeof(SysTime);

//put the CEBLOB structure in the value union for this property.
pRecord[1].val.blob = blobSystime;

We use memcpy() to move the SYSTEMTIME structure into the record allocation as a string of bytes. (This is also a good approach for storing floating point numbers in native format.) Finally, we initialize the dwCount member of the CEBLOB structure with the size of the SYSTEMTIME structure, and set the CEVALUNION member "val.blob" to the initialized blob structure.

Saving the best for last, we initialize the final CEPROPVAL structure for this record.

//copy the "what" data to a CEPROPVAL structure
//write the prop id : ce type + index
pRecord[2].propid = MAKELONG(CEVT_I2, 2) ;

//convert the data to int
pRecord[2].val.iVal = iWhat;

Because the data for this field is a short integer, we can store the property value directly in the "val.iVal" member of the CEVALUNION. Now we have a completely initialized data structure for the record.

Page 2 of 3

This article was originally published on March 29, 2004

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date