The Windows CE Remote API
The remote API (RAPI) allows applications on one machine to call functions on another machine. Windows CE supports essentially a one-way RAPI; applications on the PC can call functions on a connected Windows CE system. In the language of RAPI, the Windows CE device is the RAPI server while the PC is the RAPI client. The application runs on the client, the PC, which in turn calls functions that are executed on the server, the Windows CE device.
RAPI Overview
RAPI under Windows CE is designed so that PC applications can manage the Windows CE device remotely. The exported functions deal with the file system, registry, and databases, as well as functions for querying the system configuration. Although most RAPI functions are duplicates of functions in the Windows CE API, a few functions extend the API. You use these functions mainly for initializing the RAPI subsystem and enhancing performance of the communication link by compressing iterative operations into one RAPI call.The RAPI functions are listed in the Windows CE API reference but are called by PC applications—not by Windows CE applications. The RAPI functions are prefixed with a Ce in the function name to differentiate them from their Windows CE–side counterparts; for example, the function GetStoreInformation in Windows CE is called CeGetStoreInformation in the RAPI version of the function. Unfortunately, some APIs in Windows CE, such as the database API, also have functions prefixed with Ce. In these cases, both the CE function (for example, CeCreateDatabase) and the RAPI function (again, CeCreateDatabase) have the same name. The linker isn't confused in this case because a Windows CE application won't be calling the RAPI function and a PC-based program can't call the database function except through the RAPI interface.These Windows CE RAPI functions work for Windows 95/98/Me as well as Windows NT/2000/XP, but because they're Win32 functions, applications developed for the Win16 API can't use the Windows CE RAPI functions. The RAPI functions can be called from either a Windows-based application or a Win32-console application. All you have to do to use the RAPI functions is include the

Essentially, RAPI is a remote procedure call. It communicates a PC application's request to invoke a function and returns the results of that function. Because the RAPI layer is simple on the Windows CE side, all strings used in RAPI functions must be in Unicode regardless of whether the PC-based application calling the RAPI function uses the Unicode format.
Dealing with Different Versions of RAPI
The problem of versioning has always been an issue with redistributable DLLs under Windows. RAPI.DLL, the DLL on the PC that handles the RAPI API, is distributed with the ActiveSync software that comes with a Smartphone, a Pocket PC, or other PC-companion Windows CE devices. Trouble arises because the RAPI API has been extended over time as the Windows CE functions have expanded; you have to be aware that the RAPI DLL you load on a machine might not be the most up-to-date RAPI DLL. Older RAPI DLLs don't have all the exported functions that the newest RAPI DLL has.This isn't as much of a problem as it used to be, however. The set of RAPI functions hasn't changed from the old H/PC Pro days up to the current Pocket PC products. However, you should always be aware that new versions of ActiveSync might provide RAPI functions that aren't available on older installations.On the other hand, just because you're using the latest RAPI DLL doesn't mean that the Windows CE system on the other end of the RAPI connection supports all the functions that the RAPI DLL supports. An old H/PC running Windows CE 2.0 won't support the extended database API supported by the current Windows CE systems, no matter what RAPI DLL you're using on the PC.The best way to solve the problem of multiple versions of RAPI.DLL is to program defensively. Instead of loading the RAPI DLL implicitly by specifying an import library and directly calling the RAPI functions, you might want to load the RAPI DLL explicitly with a call to LoadLibrary. You can then access the exported functions by calling GetProcAddress for each function and then calling the pointer to that function.The problem of different versions of Windows CE has a much easier solution. Just be sure to call CeGetVersionEx to query the version of Windows CE on the remote device. This gives you a good idea of what the device capabilities of that device are. If the remote device has a newer version of Windows CE than RAPI.dll, you might want to inform the user of the version issue and suggest an upgrade of the synchronization software on the PC.
Initializing RAPI
Before you can call any of the RAPI functions, you must first initialize the RAPI library with a call to either CeRapiInit or CeRapiInitEx. The difference between the two functions is that CeRapiInit blocks, waiting on a successful connection with a Windows CE device, while CeRapiInitEx doesn't block. ActiveSync has established a connection between the PC and the device for these functions to succeed.The first initialization function is prototyped as
HRESULT CeRapiInit (void);
This function has no parameters. When the function is called, Windows looks for an established link to a Windows CE device. If one doesn't exist, the function blocks until one is established or another thread in your application calls CeRapiUninit, which is generally called to clean up after a RAPI session. The return value is either 0, indicating that a RAPI session has been established, or the constant E_FAIL, indicating an error. In this case, you can call GetLastError to diagnose the problem.Unfortunately CeRapiInit blocks, sometimes, for an extended period of time. To avoid this, you can use the other initialization function,
HRESULT CeRapiInitEx (RAPIINIT* pRapiInit);
The only parameter is a pointer to a RAPIINIT structure defined as
typedef struct _RAPIINIT {
DWORD cbSize;
HANDLE heRapiInit;
HANDLE hrRapiInit;
} RAPIINIT;
The cbSize field must be filled in before the call is made to CeRapiInitEx. After the size field has been initialized, you call CeRapiInitEx and the function returns without blocking. It fills in the second of the three fields, heRapiInit, with the handle to an event object that will be signaled when the RAPI connection is initialized. You can use WaitForSingleObject to have a thread block on this event to determine when the connection is finally established. When the event is signaled, the final field in the structure, hrRapiInit, is filled with the return code from the initialization. This value can be 0 if the connection was successful or E_FAIL if the connection wasn't made for some reason.
Handling RAPI Errors
When you're dealing with the extra RAPI layer between the caller and the execution of the function, a problem arises when an error occurs: did the error occur because the function failed or because of an error in the RAPI connection? RAPI functions return error codes indicating success or failure of the function. If a function fails, you can use the following two functions to isolate the cause of the error:
HRESULT CeRapiGetError (void);
and
DWORD CeGetLastError (void);
The difference between these two functions is that CeRapiGetError returns an error code for failures due to the network or other RAPI-layer reasons. On the other hand, CeGetLastError is the RAPI counterpart to GetLastError; it returns the extended error for a failed function on the Windows CE device. So, if a function fails, call CeRapiGetError to determine whether an error occurred in the RAPI layer. If CeRapiGetError returns 0, the error occurred in the original function on the CE device. In this case, a call to CeGetLastError returns the extended error for the failure on the device.Here's one last general function, used to free buffers that are returned by some of the RAPI functions. This function is
HRESULT CeRapiFreeBuffer (LPVOID Buffer);
The only parameter is the pointer to the buffer you want to free. The function returns S_OK when successful and E_FAIL if not. Throughout the explanation of RAPI functions, I'll mention those places where you need to use CeRapiFreeBuffer. In general, though, you use this function anywhere a RAPI function returns a buffer that it allocated for you.
Ending a RAPI Session
When you have finished making all the RAPI calls necessary, you should clean up by calling
HRESULT CeRapiUninit (void);
This function gracefully closes down the RAPI communication with the remote device. CeRapiUninit returns E_FAIL if a RAPI session hasn't been initialized.
Predefined RAPI Functions
As I mentioned in the beginning of this chapter, the RAPI services include a number of predefined RAPI functions that duplicate Windows CE functions on the PC side of the connection. So, for example, just as GetStoreInformation returns the size and free space of the object store to a Windows CE program, CeGetStoreInformation returns that same information about a connected Windows CE device to a PC-based application. The functions are divided into a number of groups that I'll talk about in the following pages. Since the actions of the functions are identical to their Windows CE–based counterparts, I won't go into the details of each function. Instead, although I'll list every RAPI function, I'll explain at length only the functions that are unique to RAPI.
RAPI System Information Functions
The RAPI database functions are shown in the following list. I've previously described most of the Windows CE counterparts to these functions, shown, with the exception of CeCheckPassword and CeRapiInvoke. The CeCheckPassword function, as well as its Windows CE counterpart CheckPassword, compares a string to the current system password. If the strings match, the function returns TRUE. The comparison is case specific. Another function you might not recognize is CeGetDesktopDeviceCaps. This is the RAPI equivalent of GetDeviceCaps on the Windows CE side.
System Information Functions | |
---|---|
CeGetVersionEx | CeGetDesktopDeviceCaps |
CeGlobalMemoryStatus | CeGetSystemInfo |
CeGetSystemPowerStatusEx | CeCheckPassword |
CeGetStoreInformation | CeCreateProcess |
CeGetSystemMetrics | CeRapiInvoke |
RAPI File and Directory Management Functions
The following list shows the RAPI file management functions, illustrating that almost any file function available to a Windows CE application is also available to a PC-based program.
File and Directory Management Functions | |
---|---|
CeFindAllFiles | CeSetFilePointer |
CeFindFirstFile | CeSetEndOfFile |
CeFindNextFile | CeCreateDirectory |
CeFindClose | CeRemoveDirectory |
CeGetFileAttributes | CeMoveFile |
CeSetFileAttributes | CeCopyFile |
CeCreateFile | CeDeleteFile |
CeReadFile | CeGetFileSize |
CeWriteFile | CeGetFileTime |
CeCloseHandle | CeSetFileTime |
Here's a new function, CeFindAllFiles, that's not even available to a Windows CE application. This function is prototyped as
BOOL CeFindAllFiles (LPCWSTR szPath, DWORD dwFlags,
LPDWORD lpdwFoundCount,
LPLPCE_FIND_DATA ppFindDataArray);
CeFindAllFiles is designed to enhance performance by returning all the files of a given directory with one call rather than having to make repeated RAPI calls using CeFindFirstFile and CeFindNextFile. The first parameter is the search string. This string must be specified in Unicode, so if you're not creating a Unicode application, the TEXT macro won't work because the TEXT macro produces char strings for non-Unicode applications. In ANSI-standard C++ compilers, prefixing the string with an L before the quoted string as in L"\\*.*" produces a proper Unicode for the function even in a non-Unicode application. For string conversion, you can use the WideCharToMultiByte and MultiByteToWideChar library functions to convert Unicode and ANSI strings into one another.The second parameter of the CeFindAllFiles function, dwFlags, defines the scope of the search and what data is returned. The first set of flags can be one or more of the following:
FAF_ATTRIB_CHILDRENReturns only directories that have child items
FAF_ATTRIB_NO_HIDDENDoesn't report hidden files or directories
FAF_FOLDERS_ONLYReturns only folders in the directory
FAF_NO_HIDDEN_SYS_ROMMODULESDoesn't report ROM-based system files
The second set of flags defines what data is returned by the CeFindAllFiles function. These flags can be one or more of the following:
FAF_ATTRIBUTESReturns file attributes
FAF_CREATION_TIMEReturns file creation time
FAF_LASTACCESS_TIMEReturns file last access time
FAF_LASTWRITE_TIMEReturns file last write time
FAF_SIZE_HIGHReturns upper 32 bits of file size
FAF_SIZE_LOWReturns lower 32 bits of file size
FAF_OIDReturns the object identifier (OID) for the file
FAF_NAMEReturns the filename
Just because the flags are listed here doesn't mean you can find a good use for them. For example, the FAF_SIZE_HIGH flag is overkill, considering that few files on a Windows CE device are going to be larger than 4 GB. The file time flags are also limited by the support of the underlying file system. For example, the Windows CE object store tracks only the last access time and reports it in all file time fields.There also appears to be a bug with the FAF_ATTRIB_CHILDREN flag. This valuable flag allows you to know when a directory contains subdirectories without your having to make an explicit call to that directory to find out. The flag seems to work only if the filename specification—the string to the right of the last directory separator backslash (\)—contains only one character. For example, the file specification
\\windows\*
works with FAF_ATTRIB_CHILDREN, whereas
\\windows\*.*
returns the same file list but the flag FILE_ATTRIBUTE_HAS_CHILDREN isn't set for directories that have subdirectories.The third parameter of CeFindAllFiles should point to a DWORD value that will receive the number of files and directories found by the call. The final parameter, ppFindDataArray, should point to a variable of type LPCE_FIND_DATA, which is a pointer to an array of CE_FIND_DATA structures. When CeFindAllFiles returns, this variable will point to an array of CE_FIND_DATA structures that contain the requested data for each of the files found by the function. The CE_FIND_DATA structure is defined as
typedef struct _CE_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwOID;
WCHAR cFileName[MAX_PATH];
} CE_FIND_DATA;
The fields of CE_FIND_DATA look familiar to us by now. The only interesting field is the dwOID field that allows a PC-based application to receive the OID of a Windows CE file. This can be used with CeGetOidGetInfo to query more information about the file or directory. The flags in the dwFileAttributes field relate to Windows CE file attributes even though your application is running on a PC. This means, for example, that the FILE_ATTRIBUTE_TEMPORARY flag indicates an external storage device like a PC Card. Also, attribute flags are defined for execute-in-place ROM files. The additional attribute flag, FILE_ATTRIBUTE_HAS_CHILDREN, is defined to indicate that the directory contains child directories.The buffer returned by CeFindAllFiles is originally allocated by the RAPI.DLL. Once you have finished with the buffer, you must call CeRapiFreeBuffer to free the buffer.
RAPI Database Management Functions
The RAPI database management functions are shown in the following list. As you can see, these functions mimic the extensive database API found in Windows CE. RAPI has not been extended to support the newer database APIs supported by Windows CE .NET. However, the older functions provide enough functionality to read databases, even if the databases were created with the newer functions.
Database Management Functions | |
---|---|
CeCreateDatabase | CeOpenDatabaseEx |
CeCreateDatabaseEx | CeReadRecordProps |
CeDeleteDatabase | CeReadRecordPropsEx |
CeDeleteDatabaseEx | CeSeekDatabase |
CeDeleteRecord | CeSetDatabaseInfo |
CeFindFirstDatabase | CeSetDatabaseInfoEx |
CeFindFirstDatabaseEx | CeWriteRecordProps |
CeFindNextDatabase | CeMountDBVol |
CeFindNextDatabaseEx | CeUnmountDBVol |
CeOidGetInfo | CeEnumDBVolumes |
CeOidGetInfoEx | CeFindAllDatabases |
CeOpenDatabase |
All but one of the database functions has a Windows CE counterpart. The only new function is CeFindAllDatabases. Like CeFindAllFiles, this function is designed as a performance enhancement so that applications can query all the databases on the system without having to iterate using the CeFindFirstDatabase and CeFindNextDatabase functions. The function is prototyped as
BOOL CeFindAllDatabases (DWORD dwDbaseType, WORD wFlags,
LPWORD cFindData,
LPLPCEDB_FIND_DATA ppFindData);
The first parameter is the database type value, or 0, if you want to return all databases. The wFlags parameter can contain one or more of the following flags, which define what data is returned by the function.
FAD_OIDReturns the database OID
FAD_FLAGSReturns the dwFlags field of the DbInfo structure
FAD_NAMEReturns the name of the database
FAD_TYPEReturns the type of the database
FAD_NUM_RECORDSReturns the number of records in the database
FAD_NUM_SORT_ORDERReturns the number of sort orders
FAD_SORT_SPECReturns the sort order specs for the database
The cFindData parameter should point to a WORD variable that receives the number of databases found. The last parameter should be the address of a pointer to an array of CEDB_FIND_DATA structures. As with the CeFindAllFiles function, CeFindAllDatabases returns the information about the databases found in an array and sets the ppFindData parameter to point to this array. The CEDB_FIND_DATA structure is defined as
struct CEDB_FIND_DATA {
CEOID OidDb;
CEDBASEINFO DbInfo;
};
The structure contains the OID for a database followed by a CEDBASEINFO structure. I described this structure in Chapter 9, but I'll repeat it here so that you can see what information can be queried by CeFindAllDatabases.
typedef struct _CEDBASEINFO {
DWORD dwFlags;
WCHAR szDbaseName[CEDB_MAXDBASENAMELEN];
DWORD dwDbaseType;
WORD wNumRecords;
WORD wNumSortOrder;
DWORD dwSize;
FILETIME ftLastModified;
SORTORDERSPEC rgSortSpecs[CEDB_MAXSORTORDER];
} CEDBASEINFO;
As with CeFindAllFiles, you must free the buffer returned by CeFindAllDatabases with a call to CeRapiFreeBuffer.
One other function in this section requires a call to CeRapiFreeBuffer. The function CeReadRecordProps, which returns properties for a database record, allocates the buffer where the data is returned. If you call the RAPI version function, you need to call CeRapiFreeBuffer to free the returned buffer.
RAPI Registry Management Functions
The RAPI functions for managing the registry are shown in the following list. The functions work identically to their Windows CE counterparts. But remember that all strings, whether they are specifying keys and values or strings returned by the functions, are in Unicode.
Registry Management Functions | |
---|---|
CeRegOpenKeyEx | CeRegEnumValue |
CeRegEnumKeyEx | CeRegDeleteValue |
CeRegCreateKeyEx | CeRegQueryInfoKey |
CeRegCloseKey | CeRegQueryValueEx |
CeRegDeleteKey | CeRegSetValueEx |
RAPI Shell Management Functions
The RAPI shell management functions are shown in the next list. Although I'll cover the Windows CE–equivalent functions in the next chapter, you can see that the self-describing names of the functions pretty well document themselves. The CeSHCreateShortcut and CeSHGetShortcutTarget functions allow you to create and query shortcuts. The other two functions, CeGetTempPath and CeGetSpecialFolderPath, let you query the locations of some of the special-purpose directories on the Windows CE system, such as the programs directory and the recycle bin.
Shell Management Functions | |
---|---|
CeSHCreateShortcut | CeGetTempPath |
CeSHGetShortcutTarget | CeGetSpecialFolderPath |
RAPI Window Management Functions
The final set of predefined RAPI functions allows a desktop application to manage the windows on the Windows CE desktop. These functions are shown in the following list. The functions work similarly to their Windows CE functions. The CeGetWindow function allows a PC-based program to query the windows and child windows on the desktop while the other functions allow you to query the values in the window structures.
Window Management Functions | |
---|---|
CeGetWindow | CeGetWindowText |
CeGetWindowLong | CeGetClassName |
The RapiDir Example Program
The RapiDir example is a PC-console application that displays the contents of a directory on an attached Windows CE device. The output of RapiDir, shown in Figure 15-1, resembles the output of the standard DIR command from a PC command line. RapiDir is passed one argument, the directory specification of the directory on the Windows CE machine. The directory specification can take wildcard arguments such as *.exe if you want, but the program isn't completely robust in parsing the directory specification. Perfect parsing of a directory string isn't the goal of RapiDir—demonstrating RAPI is.

Figure 15-1: The output of RapiDir
The source code for RapiDir is shown in Listing 15-1. The program is a command line application and therefore doesn't need the message loop or any of the other structure seen in a Windows-based application. Instead the WinMain function is replaced by our old C friend, main.
Remember that RapiDir is a standard Win32 desktop application. It won't even compile for Windows CE. On the other hand, you have the freedom to use the copious amounts of RAM and disk space provided by the comparatively huge desktop PC. When you build RapiDir, you'll need to add

RapiDir.cpp
//======================================================================
// RapiDir - Returns the contents of a directory on a Windows CE system.
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <stdio.h>
#include <rapi.h> // RAPI includes
//======================================================================
// main - Program entry point
//
int main (int argc, char **argv) {
RAPIINIT ri;
char szSrch[MAX_PATH], *pPtr;
WCHAR szwDir[MAX_PATH];
CE_FIND_DATA *pfd = 0;
DWORD i, cItems, dwTotal = 0;
FILETIME ft;
SYSTEMTIME st;
char ampm = 'a';
int rc;
// Call RapiInitEx to asynchronously start RAPI session.
ri.cbSize = sizeof (ri);
rc = CeRapiInitEx (&ri);
if (rc != NOERROR) {
printf (TEXT ("Rapi Initialization failed\r\n"));
return 0;
}
// Wait 5 seconds for connect.
rc = WaitForSingleObject (ri.heRapiInit, 5000);
if (rc == WAIT_OBJECT_0) {
if (ri.hrRapiInit != NOERROR) {
printf (TEXT ("Rapi Initialization failed.\r\n"));
return 0;
}
} else if (rc == WAIT_TIMEOUT) {
printf (TEXT ("Rapi Initialization timed out.\r\n"));
return 0;
}
// If no argument, assume root directory.
if (argc > 1)
lstrcpy (szSrch, argv[1]);
else
lstrcpy (szSrch, "\\");
// Point to end of name.
pPtr = szSrch + lstrlen (szSrch) - 1;
// Strip any trailing backslash.
if (*pPtr == '\\')
*pPtr = '\0';
// Look for wildcards in filename. pPtr points to string end.
for (i = 0; (pPtr >= szSrch) && (*pPtr != '\\'); pPtr—) {
if ((*pPtr == '*') || (*pPtr == '?'))
i++;
}
// Display dir name first so that on long calls we show we're alive.
if (pPtr >= szSrch) {
char ch;
ch = *pPtr;
*pPtr = '\0';
printf (TEXT ("\r\n Directory of %s\r\n\r\n"), szSrch);
*pPtr = ch;
} else if (i)
printf (TEXT ("\r\n Directory of \\\r\n\r\n"));
else
printf (TEXT ("\r\n Directory of %s\r\n\r\n"), szSrch);
// No wildcards, append *.*
if (i == 0)
lstrcat (szSrch, "\\*.*");
// Convert ANSI string to Unicode.
memset (szwDir, 0, sizeof (szwDir));
mbstowcs (szwDir, szSrch, lstrlen (szSrch) + 1);
// RAPI call
rc = CeFindAllFiles (szwDir, FAF_SIZE_LOW | FAF_NAME |
FAF_ATTRIBUTES | FAF_LASTACCESS_TIME,
&cItems, &pfd);
// Display the results.
if (cItems) {
for (i = 0; i < cItems; i++) {
// Convert file time.
FileTimeToLocalFileTime (&pfd->ftLastAccessTime, &ft);
FileTimeToSystemTime (&ft, &st);
// Adjust for AM/PM.
if (st.wHour == 0)
st.wHour = 12;
else if (st.wHour > 11) {
ampm = 'p';
if (st.wHour > 12)
st.wHour -= 12;
}
printf (TEXT ("%02d/%02d/%02d %02d:%02d%c\t"),
st.wMonth, st.wDay, st.wYear,
st.wHour, st.wMinute, ampm);
// Display dir marker or file size.
if (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
printf (TEXT ("<DIR>\t\t "));
else {
printf (TEXT ("\t%8d "), pfd->nFileSizeLow);
dwTotal += pfd->nFileSizeLow;
}
// Display name, use Cap %S to indicate Unicode.
printf (TEXT ("%S\r\n"), pfd->cFileName);
pfd++;
}
printf (TEXT ("\t%10d File(s)\t%9d bytes\r\n\r\n"),
cItems, dwTotal);
} else
printf (TEXT ("File not Found\r\n\r\n"));
// Clean up by freeing the FindAllFiles buffer.
if (pfd)
CeRapiFreeBuffer (pfd);
// Clean up by uninitializing RAPI.
CeRapiUninit ();
return 0;
}
This single procedure application first calls CeRapiInitEx to initialize the RAPI session. I used the Ex version of the initialization function so that RapiDir can time out and terminate if a connection isn't made within 5 seconds of starting the program. If I'd used CeRapiInit instead, the only way to terminate RapiDir if a remote CE device weren't connected would be a user-unfriendly Ctrl+C key combination.
Once the RAPI session is initialized, a minimal amount of work is done on the single command line argument that's the search string for the directory. Once that work is complete, the string is converted into Unicode and passed to CeFindAllFiles. This RAPI function then returns with an array of CE_FIND_DATA structures that contain the names and requested data of the files and directories found. The data from that array is then displayed using printf statements. The buffer returned by CeFindAllFiles is freed by means of a call to CeRapiFreeBuffer. Finally, the RAPI session is terminated with a call to CeRapiUninit.If you compare the output of RapiDir with the output of the standard DIR command, you notice that RapiDir doesn't display the total bytes free on the disk after the listing of files. Although I could have displayed the total free space for the object store using CeGetStorageInformation, this practice wouldn't work if the user displayed a directory on a PCMCIA card or other external media. Windows CE supports the GetDiskFreeSpaceEx function, but the Windows CE RAPI DLL doesn't expose this function. To get this information, we'll use RAPI's ability to call custom RAPI functions on a Windows CE system.
Custom RAPI Functions
No matter how many functions the RAPI interface supports, you can always think of functions that an application needs but the RAPI interface doesn't give you. Because of this, RAPI provides a method for a PC application to call a user-defined function on the Windows CE device.You can invoke a user-defined RAPI function in one of two ways. The first way is called block mode. In block mode, you make a call to the RAPI remote invocation function, the function makes the call to a specified function in a specified DLL, the DLL function does its thing and returns, and the RAPI function then returns to the calling PC program with the output. The second method is called stream mode. In this mode, the RAPI call to the function returns immediately, but a connection is maintained between the calling PC application and the Windows CE DLL–based function. This method allows information to be fed back to the PC on an ongoing basis.
Using RAPI to Call a Custom Function
The RAPI function that lets you call a generic function on the Windows CE device is CeRapiInvoke, which is prototyped as
HRESULT CeRapiInvoke (LPCWSTR pDllPath, LPCWSTR pFunctionName,
DWORD cbInput, BYTE *pInput, DWORD *pcbOutput,
BYTE **ppOutput, IRAPIStream **ppIRAPIStream,
DWORD dwReserved);
The first parameter to CeRapiInvoke is the name of the DLL on the Windows CE device that contains the function you want to call. The name must be in Unicode but can include a path. If no path is specified, the DLL is assumed to be in the \windows directory on the device. The second parameter is the name of the function to be called. The function name must be in Unicode and is case specific.The next two parameters, cbInput and pInput, should be set to the buffer containing the data and the size of that data to be sent to the Windows CE–based function. The input buffer should be allocated in the local heap of the application. When you call CeRapiInvoke, this buffer will be freed by the function. The pcbOutput and ppOutput parameters are both pointers—the first a pointer to a DWORD that receives the size of the data returned and the second a pointer to a PBYTE variable that receives the pointer to the buffer containing the data returned by the Windows CE function. The buffer returned by CeRapiInvoke is allocated by the function in your local heap. You're responsible for freeing this buffer. I'll describe the next-to-last parameter, ppIRAPIStream, later.To use CeRapiInvoke in block mode, all you do is specify the DLL containing the function you want to call, the name of the function, and the data, and then make the call. When CeRapiInvoke returns, the data from the CE-based function will be sitting in the buffer pointed to by your output pointer variable.
Writing a RAPI Server Function
You can't call just any function in a Windows CE DLL. The structure of the Windows CE function must conform to the following function prototype:
STDAPI INT FuncName (DWORD cbInput, BYTE *pInput, DWORD *pcbOutput,
BYTE **ppOutput, IRAPIStream *pIRAPIStream);
As you can see, the parameters closely match those of CeRapiInvoke. As with CeRapiInvoke, I'll talk about the parameter pIRAPIStream later.Listing 15-2 contains the source code for a very simple block-mode RAPI server. This is a DLL and therefore has a different structure from the application files previously used in the book. The primary difference is that the DLL contains a DllMain routine instead of WinMain. The DllMain routine is called by Windows whenever a DLL is loaded or freed by a process or thread. In our case, we don't need to take any action other than to return TRUE indicating all is well.You should be careful to make the name of your RAPI server DLL eight characters or less. Current implementations of the RAPI DLL will fail to find server DLLs with names not in the old 8.3 format.
Listing 15-2: RapiServ.cpp, a simple block-mode RAPI server DLL
RapiServ.cpp
//====================================================================
// RapiServ - A RAPI block mode server DLL
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
#include <windows.h> // For all that Windows stuff
// The following ensures that the function will be exported from the DLL
// and that any C++ compilers used won't mangle the name.
#ifdef __cplusplus
extern "C" {
#endif
__declspec (dllexport) INT RAPIGetDiskSize (DWORD, BYTE *, DWORD *,
BYTE **, PVOID);
#ifdef __cplusplus
}
#endif
//====================================================================
// DllMain - DLL initialization entry point
//
BOOL WINAPI DllMain (HANDLE hinstDLL, DWORD dwReason,
LPVOID lpvReserved) {
return TRUE;
}
//====================================================================
// RAPIGetDiskSize - Returns the disk size and free space. Called from
// PC application using RAPI.
//
INT RAPIGetDiskSize (DWORD cbInput, BYTE *pInput, DWORD *pcbOutput,
BYTE **ppOutput, PVOID reserved) {
PDWORD pdwLocal;
LPTSTR pPtr;
DWORD i;
int rc = 0;
ULARGE_INTEGER lnFree, lnTotal;
*pcbOutput = 0; // Zero output bytes for now.
if (!pInput) return –1; // Make sure there is an input buffer.
// See if proper zero-terminated string.
pPtr = (LPTSTR)pInput;
for (i = 0; i < cbInput / 2; i++)
if (!*pPtr++)
break;
// If not zero terminated or if zero length, return error.
if ((i >= cbInput / 2) || (i == 0)) {
LocalFree (pInput);
return -2;
}
// Call the function.
if (GetDiskFreeSpaceEx ((LPTSTR)pInput, NULL, &lnTotal, &lnFree)) {
// Allocate memory for the return buffer.
pdwLocal = (PDWORD) LocalAlloc (LPTR, 2 * sizeof (DWORD));
if (pdwLocal) {
// Copy data from function to output buffer.
pdwLocal[0] = lnTotal.LowPart;
pdwLocal[1] = lnFree.LowPart;
// Specify size and buffer.
*pcbOutput = 2 * sizeof (DWORD);
*ppOutput = (PBYTE)pdwLocal;
} else
rc = GetLastError();
} else
rc = GetLastError();
// The function is responsible for freeing the input buffer.
LocalFree (pInput);
return rc;
}
The unusual prefix before the function prototype for RAPIGetDiskSize,
__declspec (dllexport) INT RAPIGetDiskSize…
tells the linker to export the function listed so that external modules can call the function directly. This declaration is a shortcut for the old way of defining exports in a separate function definition (DEF) file. While this shortcut is convenient, sometimes you still need to fall back on a DEF file. The __declspec line is couched in an extern C bracket. This technique ensures that if the file is compiled with the C++ language extensions enabled, the function name won't be mangled by the compiler. This is an important assurance because we need to call this function by its real name, not by some fabricated name created by a compiler.
The function of RapiServ is to make available that GetDiskFreeSpaceEx function we needed in the RapiDir example application. The server function, RAPIGetDiskSize, has the same prototype I described earlier. The input buffer is used to pass a directory name to the DLL while the output buffer returns the total disk space and the free disk space for the directory passed. The format of the input and output buffers is totally up to you. However, the function must free the input buffer with LocalFree and the output buffer should be allocated using LocalAlloc so that the RAPI library can free it after it has been used. The value returned by RAPIGetDiskSize is the value that's returned by the CeRapiInvoke function to the PC-based application.On the PC side, a call to a block-mode RAPI server function looks like the following.
//-----------------------------------------------------------------------
// MyCeGetDiskFreeSpaceEx - Homegrown implementation of a RAPI
// GetDiskFreeSpace function
//
BOOL MyCeGetDiskFreeSpaceEx (LPWSTR pszDir, PDWORD pdwTotal,
PDWORD pdwFree) {
HRESULT hr;
DWORD dwIn, dwOut;
LPBYTE pInput;
LPWSTR pPtr;
PDWORD pOut;
BOOL bRC = FALSE;
// Get length of Unicode string.
for (dwIn = 2, pPtr = pszDir; *pPtr++; dwIn+=2);
// Allocate buffer for input.
pInput = LocalAlloc (LPTR, dwIn);
if (!pInput)
return FALSE;
// Copy directory name into input buffer.
memcpy (pInput, pszDir, dwIn);
// Call function on Windows CE device.
hr = CeRapiInvoke (L"RapiServ", L"RAPIGetDiskSize", dwIn,
pInput, &dwOut, (PBYTE *)&pOut, NULL, 0);
// If successful, return total and free values.
if (hr == 0) {
*pdwTotal = pOut[0];
*pdwFree = pOut[1];
bRC = TRUE;
}
LocalFree (pOut);
return bRC;
}
This routine encapsulates the call to CeRapiInvoke so that the call looks just like another CE RAPI call. The code in this routine simply computes the length of the Unicode string that contains the directory specification, allocates a buffer and copies the string into it, and passes it to the CeRapiInvoke function. When the routine returns, the return code indicates success or failure of the call. CeRapiInvoke frees the input buffer passed to it. The data is then copied from the output buffer and that buffer is freed with a call to LocalFree.Throughout this section, I've put off any explanation of the parameters referring to IRAPIStream. In fact, in the example code above, the prototype for the server call, RAPIGetDiskSize, simply typed the pIRAPIStream pointer as a PVOID and ignored it. In the client code, the CeRapiInvoke call passed a NULL to the ppIRAPIStream pointer. This treatment of the IRAPIStream interface is what differentiates a block-mode call from a stream-mode call. Now let's look at the IRAPIStream interface.
Stream Mode
Stream-mode RAPI calls are different from block mode in that the initial RAPI call creates a link between the PC application and the server routine on the Windows CE device. When you call CeRapiInvoke in stream mode, the call returns immediately. You communicate with the server DLL using an IRAPIStream interface. You access this interface using a pointer returned by the CeRapiInvoke call in the variable pointed to by ppIRAPIStream.The IRAPIStream interface is derived from the standard COM IStream interface. The only methods added to IStream to create IRAPIStream are SetRapiStat and GetRapiStat, which let you set a timeout value for the RAPI communication. Fortunately, we don't have to implement an IRAPIStream interface either on the client side or in the server DLL. This interface is provided for us by the RAPI services as a way to communicate.Following is a call to CeRapiInvoke that establishes a stream connection and then writes and reads back 10 bytes from the remote server DLL.
DWORD dwIn, dwOut, cbBytes;
IRAPIStream *pIRAPIStream;
BYTE bBuff[BUFF_SIZE];
PBYTE pOut;
HRESULT hr;
// RAPI call
hr = CeRapiInvoke (L"ServDLL", L"RAPIRmtFunc", dwIn, bBuff,
&dwOut, &pOut, &pIRAPIStream, 0);
if (hr == S_OK) {
// Write 10 bytes.
pIRAPIStream->Write (bBuff, 10, &cbBytes);
// Read data from server.
pIRAPIStream->Read (bBuff, 10, &cbBytes);
}
pIRAPIStream->Release ();
When establishing a stream connection, you can still use the input buffer to pass initial data down to the remote server. From then on, you should use the Read and Write methods of IRAPIStream to communicate with the server. When you're finished with the IRAPIStream interface, you must call Release to release the interface.
The RapiFind Example Program
The RapiFind example program searches the entire directory tree of a Windows CE device for files matching a search specification. The program is in two parts: a RAPI server DLL,


Figure 15-2: The output of RapiFind
Listing 15-3: FindSrv.cpp, a stream-mode RAPI server DLL
FindSrv.cpp
//====================================================================
// FindSrv - A RAPI stream server DLL
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
#include <windows.h> // For all that Windows stuff
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//-----------------------------------------------------------------------
// Add if not defined.
#ifndef RAPISTREAMFLAG
typedef enum tagRAPISTREAMFLAG {
STREAM_TIMEOUT_READ
} RAPISTREAMFLAG;
DECLARE_INTERFACE_ (IRAPIStream, IStream)
{
STDMETHOD(SetRapiStat)(THIS_ RAPISTREAMFLAG Flag,
DWORD dwValue) PURE;
STDMETHOD(GetRapiStat)(THIS_ RAPISTREAMFLAG Flag,
DWORD *pdwValue) PURE;
};
#endif
//-----------------------------------------------------------------------
// Function prototypes declared as exports from the DLL.
// Bracket so that function name won't be mangled by C++.
extern "C" {
__ declspec(dllexport) INT RAPIFindFile (DWORD cbInput, BYTE *pInput,
DWORD *pcbOutput, BYTE **ppOutput,
IRAPIStream *pIRAPIStream);
}
//====================================================================
// DllMain - DLL initialization entry point
//
BOOL WINAPI DllMain (HANDLE hinstDLL, DWORD dwReason,
LPVOID lpvReserved) {
return TRUE;
}
//-----------------------------------------------------------------------
// WriteToClient - Writes a command and optional string to the client
//
int WriteToClient (int nCmd, int nSize, LPTSTR pszStr,
IRAPIStream *pIRAPIStream) {
int nBuff;
DWORD cbBytes;
HRESULT hr;
// Write command code.
hr = pIRAPIStream->Write (&nCmd, sizeof (nCmd), &cbBytes);
// Write size value.
hr = pIRAPIStream->Write (&nSize, sizeof (nSize), &cbBytes);
// Write length of string.
nBuff = (lstrlen (pszStr) + 1) * sizeof (TCHAR);
hr = pIRAPIStream->Write (&nBuff, sizeof (nBuff), &cbBytes);
// Write string.
hr = pIRAPIStream->Write (pszStr, nBuff, &cbBytes);
return 0;
}
int nFlag;
//-----------------------------------------------------------------------
// SrchDirectory - Recursive routine that searches a directory and all
// child dirs for matching files
//
int SrchDirectory (LPTSTR pszDir, IRAPIStream *pIRAPIStream) {
WIN32_FIND_DATA fd;
TCHAR szNew[MAX_PATH];
int i, rc, nErr = 0;
HANDLE hFind;
TCHAR *pPtr, *pSrcSpec;
// Separate subdirectory from search specification.
for (pSrcSpec = pszDir + lstrlen (pszDir); pSrcSpec >= pszDir;
pSrcSpec—-)
if (*pSrcSpec == TEXT ('\\'))
break;
// Copy the search specification up to the last directory sep char.
if (pSrcSpec <= pszDir)
lstrcpy (szNew, TEXT ("\\"));
else {
for (i = 0; (i < dim(szNew)-10) &&
((pszDir+i) <= pSrcSpec); i++)
szNew[i] = *(pszDir+i);
szNew[i] = TEXT ('\0');
}
pPtr = szNew + lstrlen (szNew);
// Report directory we're searching.
WriteToClient (2, 0, szNew, pIRAPIStream);
// Find matching files.
hFind = FindFirstFile (pszDir, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
// Report all matching files.
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
WriteToClient (1, fd.nFileSizeLow, fd.cFileName,
pIRAPIStream);
rc = FindNextFile (hFind, &fd);
} while (rc);
FindClose (hFind);
} else {
rc = GetLastError();
if ((rc != ERROR_FILE_NOT_FOUND) &&
(rc != ERROR_NO_MORE_files)) {
TCHAR szDbg[64];
wsprintf (szDbg, TEXT ("1Find Error:%d"), rc);
WriteToClient (99, 0, szDbg, pIRAPIStream);
return -1;
}
}
// Create generic search string for all directories.
lstrcat (szNew, TEXT ("*.*"));
hFind = FindFirstFile (szNew, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// Recurse to the lower directory.
lstrcpy (pPtr, fd.cFileName);
lstrcat (pPtr, pSrcSpec);
nErr = SrchDirectory (szNew, pIRAPIStream);
if (nErr) break;
*pPtr = TEXT ('\0');
}
rc = FindNextFile (hFind, &fd);
} while (rc);
FindClose (hFind);
} else {
rc = GetLastError();
if ((rc != ERROR_FILE_NOT_FOUND) &&
(rc != ERROR_NO_MORE_files)) {
TCHAR szDbg[64];
wsprintf (szDbg, TEXT ("2Find Error:%d"), rc);
WriteToClient (99, 0, szDbg, pIRAPIStream);
return -1;
}
}
return nErr;
}
//======================================================================
// RAPIFindFile - Searches the device for matching files. Called from
// PC application using RAPI.
//
INT RAPIFindFile (DWORD cbInput, BYTE *pInput, DWORD *pcbOutput,
BYTE **ppOutput, IRAPIStream *pIRAPIStream) {
int nBuff;
DWORD i, cbBytes;
TCHAR *pPtr;
HRESULT hr;
*pcbOutput = 0;
// See if proper zero-terminated string.
pPtr = (LPTSTR)pInput;
for (i = 0; i < cbInput / 2; i++)
if (!*pPtr++)
break;
// If not zero terminated or if zero length, return error.
if ((i >= cbInput / 2) || (i == 0))
return -2;
nFlag = 0;
// Search for files.
SrchDirectory ((LPTSTR) pInput, pIRAPIStream);
// Write end code. Cmd 0 -> end of search
nBuff = 0;
hr = pIRAPIStream->Write (&nBuff, sizeof (nBuff), &cbBytes);
// Release the interface.
pIRAPIStream->Release ();
return 0;
}
As with the earlier RAPI server DLL, FindSrv is short and to the point. The differences between this server and the block server can be seen early in the file. The IRAPIStream interface isn't defined in some of the older tools, so if necessary, this interface is derived at the top of the file from IStream. Immediately following the interface declaration is the exported function prototype. Notice that the prototype is enclosed in an extern C bracket. This prevents the default mangling of the function name that the C++ precompiler would normally perform. We need the name of the function unmangled so that it's a known name to the client.The exported RAPI function is RAPIFindFile, which you can see at the end of the source code. This routine does little more than check to see that the search string is valid before it calls SrchDirectory, a function internal to the DLL. SrchDirectory is a recursive function that searches the directory defined in the search specification and all subdirectories underneath. When a file is found that matches the search specification, the name and size of the file are sent back to the client caller using the Write method of IRAPIStream. The format of the data transmitted between the client and server is up to the programmer. In this case, I send a command word, followed by the file size, the length of the name, and finally the filename itself. The command word gives you a minimal protocol for communication with the client. The command value 1 indicates a found file, the value 2 indicates the server is looking in a new directory, and the value 0 indicates that the search is complete. Following the last write, Release is called to free the IRAPIStream interface.The source code for the client application, RapiFind, is shown in Listing 15-4.Listing 15-4: RapiFind.cpp, a stream-mode RAPI client application
RapiFind.cpp
//====================================================================
// RapiFind - Searches for a file or files on a Windows CE system
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
#include <windows.h> // For all that Windows stuff
#include <stdio.h>
#include <rapi.h> // RAPI includes
//====================================================================
// main - Program entry point
//
int main (int argc, char **argv) {
RAPIINIT ri;
char szSrch[MAX_PATH], *pPtr;
WCHAR szwDir[MAX_PATH];
WCHAR szName[MAX_PATH];
DWORD i, dwTotal = 0, dwFiles = 0, dwIn, dwOut, cbBytes;
IRAPIStream *pIRAPIStream;
PBYTE pInput, pOut;
HRESULT hr;
int rc, nCmd, nSize;
// If no argument, fail.
if (argc < 2) {
printf ("\r\nUSAGE: %s <search spec>\r\n\r\n", argv[0]);
return -1;
}
lstrcpy (szSrch, argv[1]);
// Call RapiInitEx to asynchronously start RAPI session.
ri.cbSize = sizeof (ri);
rc = CeRapiInitEx (&ri);
if (rc != NOERROR) {
printf (TEXT ("Rapi Initialization failed\r\n"));
return 0;
}
// Wait 5 seconds for connect.
rc = WaitForSingleObject (ri.heRapiInit, 5000);
if (rc == WAIT_OBJECT_0) {
if (ri.hrRapiInit != NOERROR) {
printf (TEXT ("Rapi Initialization failed\r\n"));
return 0;
}
} else if (rc == WAIT_TIMEOUT) {
printf (TEXT ("Rapi Initialization timed out.\r\n"));
return 0;
}
// Point to end of name.
pPtr = szSrch + lstrlen (szSrch) - 1;
// Strip any trailing backslash.
if (*pPtr == '\\')
*pPtr = '\0';
// Look for wildcards in filename. pPtr points to string end.
for (i = 0; (pPtr >= szSrch) && (*pPtr != '\\'); pPtr--) {
if ((*pPtr == '*') || (*pPtr == '?'))
i++;
}
if (pPtr <= szSrch) {
lstrcpy (szSrch, TEXT ("\\"));
lstrcat (szSrch, argv[1]);
}
if (i) {
printf (TEXT ("\r\n Searching for %s\r\n\r\n"), pPtr+1);
} else
printf (TEXT ("\r\n Searching in %s\r\n\r\n"), szSrch);
// No wildcards, append *.*
if (i == 0)
lstrcat (szSrch, "\\*.*");
// Convert ANSI string to Unicode. At the same time, copy it
// into a discardable buffer for CeRapiInvoke.
dwIn = lstrlen (szSrch) + 1; //Make mbstowcs convert terminating 0.
pInput = (PBYTE)LocalAlloc (LPTR, dwIn * sizeof (WCHAR));
if (!pInput) {
printf (TEXT ("\r\nOut of memory\r\n"));
return -1;
}
mbstowcs ((LPWSTR)pInput, szSrch, dwIn);
dwIn *= sizeof (WCHAR);
// RAPI call
hr = CeRapiInvoke (L"FindSrv", L"RAPIFindFile", dwIn,
pInput, &dwOut, &pOut, &pIRAPIStream, 0);
if (hr == S_OK) {
// Read command.
pIRAPIStream->Read (&nCmd, sizeof (nCmd), &cbBytes);
while (nCmd) {
switch (nCmd) {
// Display found file.
case 1:
// Read length of file.
pIRAPIStream->Read (&i, sizeof (i), &cbBytes);
dwTotal += i;
dwFiles++;
// Read length of filename.
pIRAPIStream->Read (&nSize, sizeof (nSize), &cbBytes);
// Read name itself.
pIRAPIStream->Read (szName, nSize, &cbBytes);
// Print directory and name.
printf (TEXT ("%9d\t%S%S\r\n"), i, szwDir, szName);
break;
// Display name of directory we're currently searching.
case 2:
// Read and discard dummy length value.
pIRAPIStream->Read (&nSize, sizeof (nSize), &cbBytes);
// Read length of directory.
pIRAPIStream->Read (&nSize, sizeof (nSize), &cbBytes);
// Read directory name itself.
pIRAPIStream->Read (szwDir, nSize, &cbBytes);
break;
}
// Read next command.
pIRAPIStream->Read (&nCmd, sizeof (nCmd), &cbBytes);
}
pIRAPIStream->Release ();
} else if (hr == ERROR_FILE_NOT_FOUND)
printf (TEXT ("The RAPI server DLL FindSrv could not be found on the CE target device.\r\n"));
else
printf (TEXT ("CeRapiInvoke returned %d"), hr);
printf (TEXT ("\r\nFound %d file(s). Total of %d bytes.\r\n\r\n"),
dwFiles, dwTotal);
// Clean up by uninitializing RAPI.
CeRapiUninit ();
return 0;
}
The call to CeRapiInvoke returns a pointer to an IRAPIStream interface that's then used to read data from the server. The client reads one integer value to determine whether the following data is a found file, a report of the current search directory, or a report that the search has ended. With each command, the appropriate data is read using the Read method. The result of the search is then reported using printf statements. After all the results have been returned, the application calls the Release method to free the IRapiStream interface.Although you could implement the same file-find function of RapiFind using a block-mode connection, the stream format has a definite advantage in this case. By reporting back results as files are found, the program lets the user know that the program is executing correctly. If the program were designed to use a block-mode call, RapiFind would appear to go dead while the server DLL completed its entire search, which could take 10 or 20 seconds.
As I mentioned in the explanation of CeRapiInit, a call to this function doesn't initiate a connection to a device. You can, however, be notified when a connection to a Windows CE device is established. There are ways, both on the PC and on the Windows CE device, to know when a connection is made between the two systems. After a brief look at CeUtil, which provides some handy helper functions for PC applications dealing with Windows CE devices, I'll talk next about connection notifiers.