To examine this step, you are going to skip over what’s taking place on the CE side for now, concentrating exclusively on the desktop code that invokes the remote function. (You’ll look at the CE-side DLL and the CE-side HTML viewer application next.) You can’t invoke just any CE-side function from the desktop. There are two big restrictions. First, the function you invoke must reside in a DLL on the CE device before you try to call it; and second, the invoked function must conform to a specific prototype.
The job of invoking the remote function is handled by the RAPI call CeRapiInvoke(), which loads a remote DLL, and then jumps to a specific entry point corresponding to the supplied function name. As usual, first you initialize the RAPI subsystem. After that task is handled, you may call the CeRapiInvoke() function.
void CMainFrame::OnLaunchViewer() { // init the rapi subsystem HRESULT hr = CeRapiInit(); if ( hr != ERROR_SUCCESS ) { return; } //invoke the LaunchViewer Fxn DWORD cbOutput; PBYTE pOutput;
The key to using CeRapiInvoke is making sure the parameters are squeaky clean with respect to type and value. Here’s the declaration for CeRapiInvoke():
HRESULT CeRapiInvoke( //Unicode path to remote DLL LPCWSTR pDllPath, //C style Unicode function name LPCWSTR pFunctionName, //number bytes input buffer data DWORD cbInput, //address of input data buffer, allocated by //the caller BYTE *pInput, //address of DWORD to hold byte count of //returned data DWORD *pcbOutput, //address of ptr to returned data BYTE **ppOutput, //address of pointer to an IRAPIStream //interface IRAPIStream **ppIRAPIStream, //supply a NULL placeholder DWORD dwReserved );
CeRapiInvoke() operates in one of two modes, depending on the value of the IRAPIStream ** parameter to the function. If this parameter is set to equal NULL, the function returns after all processing is completed on the CE side. This is known as synchronous behavior, or block mode. The advantage of block mode is that it’s simple to implement, but it’s also slower and less flexible than the asynchronous stream mode. If the ppIRAPIStream parameter contains a valid pointer to an interface, CeRapiInvoke() operates in stream mode. In stream mode, the desktop side and the application can communicate during the CE-side processing, sending data of arbitrary size back and forth via the RAPI stream interface.
We use block mode here because the CE application and the desktop aren’t dynamically exchanging data.
hr = CeRapiInvoke( L"CeSideRapiInvoke.dll", L"LaunchViewer", 0, NULL, &cbOutput, &pOutput, NULL, 0 );
The trickiest parts of getting this mechanism to work are making sure that the DLL is loaded on the CE side and that the function you are trying to invoke is resolved. Two things are likely to throw a spanner in the works at this point:
- If the path or function names aren’t Unicode, they wont be resolved.
- If you are using a combination of C and C++ languages, be certain that the DLL on the CE side exports C-style function names, rather than C++-style “decorated” names.
If the CeRapiInvoke() call is failing, checking for the following errors will help you to isolate the problem.
if (hr == ERROR_FILE_NOT_FOUND ) {return;} if (hr == ERROR_CALL_NOT_IMPLEMENTED ) {return;} if (hr == ERROR_EXCEPTION_IN_SERVICE ) {return;} //uninit rapi CeRapiUninit(); }
When you are done processing, you uninitialize the RAPI subsystem.
To see exactly what function names the CE side DLL exports, you can use the Visual C++ Dependency Walker utility to check the exports from the library. You’ll explore the technique for doing this after you look into remote function invocation.