Timothy Trimble, President
Extended Technology Systems
May 18, 1999
Summary: This article covers an introduction to the use of Microsoft ActiveX® Data Objects for Windows® CE (ADOCE) with the Windows CE Toolkit for Visual Basic® 6.0 (VBCE), the approach to designing handheld applications compared with desktop development, connectivity to and from Windows CE database applications, and real-world application of this technology. Source code examples will be included to help you understand the use of VBCE and ADOCE. This article assumes that you have some familiarity with Windows CE, desktop Visual Basic, VBCE, and the use of databases from desktop Visual Basic. (21 printed pages)
Click to copy the BenchMark sample files.
The chief benefit of coupling handheld technology with Visual Basic, the most widely used corporate programming language, is the resulting ability of developers to use the same approach to designing and using databases for both the desktop world and the emerging handheld industry. The first version of Windows CE and VBCE had limitations that made data access possible only through the use of direct file I/O or by the use of third-party and quick-to-market database controls.
However, with the latest release of the Windows CE Toolkit for Visual Basic 6.0, corporate developers who were once concerned about those limitations can now dive head-first into designing and developing corporate database applications. With the addition of specialized ActiveX controls, specifically the ADOCE control, database development under VBCE now provides the Rapid Application Development (RAD) environment desired in today’s corporate environment.
The ADOCE control is Microsoft’s ActiveX Data Object for Windows CE, an ADO 1.0-based lean and trim version of the data access capabilities found in desktop Visual Basic. By using of SQL and native object methods, ADOCE provides database management functionality for Microsoft Access structured databases. Pocket Access is not required on the CE device in order to use ADOCE.
If you are using a Windows CE Handheld PC, Professional Edition (Version 2.11 or greater), then chances are that you already have the ADOCE control installed on your Windows CE device.
It is also recommended that you have the latest version of Windows CE Services installed. If you are not sure that ADOCE is installed, simply check the \windows directory on your CE device and look for the ADOCE.DLL file. If this file is not there, you will need to install the control.
Note If you already have ADOCE installed on your Windows CE Pro device, do not install the ADOCE Control from your desktop. This will cause VBCE errors and your application will not run. If you have installed ADOCE without realizing that you already had ADOCE on your device, uninstall ADOCE from the device, and then reregister the built-in ADOCE. (Run REGSVR.EXE \windows\adoce.dll.)
Using the ADOCE control under VBCE is just as easy as using ADO under desktop Visual Basic. The following example shows how easy it is to use ADOCE:
Dim rsMain, sSQL, sSNum ‘declare necessary variables
Set rsMain = CreateObject(“adoce.recordset”) ‘declare pointer to adoce object
SSQL = “SELECT * FROM NAMES WHERE LNAME = ‘SMITH’;” ‘build sql statement
rsMain.open sSQL ‘issue sql statement to adoce
rsMain.movefirst
sSNUM = rsMain.fields(“SSNUM”) ‘get ss number from names table
rsMain.close ‘close the table
As you can see, using ADOCE with VBCE does not require you to write your Visual Basic syntax any differently than if you were writing for desktop Visual Basic. However, there are some limitations in the features that are supported by ADOCE versus standard desktop ADO. The following table shows a list of SQL statements that are supported by ADOCE:
SQL Statement |
Create Table |
Drop Table |
Create Index |
Drop Index |
Alter Table |
Select (Select * From) |
Select Order By |
Select Restricted (NOT, =, <>, and so on.) |
Select Projection (SELECT fieldname, fieldname, and so on.) |
Select Join |
Delete |
Insert |
The most noticeable missing SQL statement from this list is the UPDATE statement. However, record updates can still take place through the ADOCE Update method.
This next table is a list of methods and properties that are supported by ADOCE:
ADO Method |
Addnew |
CancelUpdate |
Clone |
Close |
Delete |
GetRows |
Move |
MoveFirst |
MoveLast |
MoveNext |
MovePrevious |
Open |
Update |
Supports |
ADO Properties |
AbsolutePage |
AbsolutePosition |
ActiveConnection |
BOF,EOF |
Bookmark |
CacheSize |
Count |
CursorType |
EditMode |
LockType |
PageCount |
PageSize |
RecordCount |
Source |
ActualSize |
Attributes |
DefinedSize |
Name |
Type |
UnderlyingValue |
Value |
While this list may seem limited in comparison to desktop ADO, keep in mind that it actually provides a lot of database power for such a small device and operating system. Many developers who are making a transition to Windows CE development must make a mental note that they are not developing for a desktop computer.
This Is Not a Desktop Computer!
This is one of the hardest issues to face when making the transition from desktop to Windows CE development. Due to the size, memory, and resource constraints of these small computer devices, Windows CE is a lean OS that provides the minimum of necessary functions and resources. While a desktop developer may have 64 megabytes (MB) of RAM and a 2-gigabyte (GB) hard drive to utilize, the Windows CE developer has only memory for both program execution and storage. Memory is as low as 2 MB on some devices.
While the topic of Windows CE design methodology could be discussed in an entire book, in the scope of this white paper we’ll simply touch on the aspects related to database design.
The first consideration to keep in mind, when designing database applications for Windows CE, is the size of the data. Some of the following considerations may seem simplistic, but these are things that we sometimes take for granted when doing desktop development. Some questions that you can ask during the design stage are:
Model | Year | Color |
Ford | 1985 | Brown |
Nissan | 1992 | Red |
Dodge | 1994 | Blue |
(Size of table: 39 characters + header)
ModelTable
Model | Index Value |
Dodge | 1 |
Ford | 2 |
Mazda | 3 |
Nissan | 4 |
Plymouth | 5 |
Toyota | 6 |
ColorTable
Color | Index Value |
Black | 1 |
Blue | 2 |
Brown | 3 |
Green | 4 |
Red | 5 |
Yellow | 6 |
Now our Automobile table is this size:
Model | Year | Color |
2 | 1985 | 3 |
4 | 1992 | 5 |
1 | 1994 | 2 |
(Size of table: 18 characters + header)
This can make a big difference when dealing with hundreds or thousands of records.
Field | Used on CE | Used on Server |
FirstName | Yes | Yes |
LastName | Yes | Yes |
Company | Yes | Yes |
Address | Yes | Yes |
City | No | Yes |
State | No | Yes |
Zip | No | Yes |
PhoneNum | Yes | Yes |
AccountNum | Yes | Yes |
In this case, the City, State, and Zip are not needed on the Windows CE device since the Sales Representative has an assigned territory and only needs to know the street address of the customer. While it may be nice to have the additional information, if memory becomes an issue then the unnecessary fields can be left out of the table.
To insure that VBCE has as small a footprint as possible, many constants that we are used to automatically having under desktop Visual Basic are not automatically included in VBCE. Such is the case with the constants for the parameters used by the ADOCE Open method. However, the constants for the parameters of the MsgBox statement do not need to be declared. The example application available for download at the beginning of this article, DataBench, will demonstrate the use of these constants.
The DataBench application was written to demonstrate how to use the ADOCE control with VBCE. Both SQL and native methods are used. DataBench is also a good program for testing the speed of database usage on the Windows CE device you are using.
DataBench can be downloaded by clicking on the link at the beginning of this article.
Figure 1. The DataBench application
VBCE utilizes all variables as variants. Even though this is the case, it is always a good practice to use a variable naming convention that aids in identifying how the variable is being used. We’ve also declared the constants that are used by the ADOCE Open method.
Option Explicit
Dim rsMain ‘our recordset var
Dim lp, lp2 ‘generic loop vars
Dim dtAppStart, dtAppEnd, dtAppTotal ‘application time values
Dim dtCreateStart, dtCreateEnd, dtCreateTotal ‘create table time values
Dim dtInsertStart, dtInsertEnd, dtInsertTotal ‘insert into table time values
Dim dtUpdateStart, dtUpdateEnd, dtUpdateTotal ‘update table time values
Dim dtSortStart, dtSortEnd, dtSortTotal ‘sort table time values
Dim dtSearchStart, dtSearchEnd, dtSearchTotal ‘search table time values
Dim dtHrs, dtMins, dtSecs ‘time segments vars
Dim sAppTitle ‘application title string
Dim sSQL ‘sql string to pass to the adoce object
Dim iIterations, iCurrentIteration ‘for tracking test iterations
Dim iTestRecs, iCurrentTestRec ‘for tracking number of test records
Dim iVal ‘random integer value
Dim sVal ‘random string value
Dim bETrap ‘true if error trapping is turned on
Dim iFindVal ‘value to conduct search against
Dim bGridView ‘true if data grid is being viewed
Dim sMBText ‘text for msgbox
Const adOpenForwardOnly = 0 ‘adoce constants
Const adOpenKeyset = 1 ‘desktop vb includes these constants, vbce does not
Const adLockReadOnly = 1 ‘so we need to declare them here
Const adLockOptimistic = 3
Const adCmdText = 1
Const adCmdTable = 2
Const adCmdStoredProc = 4
Const adCmdUnknown = 8
In the Form_Load() routine, we set the recordset variable.
bETrap = True ‘set to “true” for trapping errors
If bETrap = True Then On Error Resume Next
Set rsMain = CreateObject(“adoce.recordset”) ‘create rs pointer to object
If Err Then
MsgBox “Problem with ADOCE. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
On Error GoTo 0
You’ll notice that we’re using some error checking for trapping any database errors that might occur. The bETrap variable is used as a Boolean flag to indicate if error trapping should occur or not. This is convenient during development because having the error checking off will help to reveal variable naming errors, undeclared variables, and so on. Also, unlike desktop Visual Basic, an App.End statement is required for exiting from the VBCE application.
In the CreateTable() routine, we first build our SQL statement with the table name and the fields to be placed in the table.
For this table, our table name is BENCHTBL and our fields are:
IKEY INT
NUMFIELD INT
TEXTFIELD VARCHAR(20)
DATEFIELD DATETIME
Valid field types are:
INT—4-byte signed integer.
FLOAT—double-precision floating point.
BIT—logical or Boolean.
VARCHAR(n)—null-terminated Unicode character string of length n, maximum of 255 characters.
DATETIME—date and/or time value.
TEXT—variable length string up to 32,000 characters.
VARBINARY(n)—binary value of length n, maximum of 255.
LONG VARBINARY—binary value of length less than 65,469 bytes.
SMALLINT—2-byte signed integer
UINT—unsigned 4-byte integer (for backward compatibility only).
USMALLINT—unsigned 2-byte integer (for backward compatibility only).
The SQL statement could be written as:
create table benchtbl (ikey int, numfield int, textfield varchar(20),
datefield datetime);
However this is difficult to read compared to:
Create table benchtbl (
Ikey int,
Numfield int,
Textfield varchar(20),
Datefield datetime);
Once the SQL statement is built, it can be passed to the ADOCE object with the Open method. Example:
msMain.open sSQL
The following subroutine combines the building of the SQL statement, passing it to the ADOCE object, trapping any errors that might occur, and updating the timestamp statistics.
Private Sub CreateTable() ‘create test table
lblStatus.Caption = “Creating table...”
sSQL = “create table benchtbl(“ ‘build the sql statement
sSQL = sSQL & “ikey int, “
sSQL = sSQL & “numfield int, “
sSQL = sSQL & “textfield varchar(20), “
sSQL = sSQL & “datefield datetime);”
dtCreateStart = calcseconds ‘get timestamp
If bETrap = True Then On Error Resume Next
rsMain.open sSQL ‘issue the sql statement
If Err Then
MsgBox “Create SQL error. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
On Error GoTo 0
dtCreateEnd = calcseconds
dtCreateTotal = dtCreateEnd - dtCreateStart
lblCreateTime.Caption = dtCreateTotal
End Sub
When using DDL (Data Definition Language) statements for manipulating the table structure, the recordset does not remain open after the Open method is executed. Reads or writes to the table cannot take place until the recordset is reopened.
Adding data to the table is just as easy as creating the table. There are several ways to do this. This example shows how to use the Addnew method:
rsMain.open “mytable”, ““, adOpenKeyset, adLockOptimistic
rsMain.Addnew
rsMain.fields(“firstfield”) = “ActiveX Data Objects”
rsMain.fields(“secondfield”) = 1.8
rsMain.Update
rsMain.Close
First the table is opened, then the Addnew method, which opens the table for new records, is called. The Fields collection then defines the fieldname to receive the inserted data. The Update method then stores the data in the table.
Prior to doing an Update, if you decide that you do not want to store any of the changes just made, you can use the CancelUpdate method. As:
rsMain.CancelUpdate
Addnew can also be utilized with parameters for immediately adding data to the table. This use of Addnew does not require the use of the Update method. Either a single value can be inserted, or an array with all the values can be passed. When passing array values, the number of elements in both arrays need to be the same.
rsMain.Addnew “Lastname”, “Smith”
rsMain.Addnew sFieldArray(), sValueArray()
In the DataBench application, we’ll use SQL for adding data to the table. The process is basically the same one that we used when creating the table. First, we create the SQL statement, then we pass it to ADOCE. In DataBench, we are creating random test data, so we loop through the number of required test records for populating the table. When records are inserted with SQL, the recordset is not left open. Thus, the recordset does not need to be closed when record inserting is finished.
Private Sub InsertRecs() ‘generate and insert test records
Randomize Second(Now)
lblStatus.Caption = “Inserting “ & cboNumRecs.Text & “ records...”
dtInsertStart = calcseconds
If bETrap = True Then On Error Resume Next
For iCurrentTestRec = 1 To iTestRecs
sSQL = “INSERT INTO BENCHTBL VALUES(“
sSQL = sSQL & “‘“ & iCurrentTestRec & “‘, “
sSQL = sSQL & “‘“ & Int((Rnd * 32000) + 1) & “‘, “
sSQL = sSQL & “‘“ & Chr(Int((Rnd * 26) + 65)) & “‘, “
sSQL = sSQL & “‘“ & FormatDateTime(Now) & “‘); “
rsMain.open sSQL
If Err Then
MsgBox “Insert SQL error. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
Next
On Error GoTo 0
dtInsertEnd = calcseconds
dtInsertTotal = dtInsertEnd - dtInsertStart
lblInsertTime.Caption = dtInsertTotal
End Sub
To update data through the ADOCE object, we use the Fields collection and the Update method. As with the Addnew method, the Update method can be used with a Fields collection or with parameters.
With Fields collection:
rsMain.Fields(“LastName”) = “Smith”
rsMain.Fields(“FirstName”) = “Joe”
rsMain.Update
With parameters:
RsMain.Update “LastName”, “Smith”
Or an array:
RsMain.Update sFieldArray(), sValueArray()
SQL Update is not currently available in this version of the ADOCE control, so for the DataBench application we’ll use the native method. Because this is a benchmark program, we need to update each of the records in the table. We begin by selecting all of the records with a SQL select statement:
SSQL = “SELECT * FROM BENCHTBL”
Then, while looping through the table, we update one of the fields. You’ll also notice that, once we’re halfway through the table, we get the value from the NUMFIELD and store it in the iFindVal variable. We’ll use this variable later on for doing a search through the table.
Private Sub UpdateRecs() ‘perform an update on each test record
‘The SQL Update statement is not currently supported by ADOCE. So, we’ll
‘use the object update method for updating these records.
lblStatus.Caption = “Updating “ & cboNumRecs.Text & “ records...”
dtUpdateStart = calcseconds
If bETrap = True Then On Error Resume Next
sSQL = “SELECT * FROM BENCHTBL” ‘use sql to open the table
rsMain.open sSQL, ““, adOpenKeyset, adLockOptimistic, adCmdText
rsMain.movefirst
For iCurrentTestRec = 1 To iTestRecs
rsMain.Update “NUMFIELD”, Int((Rnd * 32000) + 1) ‘perform update
If iCurrentTestRec = Int(iTestRecs / 2) Then ‘get a sample for search
iFindVal = rsMain.fields(“NUMFIELD”)
End If
rsMain.movenext
If Err Then
MsgBox “Update error. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
Next
On Error GoTo 0
rsMain.Close
dtUpdateEnd = calcseconds
dtUpdateTotal = dtUpdateEnd - dtUpdateStart
lblUpdateTime.Caption = dtUpdateTotal
End Sub
Notice that in this subroutine we had to use the Close method. This is due to the fact that we opened the table for updating.
There are two processes that can be used for deleting records from a table. The first process is uses the Delete method. A Delete is performed upon the current record in the table and the deleted record will remain current until you move to a different record. Also, the table must be opened as updateable prior to performing the delete. Here’s an example:
rsMain.open “SELECT * FROM MYTABLE”, ““, adOpenKeyset, adLockOptimistic
rsMain.movelast
rsMain.delete
rsMain.close
For deleting single records this works fine. However, if you need to delete a group of records that match specific criteria, then the best approach would be to utilize a SQL statement, such as:
sSQL = “DELETE FROM MYTABLE WHERE BALANCE < ‘0’”
rsMain.open sSQL
The Delete method supports the use of the AffectRecords parameter for maintaining compatibility with ADO desktop. However, the only value supported is 1.
Sorting a table is a very simple process with SQL. In its simplest form, a table can be sorted, while being opened, this way:
rsMain.open “SELECT * FROM MYTABLE ORDER BY LASTNAME”, ““, 1, 3
If an index has been created on the Order By field name, the parameters of the index will be utilized for the sort. Otherwise, a sort will be done in memory and an ordered result set will be opened. The default order for this type of sort is Ascending. This can be specified by adding the DESC or ASC parameter to the SQL statement. Example:
rsMain.open “SELECT * FROM MYTABLE ORDER BY LASTNAME DESC”, ““, 1, 3
Or:
rsMain.open “SELECT * FROM MYTABLE ORDER BY LASTNAME ASC”, ““, 1, 3
Here is our code for doing the sort in DataBench:
Private Sub SortRecs() ‘sort the table
lblStatus.Caption = “Sorting table...”
sSQL = “SELECT * FROM BENCHTBL “ ‘build the sql statement
sSQL = sSQL & “ORDER BY NUMFIELD; “
dtSortStart = calcseconds
If bETrap = True Then On Error Resume Next
rsMain.open sSQL ‘issue the sql statement
If Err Then
MsgBox “Sort SQL error. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
On Error GoTo 0
rsMain.Close
dtSortEnd = calcseconds
dtSortTotal = dtSortEnd - dtSortStart
lblSortTime.Caption = dtSortTotal
End Sub
In SQL, searching for a specific record or group of records can be done through the use of Where clauses that are appended to the SQL statement. These can be simple or very complex depending on the need. Here’s a simple Where clause:
sSQL = “SELECT * FROM MYTABLE WHERE LASTNAME = ‘SMITH’”
rsMain.open sSQL, ““, adOpenKeyset, adLockOptimistic
Here’s a more complex example:
sSQL = “SELECT FIRSTNAME, LASTNAME, BALANCE, ACTIVE FROM MYTABLE “
sSQL = sSQL & “WHERE LASTNAME = ‘SMITH’ “
sSQL = sSQL & “AND BALANCE > ‘100’ “
sSQL = sSQL & “AND BALANCE < ‘10000’ “
sSQL = sSQL & “AND ACTIVE IS NOT NULL “
rsMain.open sSQL, ““, adOpenKeyset, adLockOptimistic
Here is our search routine for the DataBench application. Remember that iFindVal variable we set earlier?
Private Sub FindRec() ‘conduct a search for the sampled value
lblStatus.Caption = “Searching table...”
sSQL = “SELECT * FROM BENCHTBL “ ‘build the sql statement
sSQL = sSQL & “WHERE NUMFIELD = ‘“ & iFindVal & “‘; “
dtSearchStart = calcseconds
If bETrap = True Then On Error Resume Next
rsMain.open sSQL ‘issue the sql statement
If Err Then
MsgBox “Search SQL error. “ & Hex(Err) & “ - “ & _
Err.Description, vbCritical, sAppTitle
Err.Clear
App.End
End If
On Error GoTo 0
rsMain.Close
dtSearchEnd = calcseconds
dtSearchTotal = dtSearchEnd - dtSearchStart
lblSearchTime.Caption = dtSearchTotal
End Sub
For increased performance of the database, indexes can be created and utilized. An index can be created for one field in the table and only one index can be open at a time. The following example shows how to create and use an index:
sSQL = “CREATE INDEX LNAMENDX ON MYTABLE (LASTNAME)”
rsMain.open sSQL
sSQL = “SELECT * FROM MYTABLE ORDER BY LASTNAME”
rsMain.open sSQL
The sort order defaults to Ascending but can be set to descending by using DESC.
sSQL = “CREATE INDEX LNAMENDX ON MYTABLE (LASTNAME DESC)”
Tables that have been created on the Windows CE device can be moved to Microsoft Access desktop through the use of the Import and Export Database functions in the Mobile Devices window of Windows CE Services. These can be found under the Tools menu.
To demonstrate this process, we’ll copy the BENCHTBL table from the Windows CE device to your PC:
You’ll now be able to open up the table in Access on your PC.
Windows CE Services can also be configured for automatic synchronization of the tables when the Windows CE device is connected. This can be done through the Tools menu of Windows CE Services on the PC. To activate, follow these steps:
The database will now be synchronized whenever you establish a connection between the Windows CE device and PC.
Note In order for this type of synchronizing to work, your tables need to have a primary key field with a data type of AutoNumber. While this is easy to do with Access on the desktop PC, it is a little more difficult on the Windows CE device. The Windows CE-based table must have an index called PrimaryKey that is based on a single, unique-value field. To get this to work with the DataBench program, we would need to add the following line after the table is created:
sSQL = “CREATE INDEX PrimaryKey ON BENCHTBL (iKey)”
rsMain.open sSQL
The ADOCE SDK comes with an API that allows the calling of the two functions used by Windows CE Services for moving files between the Windows CE device and the PC. These two functions are contained in the adofilter.dll file and are called DesktopToDevice and DeviceToDesktop. The following example demonstrates how to move a table from the Windows CE device to the PC via this API. (This example can also be found at http://support.microsoft.com/support/kb/articles/Q196/0/34.ASP.)
Steps to use (through desktop Visual Basic 6.0):
SELECT Authors.* INTO Authors2 FROM Authors WHERE (Authors.AU_id < 50)
Private Declare Function DESKTOPTODEVICE _
Lib “c:\program files\windows ce services\adofiltr.dll” _
(ByVal desktoplocn As String, _
ByVal tablelist As String, _
ByVal sync As Boolean, _
ByVal overwrite As Integer, _
ByVal devicelocn As String) As Long
Private Declare Function DEVICETODESKTOP _
Lib “c:\program files\windows ce services\adofiltr.dll” _
(ByVal desktoplocn As String, _
ByVal tablelist As String, _
ByVal sync As Boolean, _
ByVal overwrite As Integer, _
ByVal devicelocn As String) As Long
Private Sub Command1_Click()
Dim result As Long, sPath As String, sTableList As String
sPath = “c:\Program Files\Microsoft Visual Studio\VB98\biblio2.mdb”
sTableList = “Authors2..”
‘ Change mouse pointer to hourglass.
Screen.MousePointer = vbHourglass
‘ import table from remote device.
result = DEVICETODESKTOP(sPath, sTableList, False, False, ““)
‘ Return mouse pointer to normal.
Screen.MousePointer = vbDefault
If result = 0 Then
MsgBox “Transfer Successful”
Else
MsgBox “An error occurred transferring the data: “ & result
End If
End Sub
Private Sub Command2_Click()
Dim result As Long, sPath As String, sTableList As String
sPath = “c:\Program Files\Microsoft Visual Studio\VB98\biblio.mdb”
sTableList = “Authors2..”
‘ Change mouse pointer to hourglass.
Screen.MousePointer = vbHourglass
‘Export table to remote device.
result = DESKTOPTODEVICE(sPath, sTableList, False, False, ““)
‘ Return mouse pointer to normal.
Screen.MousePointer = vbDefault
If result = 0 Then
MsgBox “Transfer Successful”
Else
MsgBox “An error occurred transferring the data: “ & result
End If
End Sub
Be sure to click the PC to Device button first, then the Device to PC button to complete the test. You’ll notice that the biblio2.mdb file is created when the Device to PC button is clicked. If you try this test a second time, you’ll get errors because the tables already exist on the device and on the PC.
With the power to programmatically control the flow of data between the PC and the Windows CE device, the possibilities are unlimited. When using the ADOCE API from the PC, you can substitute a DSN (ODBC) in place of the desktoplocn parameter of the API functions. This gives you the ability to move data to and from any ODBC-compliant database that the PC is connected to.
With the power of the ADOCE control, the possibilities for database connectivity to Windows CE are unlimited. We can only expect that, with any future versions of ADOCE and VBCE, the power and flexibility of the ADOCE control will greatly increase. At least now, with this version, it is now possible to put the power of Windows CE into the corporate environment.
Timothy Trimble is the President of Extended Technology Systems, based in Carlsbad, California. Extended Technology Systems provides custom software development services for handheld computers. They can be found on the World Wide Web at www.exts.com/.