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.
Our new application creates a database
that consists of structured records with
four fields: a Date
value, a Time
value, a Boolean
value, an
Integer
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.
The AppForge documentation provides the size of each of the built-in
data types: four bytes for a Date
or 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:
Date
, Time
,
Boolean
, Integer
,
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
Date
and Time
values are stored
in the Date
data type.
The 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.
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
String
or Boolean
data, simply
copy the needed bytes from the array:
Dim b As Boolean b = Data(8)
For 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 UBound
and 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 Integer
or Long
values, must be converted from Motorola to Intel byte
ordering. The PDUtility
object has
methods
that explicitly do this: SwapWORD
and
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 Integer
or
Long
values. Here’s how we
convert both at once in the sample conduit:
Dim i As Integer pdUtil.ByteArrayToWORD Data, 9, True, i
The 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
ByteArrayToDWORD
function.
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.[32]
Our application stores dates
and
times exactly as returned by the AppForge VB runtime functions
Date
and 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.[33]
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.
Warning
PalmLongToDate
is 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 DaysSince1904
is done in
Double
arithmetic, this truncation
doesn’t occur. This can cause the function to be
inaccurate with some inputs.
Compared to getting the Date
, 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
The 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 Date
.
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.
[32] We don’t cover the native Palm date and time formats here. The AppForge knowledge base has an article with a code sample on how to convert those formats.
[33] Additionally, this is why we convert to a double internally. The conversion to unsigned 32-bit data would cause a silent overflow and our dates would be incorrect.
Get Programming Visual Basic for the Palm OS now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.