Remote API (RAPI)

The Remote API (RAPI) provides a set of helper functions that enable a desktop-based application to execute code on a connected Pocket PC device. Once a function has returned, the results are sent back to the PC. In essence, RAPI is a type of one-way Remote Procedure Call (RPC)?the client (your desktop application) makes a request to the server (a connected Pocket PC device) to execute some functionality, and returns the results to it.

RAPI was originally designed as a way to manage a Pocket PC device from the desktop. It includes functions that enable an application to query the file system, registry, and device databases, as well as get information about the Pocket PC's system configuration. You can even create your own functions, which can be run over the RAPI APIs.

You will quickly notice that most of the functions in RAPI look similar to the functions in the standard Pocket PC and Windows 32 API. In fact, they typically have the same definition and number of parameters, as well as the same return values, as a standard desktop function call. The only difference is that they all are prefixed with the letters Ce. For example, the RAPI function CeFindFirstFile() is the same as the desktop FindFirstFile() API, except that it will enumerate the files on a connected Pocket PC device, rather than those on the desktop. This being the case, I will not provide a detailed description of each function available via the RAPI API.

Because RAPI is run on the desktop, you must ensure that the computer running your application has the latest version of ActiveSync installed on it. This will ensure that rapi.dll (which is required for your application to work) is present on the desktop, as you may not distribute rapi.dll on your own. You can call RAPI from console applications, window applications, and even a .NET assembly.

In order to use the Remote API within your applications, you need to include the rapi.h header file in your project, as well as link with the rapi.lib library (note that because this is a desktop library, it is located in the .\wce300\Pocket PC 2002\support\ActiveSync\lib directory in the folder where you have installed Embedded Visual C++).

Using RAPI

Before you can use any of the RAPI functions, you must first initialize Windows CE's remote services and establish a communications link with a connected device by calling either the CeRapiInit() or CeRapiInitEx() functions.

The simplest way to start RAPI is by calling the synchronous (i.e., blocking) function CeRapiInit(), which is defined as follows:

HRESULT CeRapiInit();

Once the function is called, it will immediately attempt to establish a connection to a Pocket PC device, and will not return control to your application until either a connection has been made or the function fails. CeRapiInit() will return E_SUCCESS if a successful connection can be made and RAPI has initialized without a problem; otherwise, you will be returned E_FAIL. If RAPI has already been initialized, you will receive CERAPI_E_ALREADYINITIALIZED as the return value.

The following short code sample shows you how to use the CeRapiInit() function:

HRESULT hr = S_OK;
hr = CeRapiInit();

if(FAILED(hr)) {
   if(hr == CERAPI_E_ALREADYINITIALIZED)
      OutputDebugString(TEXT("RAPI has already been
          initalized"));
   return FALSE;
}

Although using the CeRapiInitEx() function is a bit more involved, you will find that it provides you with a greater amount of control because it is asynchronous (the function will return to you immediately). This means, of course, that you will have to periodically check the RAPI event handle you are returned in order to find out when it has become signaled.

The CeRapiInitEx() function is defined as follows:

HRESULT CeRapiInitEx(RAPIINIT *pRapiInit);

The only parameter that the function takes is a pointer to a RAPIINIT structure, which contains information about the RAPI event handle and its status. The structure is defined as follows:

typedef struct _RAPIINIT {
   DWORD cbSize;
   HANDLE heRapiInit;
   HRESULT hrRapiInit;
} RAPIINIT;

The first field, cbSize, should be set before calling CeRapiInitEx() with the size of the RAPIINIT structure. This is followed by heRapiInit, which will be filled in with the event handle that you can use to check on the status of your RAPI connection. The last field, hrRapiInit, will be filled in with the return code from CeRapiInitEx() once heRapiInit becomes signaled.

The following code snippet shows how you can use the CeRapiInitEx() function to initialize your RAPI connection by using the WaitForSingleObject() function to monitor the RAPI event handle:

HRESULT hr = S_OK;
RAPIINIT rapiInit;

memset(&rapiInit, 0, sizeof(RAPIINIT));
rapiInit.cbSize = sizeof(RAPIINIT);

hr = CeRapiInitEx(&rapiInit);
if(FAILED(hr)) {
   if(hr == CERAPI_E_ALREADYINITIALIZED)
      OutputDebugString(TEXT("RAPI has already been
      initalized"));
   return FALSE;
}

// Wait for RAPI to be signaled
DWORD dwResult = 0;
dwResult = WaitForSingleObject(rapiInit.heRapiInit, 5000);
if(dwResult == WAIT_TIMEOUT || dwResult == WAIT_ABANDONED) {
   // RAPI has failed or timed out. Proceed with cleanup
   CeRapiUninit();
   return FALSE;
}

if(dwResult == WAIT_OBJECT_0 && SUCCEEDED(rapiInit.hrRapiInit)) {
   // RAPI has succeeded.
   OutputDebugString(TEXT("RAPI Initialized."));

   // Do something here
}

When working with applications that use RAPI, it is important to remember that you are relying on a network connection between the desktop and a device. When a function fails, an error can occur in either the RAPI layer or the function itself. You can determine where the error has actually occurred by calling into the CeRapiGetError() function, which is defined as follows:

HRESULT CeRapiGetError(void);

The function takes no parameters, and will return a value other than 0 if RAPI itself was responsible for the function failing. If CeRapiGetError() returns 0, however, you know that the error occurred in the actual remote function, and you can use the CeGetLastError() function to determine the error code, as shown in the following example:

DWORD dwError = CeRapiGetError();

if(dwError == 0) {
   // The error did not occur in RAPI, find out what the
   // remote function returned
   dwError = CeGetLastError();
}

A few functions (CeFindAllDatabases(), CeFindAllFiles(), and CeReadRecordProps()) will allocate memory on the desktop when they are called. In order to properly free this memory, you can use the following function:

HRESULT CeRapiFreeBuffer(LPVOID);

The only parameter you need to pass in is a pointer to the buffer that was allocated. If the function succeeds, then you will be returned a value of S_OK.

When you have finished using RAPI, you must also make sure that you properly shut down the remote connection services. To do so, you can simply use the following function:

HRESULT CeRapiUninit();

The function takes no parameters, and will return a value of E_FAIL if RAPI has not been previously initialized.

File System RAPI Functions

Table 9.2 lists the Remote API functions for working with the Pocket PC file system.

Table 9.2. RAPI File System Functions

Remote File System Functions

[View full width]
BOOL CeCloseHandle(HANDLE); BOOL CeCopyFile(LPCWSTR, LPCWSTR, BOOL); BOOL CeCreateDirectory(LPCWSTR, graphics/ccc.gifLPSECURITY_ATTRIBUTES); BOOL CeDeleteFile(LPCWSTR); BOOL CeFindAllFiles(LPCWSTR, DWORD, LPDWORD, LPLPCE_FIND_DATA); BOOL CeFindClose(HANDLE); BOOL CeFindNextFile(HANDLE, LPCE_FIND_DATA); BOOL CeGetFileSize(HANDLE, LPDWORD); BOOL CeGetFileTime(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME); BOOL CeMoveFile(LPCWSTR, LPCWSTR); BOOL CeReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); BOOL CeRemoveDirectory(LPCWSTR); BOOL CeSetEndOfFile(HANDLE); BOOL CeSetFileAttributes(LPCWSTR, DWORD); BOOL CeSetFileTime(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME); BOOL CeWriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); DWORD CeGetFileAttributes(LPCWSTR); DWORD CeSetFilePointer(HANDLE, LONG, PLONG, graphics/ccc.gifDWORD); HANDLE CeCreateFile(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); HANDLE CeFindFirstFile(LPCWSTR, LPCE_FIND_DATA);

The following example shows how you can use the Remote API's CeFindAllFiles() (as well as CeRapiFreeBuffer()) function on the desktop to easily retrieve a list of the wave files that are located on the device:

LPVOID lpvFindData = NULL;
DWORD dwFileCount = 0;

// Get a list of the WAV files in the \Windows folder
if(CeFindAllFiles(TEXT("\\Windows\\*.wav"),
   FAF_NO_HIDDEN_SYS_ROMMODULES|FAF_NAME, &dwFileCount,
   (LPLPCE_FIND_DATA)&lpvFindData) == FALSE) {

   DWORD dwError = CeRapiGetError();
   TCHAR tchError[128] = TEXT("\0");

   if(dwError == 0)
      dwError = CeGetLastError();

   wsprintf(tchError, TEXT("Error: %d"), dwError);
   OutputDebugString(tchError);
   return FALSE;
}

// Walk through the files
LPCE_FIND_DATA pFindData = (LPCE_FIND_DATA)lpvFindData;

for(DWORD dwCount = 0; dwCount<dwFileCount; dwCount++) {
   TCHAR tchFile[MAX_PATH+1] = TEXT("\0");

   wsprintf(tchFile, TEXT("Remote File:%s\r\n"),
      pFindData[dwCount].cFileName);
   OutputDebugString(tchFile);
}

// Clean up the buffer
if(lpvFindData)
   CeRapiFreeBuffer(lpvFindData);
Registry RAPI Functions

Table 9.3 lists the Remote API functions that are available for manipulating the Pocket PC registry from the desktop host.

Table 9.3. RAPI Registry Functions

Remote Registry Functions

[View full width]
LONG CeRegCloseKey(HKEY); LONG CeRegCreateKeyEx(HKEY, LPCWSTR, DWORD, graphics/ccc.gifLPWSTR, DWORD, REGSAM, LPSECURITY_ATTRIBUTES, PHKEY, LPDWORD); LONG CeRegDeleteKey(HKEY, LPCWSTR); LONG CeRegDeleteValue(HKEY, LPCWSTR); LONG CeRegEnumKeyEx(HKEY, DWORD, LPWSTR, LPDWORD, graphics/ccc.gifLPDWORD, LPWSTR, LPDWORD, PFILETIME); LONG CeRegEnumValue(HKEY, DWORD, LPWSTR, LPDWORD, graphics/ccc.gifLPDWORD, LPDWORD, LPBYTE, LPDWORD); LONG CeRegOpenKeyEx(HKEY, LPCWSTR, DWORD, REGSAM, graphics/ccc.gifPHKEY); LONG CeRegQueryInfoKey(HKEY, LPWSTR, LPDWORD, graphics/ccc.gifLPDWORD, LPDWORD, LPDWORD, LPDWORD, LPDWORD, LPDWORD, graphics/ccc.gifLPDWORD, LPDWORD, PFILETIME); LONG CeRegQueryValueEx(HKEY, LPCWSTR, LPDWORD, graphics/ccc.gifLPDWORD, LPBYTE, LPDWORD); LONG CeRegSetValueEx(HKEY, LPCWSTR, DWORD, DWORD, graphics/ccc.gifLPBYTE, DWORD);

Database RAPI Functions

Table 9.4 lists the Remote API functions for working with Pocket PC databases.

Table 9.4. RAPI Database Functions

Remote Database Functions

[View full width]
BOOL CeDeleteDatabase(CEOID); BOOL CeDeleteDatabaseEx(PCEGUID, CEOID); BOOL CeDeleteRecord(HANDLE, CEOID); BOOL CeEnumDBVolumes(PCEGUID, LPWSTR, DWORD); BOOL CeFindAllDatabases(DWORD, WORD, LPWORD, LPLPCEDB_FIND_DATA); BOOL CeFlushDBVol(PCEGUID); BOOL CeMountDBVol(PCEGUID, LPWSTR, DWORD); BOOL CeOidGetInfo(CEOID, CEOIDINFO*); BOOL CeOidGetInfoEx(PCEGUID, CEOID, CEOIDINFO*); BOOL CeSetDatabaseInfo(CEOID, CEDBASEINFO*); BOOL CeSetDatabaseInfoEx(PCEGUID, CEOID, graphics/ccc.gifCEDBASEINFO*); BOOL CeUnmountDBVol(PCEGUID); CEOID CeCreateDatabase(LPWSTR, DWORD, WORD, graphics/ccc.gifSORTORDERSPEC*); CEOID CeFindNextDatabase(HANDLE); CEOID CeCreateDatabaseEx(PCEGUID, CEDBASEINFO*); CEOID CeFindNextDatabaseEx(HANDLE, PCEGUID); CEOID CeReadRecordProps(HANDLE, DWORD, LPWORD, graphics/ccc.gifCEPROPID*, LPBYTE*, LPDWORD); CEOID CeReadRecordPropsEx(HANDLE, DWORD, LPWORD, graphics/ccc.gifCEPROPID*, LPBYTE*, LPDWORD, HANDLE); CEOID CeSeekDatabase(HANDLE, DWORD, DWORD, graphics/ccc.gifLPDWORD); CEOID CeWriteRecordProps(HANDLE, CEOID, WORD, graphics/ccc.gifCEPROPVAL*); CEOID CeWriteRecordProps(HANDLE, CEOID, WORD, graphics/ccc.gifCEPROPVAL*); HANDLE CeFindFirstDatabase(DWORD); HANDLE CeFindFirstDatabaseEx(PCEGUID, DWORD); HANDLE CeOpenDatabase(PCEOID, LPWSTR, CEPROPID, graphics/ccc.gifDWORD, HWND); HANDLE CeOpenDatabaseEx(PCEGUID, PCEOID, LPWSTR, graphics/ccc.gifCEPROPID, DWORD, CENOTIFYREQUEST *);

System Configuration and Information RAPI Functions

Table 9.5 lists the Remote API functions for retrieving Pocket PC configuration and system information.

Table 9.5. RAPI System Information Functions

Remote System Configuration and Information Functions

[View full width]
BOOL CeCheckPassword(LPWSTR); BOOL CeCreateProcess(LPCWSTR, LPCWSTR, graphics/ccc.gifLPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, graphics/ccc.gifLPWSTR, LPSTARTUPINFO, LPPROCESS_INFORMATION); BOOL CeGetStoreInformation(LPSTORE_INFORMATION); BOOL CeGetSystemPowerStatusEx( graphics/ccc.gifPSYSTEM_POWER_STATUS_EX, BOOL); BOOL CeGetVersionEx(LPCEOSVERSIONINFO); DWORD CeGetLastError(void); int CeGetDesktopDeviceCaps(int); int CeGetSystemMetrics(int); VOID CeGetSystemInfo(LPSYSTEM_INFO); VOID CeGlobalMemoryStatus(LPMEMORYSTATUS);

Shell RAPI Functions

Table 9.6 lists the Remote API shell management functions.

Table 9.6. RAPI Shell Management Functions

Remote Shell Functions

BOOL CeSHGetShortcutTarget(LPWSTR, LPWSTR, int);
DWORD CeGetSpecialFolderPath(int, DWORD, LPWSTR);
DWORD CeGetTempPath(DWORD, LPWSTR);
DWORD CeSHCreateShortcut(LPWSTR, LPWSTR);

Window RAPI Functions

Table 9.7 lists the Remote API window management functions.

Table 9.7. RAPI Window Management Functions

Remote Window Functions

HWND CeGetWindow(HWND, UINT);
int CeGetClassName(HWND, LPWSTR, int);
int CeGetWindowText(HWND, LPWSTR, int);
LONG CeGetWindowLong(HWND, int);

Creating Your Own RAPI Functions

While the Remote API provides a wide breadth of functions that cover most of the basic information you need about your Pocket PC device, there are certainly going to be times when you will need to communicate with your device in a way that isn't supported by the supplied functions. Fortunately, RAPI also provides you with an API that gives you the capability to create and execute your own functions on the Pocket PC.

You can use two different methods to call your own RAPI functions:

  1. Block mode. When you call a remote function through RAPI in block (or synchronous) mode, your desktop application will not be returned control until the function has completed. RAPI will send the command to the device, load the specified DLL into memory, call the function you want to execute, and return any data through RAPI when it is finished.

  2. Stream mode. When calling a remote function in stream (or asynchronous) mode, your application is returned control immediately after making the call. You then need to use an IRAPIStream COM interface (based on IStream) to send data back and forth between the desktop and the remote device. Using stream mode typically provides faster communications with a remote device than block mode.

Both block mode and stream mode use the same function, CeRapiInvoke(), to execute your own user-defined function, and is defined as follows:

HRESULT CeRapiInvoke(LPCWSTR pDllPath, LPCWSTR
   pFunctionName, DWORD cbInput, BYTE *pInput, DWORD *pcbOutput,
   BYTE **ppOutput, IRAPIStream **ppIRAPIStream, DWORD dwReserved);

The first parameter, pDllPath, should point to a null-terminated string that contains the full name and path of the DLL (in Unicode) on the Pocket PC that contains the function you are calling. The next parameter, pFunctionName, is the name of the function that you want RAPI to execute.

The next two parameters are used to set up the data you are going to send to the remote function. The cbInput parameter should specify the number of bytes that are located in the data buffer, which is specified by the pointer to which you set the pInput parameter. Be aware that the buffer you pass in the pInput parameter will be freed by the function you are calling into, so you should make sure that you allocate it by using the LocalAlloc() function.

The pcbOutput and ppOutput parameters are used to receive data from the remote function. The pcbOutput parameter is a pointer to a DWORD value that will be filled in with the number of bytes that are contained in the data buffer to which ppOutput points. Because the remote function (and RAPI) has allocated memory on the desktop for the returned data buffer, you need to remember to call the LocalFree() function to free the memory that is being used by the ppOutput buffer when you are finished using it.

The ppIRAPIStream parameter will determine whether you are using stream mode or block mode to call the remote function. To call your function using block mode, you can simply set this to NULL. To use stream mode, you need to set this parameter to a pointer of an IRAPIStream interface.

The last parameter is reserved, and should be set to 0.

Although you can name your remote function anything you like, you have to make sure that it matches the following prototype:

STDAPI RAPIFunctionName(DWORD cbInput, BYTE *pInput, DWORD
   *pcbOutput, BYTE **ppOutput, IRAPIStream *pIRAPIStream);

The first two parameters, cbInput and pInput, specify the size and data buffer parameters, respectively, that are passed from the desktop call to CeRapiInvoke(). Because RAPI has automatically allocated data for pInput, your function needs to call LocalFree() on the buffer that is being passed in so that memory can be properly managed.

The next two parameters, pcbOutput and ppOutput, are used to allocate a buffer, set the size of it, and return data to the desktop. The desktop application calling your remote function is responsible for freeing any data you allocate for the output buffer.

The last parameter, pIRAPIStream, will point to an IRAPIStream object that RAPI has allocated if you are communicating with your desktop counterpart in stream mode.

Now that you know how to call your own remote functions using CeRapiInvoke(), let's take a look at what's actually involved with writing a remote function that returns information from the Subscriber Identity Module (SIM) phonebook on a Pocket PC Phone Edition device (see Chapter 8 for more information about using the SIM Manager APIs).

Using Block Mode

Calling your own functions using RAPI's block mode interface is extremely straightforward. All you need to do is create a DLL for the Pocket PC device that contains the functions you need (following the prototype shown above). A desktop application can then use CeRapiInvoke() to call your function (be sure to pass in NULL for the ppIRAPIStream parameter). Essentially, this operates in the same fashion as all of the standard RAPI function calls.

Let's take a look at how you can create a RAPI function to read a specific phonebook entry by using the SimReadPhonebookEntry() function on a Pocket PC Phone Edition device.

The code for the DLL that you will be putting on your Pocket PC Phone Edition device would look like the following:

#include <windows.h>
#include <Simmgr.h>

// Exported function prototypes
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) INT MyCeGetPhonebookEntry(DWORD,
   BYTE *, DWORD *, BYTE **, PVOID);
#ifdef __cplusplus
}
#endif

// Standard DLL entry point
BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason,
   LPVOID lpvReserved)
{
   return TRUE;
}

// Remote Block Function
int MyCeGetPhonebookEntry(DWORD cbInput, BYTE *pInput,
   DWORD *pcbOutput, BYTE **ppOutput, PVOID *pVoid)
{
   // Check to see if we have an input buffer
   if(!pInput || sizeof(cbInput)>4)
      return -1;

   // Get the phone entry that was requested
   DWORD dwEntry = 0;
   memcpy(&dwEntry, pInput, cbInput);

   // Free up the buffer that was passed in
   LocalFree(pInput);

// Initialize the SIM
HRESULT hr = S_OK;
HSIM hSim = NULL;

if(FAILED(SimInitialize(0, NULL, 0, &hSim)))
   return -1;

// Get the data from the SIM
SIMPHONEBOOKENTRY simEntry;

memset(&simEntry, 0, sizeof(SIMPHONEBOOKENTRY));
simEntry.cbSize = sizeof(SIMPHONEBOOKENTRY);

hr = SimReadPhonebookEntry(hSim, SIM_PBSTORAGE_SIM,
   dwEntry, &simEntry);

SimDeinitialize(hSim);

// If the read failed, just exit
if(FAILED(hr))
   return -1;

// Allocate the return buffer
DWORD dwBufferLength =
   (MAX_LENGTH_PHONEBOOKENTRYTEXT+MAX_LENGTH_ADDRESS+1);
TCHAR tchBuffer[MAX_LENGTH_PHONEBOOKENTRYTEXT+MAX_
   LENGTH_ADDRESS+1];
BYTE *pOutputBuffer = NULL;

pOutputBuffer = (BYTE*)LocalAlloc(LPTR, dwBufferLength);

if(!pOutputBuffer)
   return -1;

// Copy the name and phone to the buffer
wsprintf(tchBuffer, TEXT("%s %s"), simEntry.lpszText,
   simEntry.lpszAddress);

// Convert it to non-unicode
wcstombs((char *)pOutputBuffer, tchBuffer,
   dwBufferLength);

// Set up for the return
*ppOutput = (BYTE *)pOutputBuffer;
  *pcbOutput = dwBufferLength;
  return 1;
}

Your code on the desktop for calling the remote function, MyCeGetPhonebookEntry(), in block mode is as follows:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <rapi.h>

// Entry point for a console app on the desktop
int wmain()

{

   HRESULT hr = S_OK;

   // Initalize RAPI
   hr = CeRapiInit();
   if(FAILED(hr)) {
      printf("Could not initialize RAPI\r\n");
      return -1;
   }

   // Set up stuff to send over
   DWORD dwSIMEntry = 1;
   DWORD dwSize = sizeof(dwSIMEntry);
   DWORD dwOutputSize = 0;
   BYTE *pOutput = NULL;
   BYTE *pInput = NULL;

   pInput = (BYTE *)LocalAlloc(LPTR, dwSize);
   memcpy(pInput, &dwSIMEntry, dwSize);

   // Call into the Remote API function
   hr = CeRapiInvoke(TEXT("myrapice"),
      TEXT("MyCeGetPhonebookEntry"), dwSize,
      pInput, &dwOutputSize, &pOutput, NULL, 0);

   if(FAILED(hr)) {
      printf("Could not get SIM information!\r\n");
      CeRapiUninit();
      return -1;
   }

   if(pOutput) {
      printf((char *)pOutput);
      LocalFree(pOutput);
   }

   CeRapiUninit();
   return 0;
}

In order to call this function in block mode from the desktop, you need to first allocate a buffer that will be used to pass information to the remote function. In this case, you want to pass in an argument that indicates which entry number from the SIM phonebook you are interested in. Once you have allocated a DWORD, you set it with the entry number you would like, and call the CeRapiInvoke() function. When the call returns, the output buffer will be filled in with a null-terminated string that contains the name and phone number of the entry. You must remember to call LocalFree() on the returned buffer in order to properly free the allocated memory.

Using Stream Mode

Although using the Remote API's stream mode to communicate with a device might seem a bit more complicated because it uses a COM object to send and receive data, using it is actually fairly straightforward, and the benefits outweigh the additional code. Not only will you have control returned to your application immediately after calling CeRapiInvoke(), using stream mode also enables you to more precisely control the flow of data between the desktop and a device.

To use stream mode, all you need to do is call the CeRapiInvoke() function and pass in a pointer to an IRAPIStream object that has been initialized and set to NULL for the ppIRAPIStream parameter, as shown in the following example:

IRAPIStream *pIRAPIStream = NULL;
DWORD dwOutputSize = 0;
BYTE *pOutput = NULL;

// Initalize the RAPI Stream interface
CoInitialize(NULL);

// Call into the Remote API function
hr = CeRapiInvoke(TEXT("myrapice"), TEXT("MyCeSimDir"),
   0, NULL, &dwOutputSize, &pOutput, &pIRAPIStream, 0);

As you can see, what makes using stream mode easy is that RAPI itself implements the IRAPIStream object?you only need to talk to the object to send and receive data.

The IRAPIStream interface is based on the standard IStream, which generically supports sending and receiving data from other stream objects. Only two additional methods have been added to the IRAPIStream interface, and they enable you to get and set timeout values for your communications stream. They are defined as follows:

HRESULT SetRapiStat(RAPISTREAMFLAG Flag, DWORD dwValue);
HRESULT GetRapiStat(RAPISTREAMFLAG Flag, DWORD *pdwValue);

For both setting and getting the timeout value, the first parameter must be set to STREAM_TIMEOUT_READ. If you are setting the timeout value, you should set dwValue to the amount of time you want to elapse before the communications stream times out. If you are getting the value, then you need to pass in only a pointer to a DWORD variable that will receive the set timeout.

When calling a remote function using stream mode, you can still use CeRapiInvoke() to pass in some initial data using its cbInput and pInput parameters. However, once the initial call has been made, it is recommended that you use the stream object's Read() and Write() methods to pass data back and forth.

Let's take a look at what would be involved if you wanted to use RAPI's stream mode to get a directory of phonebook entries from the SIM card on a Pocket PC Phone Edition device.

The code for the DLL that contains the MyCeSimDir() command you will be putting on your Pocket PC Phone Edition device would look like the following:

#include <windows.h>
#include <Simmgr.h>
#include <rapi.h>

#ifdef __cplusplus
extern "C" {
#endif

__declspec(dllexport) INT MyCeSimDir(DWORD, BYTE *, DWORD *,
   BYTE **, IRAPIStream *);
#ifdef __cplusplus
}
#endif

BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason,
   LPVOID lpvReserved)
{
   return TRUE;
}

int MyCeSimDir(DWORD cbInput, BYTE *pInput,
   DWORD *pcbOutput, BYTE **ppOutput, IRAPIStream *pIRAPIStream)
{
   // This sample will return all entries, so ignore the
   // buffer that was passed in
   if(pInput)
      LocalFree(pInput);

   // Make sure that the IRAPIStream is valid
   if(!pIRAPIStream)
      return -1;

   // Initialize the SIM
   HRESULT hr = S_OK;
   HSIM hSim = NULL;

   if(FAILED(SimInitialize(0, NULL, 0, &hSim)))
      return -1;

   // Get the total number of entries
   DWORD dwUsed = 0, dwTotal = 0;

   hr = SimGetPhonebookStatus(hSim, SIM_PBSTORAGE_SIM,
      &dwUsed, &dwTotal);

   if(FAILED(hr)) {
      SimDeinitialize(hSim);
      return -1;
   }

   // Make sure there's something to return
   if(dwUsed == 0) {
      SimDeinitialize(hSim);
      return -1;
   }

   // Get each entry
   // We'll pass them back as a Name [Phonenumber] string.
   SIMPHONEBOOKENTRY simEntry;
   TCHAR tchEntry[MAX_LENGTH_PHONEBOOKENTRYTEXT+
      MAX_LENGTH_ADDRESS+1];
   DWORD dwLength = 0, dwBytesWritten = 0;

   for(DWORD dwEntry = 0; dwEntry<dwUsed; dwEntry++) {
      memset(&simEntry, 0, sizeof(SIMPHONEBOOKENTRY));
      simEntry.cbSize = sizeof(SIMPHONEBOOKENTRY);

      hr = SimReadPhonebookEntry(hSim, SIM_PBSTORAGE_SIM,
         dwEntry, &simEntry);
      if(FAILED(hr))
         break;

      // Build the string
      memset(tchEntry, 0,
         MAX_LENGTH_PHONEBOOKENTRYTEXT+MAX_LENGTH_ADDRESS+1);
      wsprintf(tchEntry, TEXT("%s [%s]"), simEntry.lpszText,
         simEntry.lpszAddress);

      // Write it to the stream
      // First, send the length
      dwLength = (lstrlen(tchEntry)+1)*sizeof(TCHAR);
      hr = pIRAPIStream->Write(&dwLength, sizeof(dwLength),
         &dwBytesWritten);

      // Next, send the buffer
      hr = pIRAPIStream->Write(tchEntry, dwLength,
         &dwBytesWritten);
      if(FAILED(hr))
         break;
}

// Clean up
SimDeinitialize(hSim);

   // Send that there's "No more data"
   DWORD dwFinished = 0xFFFFFFFF;
   hr = pIRAPIStream->Write(&dwFinished, sizeof(dwFinished),
      &dwBytesWritten);

   // Release the stream interface
   pIRAPIStream->Release();
   return 0;
}

On the desktop side, you can create a simple console application that will call into your remote function using stream mode to output the contents of the SIM phonebook:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <rapi.h>

#define BUFFER_SIZE 1024

// Entry point for a console app on the desktop
int wmain()
{
   HRESULT hr = S_OK;

   // Initalize RAPI
   hr = CeRapiInit();
   if(FAILED(hr)) {
      printf("Could not initialize RAPI\r\n");
      return -1;
   }

   // Set up stuff to send over
   IRAPIStream *pIRAPIStream = NULL;
   DWORD dwOutputSize = 0;
   BYTE *pOutput = NULL;

   // Initalize the RAPI Stream interface
   CoInitialize(NULL);

   // Call into the Remote API function
   hr = CeRapiInvoke(TEXT("myrapice"), TEXT("MyCeSimDir"),
      0, NULL, &dwOutputSize, &pOutput, &pIRAPIStream, 0);

   if(FAILED(hr)) {
      DWORD dwError = CeRapiGetError();
      if(dwError == 0)
         dwError = CeGetLastError();

      printf("Could not get SIM information! Error: %u\r\n",
         dwError);
      CeRapiUninit();
      return -1;
   }

   // Read from the remote stream
   TCHAR tchEntry[BUFFER_SIZE+1];
   DWORD dwSize = BUFFER_SIZE;
   DWORD dwBytesRead = 0;
   DWORD dwLength = 0;

   do {
      // Get the length. If it is 0xFFFFFFFF, we're done.
      hr = pIRAPIStream->Read(&dwLength, sizeof(dwLength),
         &dwBytesRead);

      if(FAILED(hr) || dwLength == 0xFFFFFFFF)
         break;

      // Ok, read the buffer
      hr = pIRAPIStream->Read(tchEntry, dwLength,
         &dwBytesRead);

      // Do something with the output here
      // ....

      // Clean up for next entry
      dwSize = BUFFER_SIZE;
      memset(tchEntry, 0, BUFFER_SIZE+1);
      dwBytesRead = 0;
   } while(1);

   // Clean up
   pIRAPIStream->Release();
   CeRapiUninit();
   return 0;
}