Implementing the Windows CE Device Provider

< BACK  NEXT >
[oR]

ActiveSync on a Windows CE device is based around store objects such as files, directories, databases, and database records. As described in Chapter 4, each object has a unique object identifier or CEOID. Any object that has a CEOID can be an item synchronized by ActiveSync. Thus, you can synchronize files, directories, databases, or database records. You can use these objects to hold more than one item (for example a file might contain many records, each of which is an item), but you will need to manage lists of these items, and this gets more complex. The simplest approach is to represent an ActiveSync item by a single database record.

Windows CE 2.1 and later versions allow databases to be created in volumes that can be located on, for example, storage cards. Additional functions (FindObjects and SyncData) are provided to synchronize databases in these volumes. Note that some storage cards and other media do not use the CEOID object identifiers for the file system, so they are more difficult to synchronize.

Remember, the Windows CE ActiveSync DLL is not a COM component. However, the DLL does implement the interface IReplObjHandler. The DLL itself is responsible for creating an instance of this interface (usually through implementing the interface using a C++ class), and not any external application. Therefore, all the usual COM elements, such as class factories, DLLGetObject, and other exported functions, are not required.

First, let's look at the functions the ActiveSync provider must export.

InitObjType Exported Function

InitObjType, located in ASDevice.cpp, is called by ActiveSync when the provider is started and terminated. This occurs when the Windows CE device connects or disconnects. This function carries out any initialization/termination required by the provider and returns a pointer to the IReplObjHandler interface.

Listing 17.1 Implementation of InitObjType
 extern "C" BOOL _declspec(dllexport) InitObjType(       LPWSTR lpszObjType,       IReplObjHandler **ppObjHandler,       UINT uPartnerBit) {   if ( lpszObjType == NULL )   {     // Terminates the device provider module and     // frees all allocated resources.     return TRUE;   }   // Allocate a new IReplObjHandler.   *ppObjHandler = new CDataHandler;   // Save the uPartnerBit so that you can use it later on   g_uPartnerBit = uPartnerBit;   // Find Object Identifier of our database   g_oidDataBase = ASGetDBOID(DB_NAME);   return TRUE; } 

In Listing 17.1 the C++ class CDataHandler implements the IReplObjHandler interface, so an instance of the class is created and a pointer returned through the parameter ppObjHandler.

A Windows CE device can maintain synchronization with up to two desktop PCs. The uPartnerBit has a value 1 when synchronizing with the first partnership and 2 with the second. Maintaining two partnerships is more complex; you can choose to support only one partnership, as is the case with the EMail ActiveSync option. In this code the partnership bit is saved in the global variable g_uPartnerBit.

Lastly, the CEOID of our database is stored in the global variable g_oidDatabase. The function ASGetDBOID is located in DB.CPP, together with the other database access code for the provider.

ObjectNotify Exported Function

Windows CE constantly monitors changes in the object store. When a change occurs (for example, a database record is updated), all loaded ActiveSync providers are notified of the change through a call to ObjectNotify. This function determines the nature of the change (whether it was a file, directory, database, or record change) and returns TRUE if it is an item this provider can synchronize.

The provider should ensure that the record is in its own database. There is no point synchronizing someone else's database! This is done through the function ASRecInDB to be found in DB.CPP, which determines the parent CEOID for the database record by calling CEOidGetInfo, and checks that this is the same as the CEOID for our database.

Listing 17.2 Implementation of ObjectNotify
 extern "C" BOOL _declspec(dllexport) ObjectNotify(         POBJNOTIFY pNotify) {   // Check to see if the structure size   // is the smaller (version control).   if ( pNotify->cbStruct  sizeof( OBJNOTIFY ) )   {     MessageBox(NULL,       _T("ObjectNotify incorrect version"),       NULL, MB_OK);     return FALSE;   }   // We're only interested in database record   // changes or clear change notifications   if(!(pNotify->uFlags & (ONF_RECORD |         ONF_CLEAR_CHANGE)))     return FALSE;   // For non-deleted records, check that the   // record is in our database   if(!(pNotify->uFlags & ONF_DELETED))   {     if(!(pNotify->uFlags & ONF_RECORD))       // it's not actually a record, so ignore       return FALSE;     if(!ASRecInDB(g_oidDataBase, pNotify->oidObject))       // not in our database       return FALSE;   }   // sets the oid of the object to be replicated   pNotify->poid = (UINT*) &pNotify->oidObject;   // if object is to be deleted, set the   // number of objects to be deleted   if(pNotify->uFlags & ONF_DELETED)     pNotify->cOidDel = 1;    else     pNotify->cOidChg = 1;   return TRUE; } 

ObjectNotify is passed a pointer to an OBJNOTIFY structure. The members of this structure used in this sample are shown in Table 17.1.

Table 17.1. OBJNOTIFY structure members
Member Description
cbStruct The size of the structure being passed in. The provider should check this against the size of structure it is using to ensure version compatibility. This should be done for all structures passed to provider functions.
uFlags Contains flags indicating the type of change. For example, ONF_RECORD indicates a database record has changed, ONF_CLEAR_CHANGE indicates that the change bit for the object should be cleared, and ONF_DELETED indicates that a record has been deleted.
oidObject CEOID of the item being notified.
poid Set by the provider to be a pointer to the CEOID of the item to be synchronized. In simple providers, this will generally be the CEOID of the item passed into ObjectNotify through the member oidObject. More complex providers can set an array of CEOIDs to be synchronized.
cOidDel Number of items to be deleted. This will be one if uFlags is set to ONF_DELETED for simple providers.
cOidChg Number of items to be changed. This will be one if uFlags is set to ONF_RECORD for simple providers.

GetObjTypeInfo Exported Function

ActiveSync on the Windows CE device calls this exported function when it needs information about the database being synchronized. The function fills in members of the OBJTYPEINFO structure, such as the following:

  • szName The name of the database.

  • cObjects The number of items to be synchronized (which for simple providers is the number of database records).

  • cbAllobj The overall size of the items to be synchronized. This is equal to the size of the database in bytes.

  • ftLastModified A FILETIME structure containing the time and date of when the database was last changed.

Listing 17.3 shows the implementation of GetObjTypeInfo from the file ASDevice.CPP.

Listing 17.3 Implementation of GetObjTypeInfo
 extern "C" BOOL _declspec(dllexport) GetObjTypeInfo       (POBJTYPEINFO pInfo) {   CEOIDINFO oidInfo;   // Check versioning of the structure   if ( pInfo->cbStruct < sizeof( OBJTYPEINFO ) )   {     MessageBox(NULL,       _T("GetObjTypeInfo called wrong version"),       NULL, MB_OK);     return FALSE;   }   // Clear the structure.   memset( &(oidInfo), 0, sizeof(oidInfo));   // Retrieves information about the object   // in the object store.   CeOidGetInfo( g_oidDataBase, &oidInfo );   // Store the database information into   // the OBJTYPEINFO structure.   wcscpy( pInfo->szName,     oidInfo.infDatabase.szDbaseName );   pInfo->cObjects = oidInfo.infDatabase.wNumRecords;   pInfo->cbAllObj = oidInfo.infDatabase.dwSize;   pInfo->ftLastModified =     oidInfo.infDatabase.ftLastModified;   return TRUE; } 

Note that the version of the OBJTYPEINFO structure is checked. Information about the database is obtained through a call to the Windows CE function CeOidGetInfo, which fills in a CEOIDINFO structure.

Implementing the Device IReplObjHandler COM Interface

IReplObjHandler functions are responsible for converting your items (such as database records) into a stream of bytes (serialization) or converting a stream of bytes into an item (deserialization). Serialization and deserialization are required so that items can be transferred between Windows CE devices and desktop PCs.

The desktop PC ActiveSync provider also needs to implement the IReplObjHandler and should serialize and deserialize items using the same data format. However, the implementations are typically different (since the item stores are not the same), so it is usually best to keep to separate code implementations.

In the example, IReplObjHandler is implemented by the C++ class CDataHandler that is declared in ReplObjHandler.h and implemented in ReplObjHandler.cpp. Since IReplObjHandler is declared as a COM interface, IUKnown must be implemented. However, since we are only implementing a COM interface and not an entire COM component, these implementations are very straightforward. AddRef and Release simply increment and decrement a reference count. QueryInterfce always returns E_NOINTERFACE this function will never actually be called.

An overview of the essential IReplObjHandler interface functions is provided in Table 17.2.

Table 17.2. IReplObjHandler interface functions
Function Description
Setup Called when serialization or deserialization of an item is about to begin.
Reset Called when serialization or deserialization is completed.
GetPacket Serialization. This function is called to convert an item into a stream of bytes, which occurs when an item is being sent from the Windows CE device to desktop PC. Large items need to be split up into packets, and GetPacket will be called once for each packet. Small items can be serialized in a single packet. The function returns NOERROR if more packets are required, or RWRN_LAST_PACKET if this is the last or only packet.
SetPacket Deserialization. This function is called when an item needs to be converted from a stream of bytes to an item. This occurs when an item is being sent from the desktop PC to Windows CE device. Large items are divided into packets, and a call to SetPacket is made for each packet. The item is written out to the database when all packets are received. This could result in an existing record being updated, or a new record added.
DeleteObject Called when ActiveSync detects that an item must be deleted from the Windows CE device database.

Serialization Format

You will need to determine the format to be used for serialization and deserialization. GetPacket and SetPacket provide LPBYTE pointers, but this can be cast to any pointer you like. In the example, a typedef for a structure called NOTE is used, and this contains the creation FILETIME (the unique identifier), the last modify timestamp as a FILETIME, and a Unicode string (Listing 17.4). It is declared in db.h.

Listing 17.4 Structure NOTE
 typedef struct tagNOTE {   FILETIME ftOriginal;     // time when note was created   FILETIME ftLastUpdate;   // time last updated   WCHAR szNote[STRLEN_NOTE]; } NOTE; 

Note that the last modify timestamp member is not used on the device, and so is not strictly required in this structure. However, this same structure is used on the desktop PC for storing data, so it is convenient to leave it here.

It is essential that exactly the same format is used by the ActiveSync provider on the Windows CE device and the desktop PC. Note how the Unicode string has been declared as WCHAR and not TCHAR. This ensures that it is defined as a Unicode string even if the code is compiled for ANSI (which is most often the case on the desktop PC).

IReplObjHandler::Setup

This function is called by ActiveSync before any item is received or sent. This provides an opportunity to perform any initialization. A pointer to a REPLSETUP structure is passed in, and this provides information such as the direction of the transfer.

The Setup function will normally save a pointer to the REPLSETUP structure for future use. Since ActiveSync is multithreaded, it is possible that a read (outgoing transfer) occurs at the same time as a write (incoming transfer). Therefore, the IReplObjHandler class has two members, m_pReadSetup and m_p- WriteSetup, to store separate pointers (Listing 17.5). The pointer will be used later in GetPacket and SetPacket.

Listing 17.5 IReplObjHandler::Setup implementation
 STDMETHODIMP CDataHandler::Setup(PREPLSETUP pSetup) {   // Can be reading and writing at the same time, so need   // two setups   if(pSetup->fRead)     m_pReadSetup = pSetup;   else     m_pWriteSetup = pSetup;   return NOERROR; } 

Most REPLSETUP members are only used on a desktop implementation of IReplObjHandler. Those listed below may be used on the Windows CE device.

  • fRead TRUE if Setup is being called for reading an item, FALSE for a write

  • Oid CEOID of the item, for example, an existing record in the database that is being sent to the desktop

  • oidNew Set to the CEOID of the item that has been added or updated in the CE property database

  • dwFlags Contains the value RSF_NEW_OBJECT if this is a new record to be added to the CE property database

IReplObjHandler::Reset

The Reset function provides an opportunity to free any resources created during serialization or deserialization. In this case, there is nothing to do (Listing 17.6).

Listing 17.6 IReplObjHandler::Reset implementation
 STDMETHODIMP CDataHandler::Reset(PREPLSETUP pSetup) {   return NOERROR;    // no resources to be freed } 

IReplObjHandler::GetPacket

ActiveSync calls this function to request a packet for a particular item being synchronized. Your implementation should produce a byte stream representing the entire item (if it fits into a single packet) or the next packet in sequence. The function passes in the recommended maximum size of the packet in cbRecommend.

Listing 17.7 shows the implementation of GetPacket. The function calls ASSerializeRecord (located in db.cpp) to read the record for the given CEOID (m_pReadSetup->oid). The function serializes the record into a NOTE structure, returns a pointer to the structure in lpByte, and returns the size of the NOTE structure in dwLen. The pointer and size are returned to ActiveSync through the parameters lppbData and pcbData.

Listing 17.7 IReplObjHandler:: GetPacket implementation
 STDMETHODIMP CDataHandler::GetPacket(LPBYTE *lppbData,     DWORD *pcbData, DWORD cbRecommend) {   HRESULT hr = RWRN_LAST_PACKET;   LPBYTE lpByte;   DWORD dwLen;   if(!ASSerialiseRecord(m_pReadSetup->oid,         &lpByte, &dwLen))     hr = RERR_BAD_OBJECT;   else   {     *lppbData = lpByte;     *pcbData = dwLen;   }   return hr; } 

GetPacket returns RWRN_LAST_PACKET if the serialization was successful and this is the last or only packet. RERR_BAD_OBJECT is returned if the object could not be serialized.

IReplObjHandler::SetPacket

SetPacket does the opposite of GetPacket it is passed a pointer to a stream of bytes and writes the data to a new or existing record in the database. The REPLSETUP structure member dwFlags contains the value RSF_NEW_OBJECT if this item is a new record; otherwise, an existing record is to be updated.

In Listing 17.8, SetPacket casts the incoming lpbData pointer to a NOTE pointer and calls ASDeserializeRecord (located in db.cpp) to perform the update. The REPLSETUP structure member oid contains the CEOID of the record to be updated, or 0 if this is a new record.

Listing 17.8 IReplObjHandler:: SetPacket implementation
 STDMETHODIMP CDataHandler::SetPacket(LPBYTE lpbData,         DWORD cbData) {   NOTE* aNote;   CEOID oidNewRec;   BOOL bNewRec;   aNote = (NOTE*)lpbData;   bNewRec = m_pWriteSetup->dwFlags & RSF_NEW_OBJECT;   if((oidNewRec = ASDeserializeRecord(&aNote->ftOriginal,         &aNote->ftLastUpdate,         aNote->szNote,         wcslen(aNote->szNote),         bNewRec, m_pWriteSetup->oid)) == 0)     return RERR_SKIP_ALL;   else   {     m_pWriteSetup->oidNew = oidNewRec;     return NOERROR;   } } 

SetPacket returns RERR_SKIP_ALL if the update fails. This will cause all subsequent packets to be discarded. If successful, the CEOID of the new record is assigned to the oidNew member of REPLSETUP, and the function returns NOERROR.

IReplObjHandler::DeleteObj

DeleteObj is called when an item needs to be deleted from the database. The function is passed a REPLSETUP pointer as a parameter and calls ASDeleteRecord (located in DB.CPP) to delete the record (Listing 17.9).

Listing 17.9 IReplObjHandler:: DeleteObj implementation
 STDMETHODIMP CDataHandler::DeleteObj(PREPLSETUP pSetup) {   if(ASDeleteRecord(pSetup->oid))     return NOERROR;   else     return E_UNEXPECTED; } 

< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net