O'Reilly Book Excerpts: Programming Visual Basic for the Palm OS
Building Palm Conduits, Part 4
Until now, our focus has been on how to interface with the HotSync manager, and how to use the Sync Suite API to synchronize databases and records. It was easy to read and write the actual record data, because we only supported ASCII strings. A real Palm program is going to have a much more complicated record structure, with a mixture of string and binary data.
The sample code for this section includes a new AppForge project, Ch4b.vbp., and a new conduit project, Ch4bCond.vbp. Together, these show how to handle the low-level chores associated with packing and unpacking record data, converting numbers between the Intel and Motorola formats, and handling differences between Palm and Windows date formats.
Note that if you use the AppForge database utilities and the Universal Conduit, you don't have to worry about packing and unpacking records and fields, or converting between Palm PDA and Windows data types. We covered the database utilities in Chapter 3, and will look at the Universal Conduit in Chapter 5.
Creating Packed Record Data
Our new application creates a database that consists of structured records with four fields: a
Date value, a
Time value, a
Boolean value, an I
nteger value, a
Long integer, and a
String with 20 characters. Figure 4-9 shows the application's user interface on the Palm PDA.
First, let's look at how to create a packed record on the Palm. You'll find this code in the form module Ch4b.frm in the new application.
In This Series
Building Palm Conduits, Part 1
The AppForge documentation provides the size of each of the built-in data types: four bytes for a
Time field, one byte for a
Boolean, two bytes for an
Integer, four bytes for a
Long, and one byte for each character in a string variable.
When the user presses the
WriteDB button in the application, we use PDBCreateRecord to allocate a record large enough to hold the six fields.
Dim Count As Long
Count = 4 + 4 + 1 + 2 + 4 + 20
PDBCreateRecord hPdb, Count
We pack the fields into our record in this order:
Long, and then
String. Knowing the layout of the fields--their type, order and size--is critical when unpacking the data. AppForge lists the sizes of the basic data types in their documentation. As under Windows, both
Time values are stored in the
Date data type.
PDBWriteFieldByOffset stores the binary representation of a value into the record. Here's how to write a single
Long integer field into the Palm record:
Dim lVar As Long lVar = CLng(AFTextLong.Text) PDBWriteFieldByOffset hPdb, 5, lVar
Note that this function requires that the field offset into the record be specified. In this example, we are writing a
Long value at offset 5. The AppForge VB runtime engine already knows that the size of this variable is 4 bytes, so we don't have to pass that also.
After we have written all six fields, we call
PDBUpdateRecord to flush the new record into the Palm database.
Reading Packed Record Data
Now let's look at how to unpack the field data in the conduit. There are two main issues we address when unpacking the record data: finding the start and end of fields, and converting formats between the Palm device and Windows. You'll find the code for this section in Ch4bLogic.cls--note the `b'--in the new conduit. This conduit is structured just like the first one.
Locating the field data is just the reverse of how the record was created. First we get the record bytes into the variant array Data, using one of the
PDRecordAdapter Read iterator functions. Because the array data is a byte-wise copy of the Palm record, the offsets used for writing are identical to those used for reading.
If the data is byte- or character-oriented, such as
Boolean data, simply copy the needed bytes from the array:
Dim b As Boolean b = Data(8)
String data, you need to know the size ahead of time. In this example, we read from offset 15 until the end of the record data:
Dim s As String pdUtil.ByteArrayToBSTR Data, 15, UBound(Data) - LBound(Data) + 1 - 15, s
It is probably overkill to use the
LBound functions here. These are two of the rare VB functions that index from zero, not one, which is why there is an extra
+1 in the
ByteArrayToBSTR function call.
Numeric data, such as
Long values, must be converted from Motorola to Intel byte ordering. The
PDUtility object has methods that explicitly do this:
SwapDWORD take 16-bit and 32-bit quantities and reverse the high and low bytes as appropriate.
There is another issue when converting numbers: the data in the array is byte-oriented, while we want whole
Long values. Here's how we convert both at once in the sample conduit:
Dim i As Integer pdUtil.ByteArrayToWORD Data, 9, True, i
ByteArrayToWORD function locates the two bytes at offset 9 in the data array and converts them to an
Integer quantity, and it swaps the byte ordering as well. We request the swapping by passing
True as the third parameter; if you don't want the implicit conversion, pass
False instead. To convert a 32-bit quantity, use the
Converting Dates and Times
Dates and times are the hardest values to convert, because they are a blend of application code and operating system convention. Dates and times on the Palm are represented in the Palm operating system as seconds since January 1, 1904. How these values are stored in database files, however, varies wildly from application to application.
The Palm native applications, for example, use a packed unsigned 16-bit quantity to represent the date: 7 bits for the year since 1904, 4 bits for the month, and 5 bits for the day. These applications use an unsigned 16-bit quantity to represent the time: 8 bits for the hour, and 8 bits for the minute.
Our application stores dates and times exactly as returned by the AppForge VB runtime functions
Time. These values are the Palm operating system values: seconds since January 1, 1904. Depending on how the value is constructed, a
Date value may or may not have the day or time component:
Dim d As Date d = Date ' No time component d = Date + Time ' Has time component
Although it's not documented, AppForge
Date values are 32-bit quantities.
First we extract the 32-bit date from the packed record data using
ByteArrayToDWORD (not shown). Then we call
PalmLongToDate to convert the
Long value to a VB
Date value. The code for
PalmLongToDate is shown in Example 4-14.
Example 4-14: Listing for PalmLongToDate
Private Function PalmLongToDate(ByVal d As Long) As Date Dim SecsSince1904 As Double Dim DaysSince1904 As Double Const SecsPerDay As Long = 86400 Const UnsignedLngMax As Double = 4294967296# ' Handle signed/unsigned issues and use a double to prevent overflow. If d < 0 Then SecsSince1904 = UnsignedLngMax + d Else SecsSince1904 = d End If ' Figure out how many days have passed since 1904. Then add to ' earliest possible date. Let VB adjust for leap year, etc! DaysSince1904 = SecsSince1904 / SecsPerDay PalmLongToDate = DateSerial(1904, 1, 1) + CLng(DaysSince1904) End Function
First we convert the
Long argument, which represents seconds since January 1, 1904, into a double. We also adjust the argument if it is negative. This is necessary because the Palm data type is unsigned, so a negative number indicates that we have lost a bit! Adding the huge
UnsignedLngMax brings it back.
Next, we can calculate how many days have passed since January 1, 1904.
DaysSince1904 = SecsSince1904 / SecsPerDay
That's the hard part. Finally, we create a VB
Date with the initial magic value using the
DateSerial function, and add the correct number of days to it to obtain the converted date:
PalmLongToDate = DateSerial(1904, 1, 1) + CLng(DaysSince1904)
By using VB date arithmetic, we avoid issues such as leap year, which are better handled by the operating system and runtime libraries.
PalmLongToDateis only accurate if it is given a pure date--one that has no time component. During normal integer division, the remainder is silently discarded. But because the calculation to get
DaysSince1904is done in
Doublearithmetic, this truncation doesn't occur. This can cause the function to be inaccurate with some inputs.
Compared to getting the
Time routine is almost trivial. It is shown in Example 4-15.
Example 4-15: Listing for PalmLongToTime
Private Function PalmLongToTime(ByVal T As Long) As Date Dim Hours As Integer Dim Minutes As Integer Dim Seconds As Integer ' Strip off any vestigal seconds, handle signed/unsigned issues. If T < 0 Then T = T + &H10000 T = T And &H1FFFF ' Calculate hours, minutes and seconds based Hours = T \ 3600 Minutes = (T \ 60) Mod 60 Seconds = T Mod 60 PalmLongToTime = TimeSerial(Hours, Minutes, Seconds) End Function
Time value is stored in the lower 17 bits of the 32-bit quantity. This means that we don't have to worry about overflow while converting between signed and unsigned formats:
If T < 0 Then T = T + &H10000 T = T And &H1FFFF
We can discard any high-order bits that belong to a
Date component, which means that
PalmLongToTime may be safely called with any valid
At this point, the variable
T holds the number of seconds since midnight; this is converted to hours, minutes, and seconds. Adding these together with the VB
TimeSerial function gives us the correct time, which we return as the value of the function.
This chapter presented the fundamentals of conduit development using VB. At this point, you should understand what a conduit is and how it fits into Palm's HotSync architecture. You should be comfortable with building and running conduits under the Palm HotSync manager, and you should be able to address the issues encountered when designing a conduit.
There are many resources available from Palm to assist in the conduit development process. The Palm OS development web site has a page for conduit development at http://www.palmos.com/dev/tech/conduits. This web page has several resources, including an active mailing list, the Palm knowledge base, and links to documentation.
There are two sets of official Palm documents for the CDK. One set is based on the COM specification, and the other is based on the C/C++ language. Like all Palm documentation, the sets come in two parts: a Reference and a Companion. The companion document explains high-level concepts; the reference provides a description of every class, method, and property in the CDK. Other documentation on the web site includes presentations from the PalmSource developer conferences on conduit development.
Palm OS Programming (O'Reilly & Associates, Inc.), now in its second edition, provides a lot of detail and insight into conduit logic and the inner workings of the HotSync manager. Be aware, however, that the book is intended primarily for C/C++ developers.
4. The Palm device currently uses the Motorola 68000 series processor. This CPU represents numbers in little-Endian order, which is different from the Intel 80x86 processors. Your conduit must handle the conversion if your application stores numeric data.
5. The slow sync logic given here fails to handle this important case. If the user syncs with his desktop, then does a delete on the handheld, and then syncs with another desktop, the deleted record on the handheld is gone. Now, if the user syncs with his desktop, the conduit doesn't see the deleted record on the handheld, and so it shouldn't delete it on the desktop. Whew. You'll need to iterate through the records on the desktop. Any that aren't on the handheld (and aren't new) have been deleted, and must be deleted from the desktop (unless, of course, they've been modified on the desktop).
Roger Knoell s a software developer with 10 years experience leveraging high-level language development tools and environments.
Patrick Burton has been programming in C/C++ for most of his career. His experience inlcludes algorithm development for embedded satellite receivers, Linux system programming, and Windows programming using the Win32 API and Microsoft Found Classes (MFC).
Matthew Holmes has been developing computer software for 15 years. He cherishs his liberal arts degree in Foreign Affairs from the University of Virginia, and he holds a graduate degree in Computer Science from George Mason University.
View catalog information for Programming Visual Basic for the Palm OS
Return to .NET DevCenter.