Write Your Own RAPI Functions with CeRapiInvoke

< BACK  NEXT >
[oR]

Earlier in this chapter CeCreateProcess was used to create a process on a Windows CE device. However, because the handles returned in the desktop application actually reside on the Windows CE device, you cannot use these handles to block until the application is terminated. This may be important if you need your desktop application to wait until the CE application has terminated. You can circumvent this problem by writing your own RAPI functions on the Windows CE device and calling them from the desktop using CeRapiInvoke.

To do this you must do the following:

  • Write a dynamic link library (DLL) for the Windows CE device, and implement your own RAPI function. This function must be exported.

  • Call the CeRapiInvoke function in your desktop application and specify the name of the Windows CE DLL and function name.

Using CeRapiInvoke provides complete freedom for calling almost any Windows CE functions from a desktop application. There are actually two types of functions that can be called from CeRapiInvoke:

  • Blocking functions. The call CeRapiInvoke does not return until the Windows CE device function has returned.

  • Stream functions. The call to CeRapiInvoke returns immediately and the IRAPIStream COM interface is used to allow the desktop and Windows CE DLL to communicate over an extended period of time.

The first example will show how to write a blocking function that solves the problem with CreateProcess described above.

A CeRapiInvoke Blocking Function

To write a CeRapiInvoke function, you will need to implement code both on the desktop (where the call to CeRapiInvoke is made) and on the Windows CE device (the implementation of your function, which is in a DLL). First, let's look at building the DLL.

The DLL project can be built by selecting "WCE Dynamic-Link Library" in the "Projects" list from AppWizard. At step 1 of the wizard select "A Simple Windows CE DLL Project" to build the default files for you. Listing 10.4 shows the complete code that is placed in the DLL. (You can find this code on the CDROM in the directory \RAPI\CustomBlock\CEBlock.)

Listing 10.4 Windows CE DLL implementation of a CeRapiInvoke function
 #include "stdafx.h" // Function prototype to export function. extern "C" { __declspec(dllexport) int WaitCreateProcess(DWORD cbInput,       BYTE* pInput, DWORD *pcbOutput,       BYTE **ppOutput, PVOID reserved); } int WaitCreateProcess(DWORD cbInput,     BYTE* pInput, DWORD *pcbOutput,     BYTE **ppOutput, PVOID reserved) {   LPTSTR lpAppName = (LPTSTR)pInput;   PROCESS_INFORMATION pi;   int nError = 0;   if(!CreateProcess(lpAppName, NULL,       NULL, NULL, FALSE,0,       NULL, NULL, NULL, &pi))     nError = GetLastError();   else   {     if(WaitForSingleObject(pi.hProcess,         INFINITE) == WAIT_FAILED)       nError = GetLastError();   }   CloseHandle(pi.hProcess);   CloseHandle(pi.hThread);   *ppOutput = (BYTE*)LocalAlloc(LPTR, sizeof(nError));   memcpy(*ppOutput, &nError, sizeof(nError));   *pcbOutput = sizeof(nError);   return 0; } 

All functions you write that will be called through CeRapiInvoke must be exported. This is done in Listing 10.4 by writing a function prototype and including the specification __declspec(dllexport). Note that there are two underscores at the start of this specification. Further, if you are compiling the source file using C++ (that is, it has a .cpp extension), you will need to include the function prototype in an extern "C" block. This stops the function name from being decorated (mangled), and uses the standard C function-naming conventions.

Any function being called from CeRapiInvoke must have the same parameters and return type as function WaitCreateProcess. Table 10. 8 describes these parameters. In the case of WaitCreateProcess, pInput points to the name of the application to be run as a Unicode string.

In Listing 10.4 the function CreateProcess is called to create a new process on the Windows CE device. The arguments passed are identical to CeCreateProcess described in Table 10.1 and shown in Listing 10.1. If the call to CreateProcess succeeds, the process handle is passed to WaitForSingleObject, and this will block until the process terminates. Remember that a process handle is signaled when the process associated with the handle terminates. CloseHandle is called on both the process and thread handles. Refer to Chapter 5 for more details on CreateProcess.

Table 10.8. WaitCreateProcess Function parameters for a function to be called with CeRapiInvoke
WaitCreateProcess
DWORD cbInput Number of bytes of data being passed to function from CeRapiInvoke
BYTE* pInput Pointer to the data being passed from CeRapiInvoke
DWORD *pcbOutput Pointer to a DWORD in which the function places the number of bytes of data being returned to CeRapiInvoke
BYTE **ppOutput Pointer to a BYTE pointer in which a pointer to the data being returned to CeRapiInvoke is placed
PVOID reserved Reserved value, ignore
int Return Value Function return value returned to CeRapiInvoke

If an error occurs the error number is obtained from GetLastError and stored in the variable nError. The contents of this variable will be returned through the ppOutput pointer. To do this, the following steps are carried out:

  • Allocate a memory block large enough to take an int (the size of the nError variable), through calling LocalAlloc.

  • Copy the contents of nError into this new memory block, through calling memcpy.

  • Set the number of bytes being returned in the ppOutput pointer in the parameter pcbOutput.

The memory block returned through ppOutput is owned by the operating system and does not have to be freed in the Windows CE DLL.

Building the Windows CE DLL will automatically download the file to the Windows CE device. The default location for this is the root of the object store. Note that, by default, DLLs are not listed in an Explorer file listing to change this, select the View and Options menu commands in Explorer, and select "Show all Files."

The next stage is to write code to use CeRapiInvoke to call the function you have just written. The call to CeRapiInvoke is made, as you would expect, in a desktop application. Listing 10.5 shows the complete code for a desktop console application that calls the WaitCreateProcess function implemented in Listing 10.4. The code can be found in the directory \RAPI\CustomBlock\CustomBlock on the CDROM.

Listing 10.5 Calling CeRapiInvoke from the desktop application
 #include "stdafx.h" #include <rapi.h> #include <iostream.h> int main(int argc, char* argv[]) {   HRESULT hr;   DWORD dwOut;   BYTE* pOut;   int nErr;   hr = CeRapiInit();   if(FAILED(hr))   {     cout   "Could not initialize RAPI:"            GetLastError()   endl;     return 1;   }   LPWSTR lpAppname = L"\\windows\\cmd.exe";   hr = CeRapiInvoke(L"CEBlock", L"WaitCreateProcess",       (wcslen(lpAppname) + 1) * sizeof(WCHAR),       (BYTE*)lpAppname, &dwOut, &pOut, NULL, 0);   if(FAILED(hr))   {     nErr = CeGetLastError();     switch(nErr)     {     case ERROR_FILE_NOT_FOUND :       cout   "Library not found"   endl;       break;     case ERROR_CALL_NOT_IMPLEMENTED:       cout   "Could not locate function in DLL"              endl;       break;     case ERROR_EXCEPTION_IN_SERVICE:       cout   "Exception caught in function"              endl;       break;     default:       cout   "Error in invoke: "              nErr   endl;     }   }   if(pOut != NULL)   {     nErr = (int)*pOut;     cout   "Error Return: "   nErr   endl;     CeRapiFreeBuffer(pOut);   }   else     cout   "Function failed to return error info"            endl;   hr = CeRapiUninit();   if(FAILED(hr))     cout   "Could not un-initialize RAPI"   endl;   return 0; } 

The call to CeRapiInvoke is passed the name of the DLL on the Windows CE device and the name of the function to be called in that DLL (Table10.9). Misspecification of these two parameters is the most common reason why the function call fails. The pInput parameter is used to pass the name of the application to execute, which is passed as a Unicode string. The cbInput parameter is set to the number of bytes of data in the application name, including the terminating NULL characters.

Table 10.9. CeRapiInvoke Calls a function in a Windows CE DLL from the desktop
CeRapiInvoke
LPCWSTR pDllPath DLL name and path to be used. Unicode string.
LPCWSTR pFunctionName Function name in DLL to be called. Unicode string.
DWORD cbInput Number of bytes of data to send to function.
BYTE *pInput Pointer to the data to send to function.
DWORD *pcbOutput Receives number of bytes of data being returned from function call.
BYTE **ppOutput Pointer to data being returned by function call.
IRAPIStream **ppIRAPIStream Receives an IRAPIStream COM interface. This is not used for blocking calls so pass NULL.
DWORD dwReserved Pass as 0.
HRESULT Return Value HRESULT indicating success or failure.

If the CeRapiInvoke call fails, CeGetLastError is used to obtain the error code. The switch case shows the three most common errors returned. If the call succeeds, the pOut pointer should point at an integer value containing the error code returned from CreateProcess, or 0 if the call succeeded. The contents of pOut are copied into nErr and displayed to the user.

Finally, CeRapiFreeBuffer must be called on the memory pointed to by pOut to ensure that the memory block is freed correctly.

RAPI Stream Functions

RAPI stream functions allow much more flexibility than blocking functions you can use them to communicate between a desktop application and Windows CE application over an extended period of time. Chapter 8 (TCP/IP communications) noted that ActiveSync versions 3.0 and later do not allow TCP/IP routing, so sockets cannot be used to communicate between a desktop and Windows CE application when ActiveSync is running. Instead, you can use RAPI stream functions.

A RAPI stream function is simple to implement once you know how to write a blocking function. In addition to the blocking function code you need to do the following:

  • Declare a IRAPIStream COM interface pointer variable in the desktop application.

  • Pass this IRAPIStream pointer to CeRapiInvoke.

  • Modify the RAPI function in the Windows CE DLL to receive an IRAPIStream pointer.

  • Call the Read and Write IRAPIStream functions to transfer data between the Windows CE DLL and desktop application.

IRAPIStream is actually a COM interface derived from the standard IStream interface. RAPI looks after the creation of the COM object and the interface, so you do not need to know about COM to use the interface.

You will need to design a simple communications protocol so that both the Windows CE DLL and desktop application know what data to expect and how to deal with it. Further, you will need to build into the protocol a mechanism to allow either the Windows CE DLL or desktop application to terminate the communications.

The example shown here will take the blocking application and add code to allow the Windows CE device to report back the amount of processor time the primary thread in the launched application has consumed. This information will be reported back every five seconds and will be displayed in the desktop application's window. This application provides a simple way of monitoring CPU usage by your application.

The Windows CE function GetThreadTimes returns the amount of processor time consumed by a thread in a FILETIME structure. In Windows CE 3.0 and later, the function returns the amount of time spent in user code (your code) and in kernel (operating system) code. In earlier versions of Windows CE the kernel time is always returned as zero, and all time spent executing is returned as user code execution. This function is described in more detail in Chapter 5, "Processes and Threads." The parameters passed to GetThreadTimes are the following:

  • Handle to the thread to obtain thread times for

  • FILETIME when the thread was created

  • FILETIME when the thread was terminated

  • FILETIME for the time spent in kernel functions

  • FILETIME for the time spent in user code

The Windows CE DLL will return data back to the desktop application, preceded by a DWORD code indicating the nature of the data being returned. These codes are declared in both the desktop application and Windows CE DLL.

 const DWORD dwCODE_ERROR = 1; const DWORD dwCODE_USERTIME = 2; const DWORD dwCODE_END = 3; 

The code dwCode_ERROR indicates that a DWORD will follow containing an error, dwCODE_USERTIME indicates that the FILETIME structures returned by GetThreadTimes follow, and dwCODE_END indicates that the process has terminated and the DLL will stop sending data.

The complete Windows CE DLL is shown in Listing 10.6. The code can be found on the CDROM in the directory \RAPI\CustomStream\CEStream. Note how the last parameter in the RAPI function has been changed to an IRAPIStream pointer. The function will receive this pointer through which the read and write functions can be called to communicate with the desktop application.

Listing 10.6 Windows CE DLL code for RAPI stream function
 #include "stdafx.h" #include "rapi.h" // Function prototype to export function. extern "C" { __declspec(dllexport) int ThreadTimes(DWORD cbInput,     BYTE* pInput, DWORD *pcbOutput,     BYTE **ppOutput, IRAPIStream *pStream); } const DWORD dwCODE_ERROR = 1; const DWORD dwCODE_USERTIME = 2; const DWORD dwCODE_END = 3; BOOL WriteResult(DWORD dwCode, void* pData,     DWORD dwBytesToWrite, IRAPIStream *pStream) {   DWORD dwWritten, dwToWrite, dwError;   HRESULT hr;   dwToWrite = sizeof(DWORD);   hr = pStream->Write(&dwCode, dwToWrite, &dwWritten);   if(FAILED(hr) || dwToWrite != dwWritten)     return FALSE;   if(dwBytesToWrite > 0)   {     dwError = GetLastError();     hr = pStream->Write(pData,       dwBytesToWrite, &dwWritten);     if(FAILED(hr) || dwBytesToWrite != dwWritten)       return FALSE;   }   return TRUE; } int ThreadTimes(DWORD cbInput, BYTE* pInput,     DWORD *pcbOutput,     BYTE **ppOutput, IRAPIStream *pStream) {   LPTSTR lpAppName = (LPTSTR)pInput;   PROCESS_INFORMATION pi;   FILETIME ft[4];   DWORD dwError;   BOOL bContinue = TRUE;   if(!CreateProcess(lpAppName, NULL, NULL,         NULL, FALSE,0, NULL, NULL, NULL, &pi))   {     dwError = GetLastError();     WriteResult(dwCODE_ERROR,       &dwError, sizeof(dwError), pStream);   }   else   {     while(bContinue &&       WaitForSingleObject(pi.hProcess, 5000)       == WAIT_TIMEOUT)     {       if(!GetThreadTimes(pi.hThread,         ft[0], &ft[1],         &ft[2], &ft[3]))       {         dwError = GetLastError();         WriteResult(dwCODE_ERROR,           &dwError,           sizeof(dwError), pStream);         bContinue = FALSE;       }       else       {         if(!WriteResult(dwCODE_USERTIME,             ft, sizeof(ft), pStream))           bContinue = FALSE;       }     }     if(bContinue)       WriteResult(dwCODE_END, NULL, 0, pStream);   }   CloseHandle(pi.hProcess);   CloseHandle(pi.hThread);   // no output data to send back   *ppOutput = NULL;   *pcbOutput = 0;   return 0; } 

The function WriteResult in Listing 10.6 is used to send the code indicating what type of data is being returned and to send the data itself back to the desktop application. The function is passed the DWORD code (one of dwCODE_ERROR, dwCODE_USERTIME, or dwCODE_END), a pointer pData to the data to write (which could be NULL), the number of bytes in dwBytesToWrite pointed to by pData, and the IRAPIStream pointer through which to write. WriteResult uses the IRAPIStream Write function to write the data to the desktop application, and this is passed the following:

  • A pointer to the data to send

  • A DWORD containing the number of bytes to send

  • A DWORD pointer in which the actual number of bytes sent is returned

The function ThreadTimes has a "while" loop that calls WaitForSingleObject on the process handle with a timeout of 5000 milliseconds. The loop continues while WaitForSingleObject returns WAIT_TIMEOUT, indicating that the process is still running. The function GetThreadTimes is called on each "while" loop iteration, and all four FILETIME structures are written out to the desktop application. Any errors terminate the "while" loop, and these errors are returned back to the desktop application. Finally, when the "while" loop terminates, a dwCODE_END code is sent to the desktop. Note that the function ThreadTimes does use the ppOutput pointer to return data back to the desktop application.

The code for the console desktop application is shown in Listing 10.7. The main function declares an IRAPIStream pointer and passes a pointer to this pointer in CeRapiInvoke. On return, a valid IRAPIStream COM interface pointer is obtained.

Listing 10.7 Desktop code for RAPI stream function
 #include "stdafx.h" #include <rapi.h> #include <iostream.h> const DWORD dwCODE_ERROR = 1; const DWORD dwCODE_USERTIME = 2; const DWORD dwCODE_END = 3; void ShowThreadTime(FILETIME ft[4]) {   __int64 ht;   ht = ft[3].dwHighDateTime;   ht   = 32;   ht |= ft[3].dwLowDateTime;   ht /= 10000;   cout   "User Time: "   (DWORD)ht   endl; } int main(int argc, char* argv[]) {   HRESULT hr;   DWORD dwOut, dwCode, dwBytesRead, dwError;   BYTE* pOut;   int nErr;   BOOL bContinue = TRUE;   IRAPIStream *pStream;   FILETIME ft[4];   hr = CeRapiInit();   if(FAILED(hr))   {     cout   "Could not initialize RAPI:"            GetLastError()   endl;     return 1;   }   LPWSTR lpAppname = L"\\windows\\cmd.exe";   hr = CeRapiInvoke(L"CEStream", L"ThreadTimes",       (wcslen(lpAppname) + 1) * sizeof(WCHAR),       (BYTE*)lpAppname, &dwOut,       &pOut, &pStream, 0);   if(FAILED(hr))   {     nErr = CeGetLastError();     switch(nErr)     {     case ERROR_FILE_NOT_FOUND :       cout   "Library not found"   endl;       break;     case ERROR_CALL_NOT_IMPLEMENTED:       cout   "Could not locate function in DLL"              endl;       break;     case ERROR_EXCEPTION_IN_SERVICE:       cout   "Exception caught in function"              endl;       break;     default:       cout   "Error in invoke: "   nErr              endl;     }   }   else   {     while(bContinue)     {       // Read the DWORD code       hr = pStream->Read(&dwCode,         sizeof(DWORD), &dwBytesRead);       if(FAILED(hr) ||         dwBytesRead != sizeof(DWORD))       {         cout   "Could not read result: "                CeGetLastError();         bContinue = FALSE;       }       else       {         switch (dwCode)         {         case dwCODE_ERROR:           hr = pStream->Read(&dwError,                  sizeof(DWORD),             &dwBytesRead);           if(FAILED(hr) || dwBytesRead                      != sizeof(DWORD))             cout   "Error in read"                    endl;           else             cout   "Error from CE:"                    dwError   endl;           bContinue = FALSE;           break;         case dwCODE_USERTIME:           hr = pStream->Read(ft,                 sizeof(ft),             &dwBytesRead);           if(FAILED(hr) || dwBytesRead                      != sizeof(ft))             cout              "Could not read filetime"               endl;           else             ShowThreadTime(ft);           break;         case dwCODE_END:           bContinue = FALSE;           break;         }       }     }   }   hr = CeRapiUninit();   if(FAILED(hr))     cout   "Could not un-initialize RAPI"   endl;   return 0; } 

The main function creates a "while" loop to read each code and associated data sent from the Windows CE DLL. The "while" loop uses the IRAPIStream interface's Read function, which takes the following parameters:

  • A pointer to a buffer to receive the data

  • A DWORD value containing the number of bytes to read

  • A pointer to DWORD variable in which the actual number of bytes read will be placed

The code is read and a switch statement used to determine the action to be taken. In the case of a dwCODE_ERROR code, the DWORD error is read and the loop terminated. No extra data is read for a dwCODE_END code; the loop is simply terminated. For a dwCODE_USERTIME code the FILETIME structures are read and passed to the function ShowThreadTime for display.

The ShowThreadTime function displays just the time spent in user code, which is contained in the fourth element of the FILETIME structure. The FILETIME structure is usually used to contain an absolute time, but in the case of GetThreadTimes it contains an elapsed time. The FILETIME structure contains two members, dwHighDateTime and dwLowDateTime, which combined contain a number of 100 nanosecond intervals.

The code in ShowThreadTime moves the dwHighDateTime and dwLowDataTime members into a __int64 variable called ht. The datatype __int64 stores 64-bit, or 8-byte, integer values. The function moves a FILETIME structure to a __int64 variable by doing the following:

  • Copying dwHighDateTime into ht, which is placed in the lowest 4 bytes of ht.

  • Shifting the 4 bytes just copied into ht into the top 4 bytes of ht (using the bit shift operation " ").

  • Moving the dwLowDataTime bytes into the lowest 4 bytes of ht. The bitwise OR operation (|) is used so as not to overwrite the data in the top 4 bytes of ht.

The value in ht is then divided by 10000 to convert the units from 100 nanosecond intervals to milliseconds. This value is then displayed to the user. Note that only the lowest 4 bytes of ht are actually displayed, meaning that the application will fail to display the correct thread times after about 49 days.


< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net