How DesktopRAPIInvoker Launches the HTML Viewer on the CE Device

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.

How DesktopRAPIInvoker Launches the HTML Viewer on the CE Device

Creating the DLL for the Remotely Invoked Function

As mentioned in this lesson's introduction, there are two keys to creating a CE function that may be remotely invoked. First, the function prototype must conform exactly to the declaration you see below:

HRESULT (STDAPICALLTYPE RAPIEXT) MyRapiInvokableFxn (
                   DWORD          cbInput,         // [IN]
                   BYTE           *pInput,         // [IN]
                   DWORD          *pcbOutput,      // [OUT]
                   BYTE           **ppOutput,      // [OUT]
                   IRAPIStream    *pIRAPIStream    // [IN]
                                  );

Second, you must make sure the name exported for the function is a C-style function name, not a C++-style "decorated" name. C++ compilers generate arbitrary function names that encode things such as parameter list information, function return types, and class membership. These names are not standardized, so you can't safely use them to invoke functions in the DLL you are constructing for use with RAPI. This can be a bit of a drawback if you enjoy the convenience of some C++ development environment tools.

If you use Visual C++ Wizards or other similar productivity facilities to generate the skeleton files for your DLL, a thicket of macros and defines are automatically invoked in your behalf. These make it hard to tell just what kind of function names you'll end up with. To see exactly what is exported by the DLL, use the Visual C++ Dependency Walker to look at the exported symbols in the DLL.

Figure 1: Looking At DLL Exports With The Dependency Walker

This is the Dependency Walker's view of the DLL that contains LaunchViewer(), the function you invoke from DesktopRAPIInvoker. The DLL was built using files generated by App Wizard. When the files were initially created, I chose the option that includes an exported dummy variable and an exported dummy function in the generated files. Notice that there are three more or less "English" looking names in the Exports pane: LaunchViewer, the exported function, and fnDRICompanion and nDRICompanion, the automatically generated "dummy" exports. By contrast, there are two long (ugly) names above, which happen to be the class contructor and destructor for CDRICompanion. If a function you are trying to call from CeRapiInvoke() looks something like the first two names in the Exports pane, you've gotten C++ style function naming and the call won't be resolved.

Summing Up & Looking Ahead

Getting you DLL properly declared and built is without question the most challenging part of multi-platform CE programming. In this lesson, you've seen how a remotely invoked function must be declared. In the next, you'll dissect the header file for the DLL that packages your remote function. Given these basics, you'll have a powerful tool for creating customized, collaborative presentation applications on the CE device.



About the Author

Nancy Nicolaisen

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation. In addition to writing for Developer.com and CodeGuru, she has written several books, including Making Win 32 Applications Mobile.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Do you know where your data is? Consumer cloud-based file sharing services store your sensitive company data on servers outside of your control, outside of your policy and regulatory guidelines – maybe even outside your country – and not managed by you. The potential for data leakage, security breaches, and harm to your business is enormous. Download this white paper to learn about file sync and share alternatives that allow you to manage and protect your sensitive data while integrating and …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds