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
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 RAPIBefore 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
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
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 FunctionsTable 9.2 lists the Remote API functions for working with the Pocket PC file system. Table 9.2. RAPI File System Functions
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_ROMMODULESFAF_NAME, &dwFileCount,
(LPLPCE_FIND_DATA)&lpvFindData) == FALSE) {
DWORD dwError = CeRapiGetError();
TCHAR tchError[128] = TEXT("
Registry RAPI FunctionsTable 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
Database RAPI FunctionsTable 9.4 lists the Remote API functions for working with Pocket PC databases. Table 9.4. RAPI Database Functions
System Configuration and Information RAPI FunctionsTable 9.5 lists the Remote API functions for retrieving Pocket PC configuration and system information. Table 9.5. RAPI System Information Functions
Shell RAPI FunctionsTable 9.6 lists the Remote API shell management functions. Table 9.6. RAPI Shell Management Functions
Window RAPI FunctionsTable 9.7 lists the Remote API window management functions. Table 9.7. RAPI Window Management Functions
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
You can use two different
Both block mode and stream mode use the same function,
CeRapiInvoke()
, to execute your own
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-
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
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 ModeCalling 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
#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
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;
}
|