RAPI is one of the most useful features in the Windows CE OS because it allows you to remotely access your device while connected via ActiveSync or WMDC. It's like having a generic socket server on your device that is always listening for a connection from the connected PC and that, besides implementing a nuber of very useful APIs (Registry and File System to name a few) it also allows you to extend it by using special DLLs. Unfortunately this feature also is a security issue because it allows any connected PC to scan your device's file for instance. This issue was solved with the Windows Mobile 5 version of the OS by enforcing security policies that vary in degree of strictness. For Smatphones, the security policy has always been much stricter than for "Pocket PCs" (the main distinction between both is whether or not the screen is touch-sensitive), with their "two-tiered" security model. Pocket PCs have a single-tier security model and are a bit kinder on security. In a nutshell, you usually get these devices either partially or totally locked for RAPI connections. If your device is completely locked, then there's no hope for you and you can stop reading this right now. On the other hand, if you have a partially locked device you can either use VS2008's Device Secutity Manager tool to crack open the device's security or use the SDK cerificates to sign your DLL (you don't want to spend money on this by purchasing Geotrust signing events, do you?). Anyway, if you plan to sell your DLL as part of an application's setup file, you will have to sign it (and do some other crazy stuff like marking the DLL file with the system attribute bit and adding a bit of XML to the cab).
Writing the DLL is way simpler than all of the above. Open VS2008 and create a new project and select Visual C++ / Smart Device / Win32 Smart Device Project. Name this project "HTCGServer". When the Win32 Smart Device Project Wizard shows up, click the Next button to select the desired SDKs. Next, select the "DLL" radio button and the "Export symbols" check box.
Because this is a RAPI extension library, we need to make a reference to the RAPI header files which ship
with the SDKs. On my system the header file is on this folder:
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Activesync\Inc
For the Windows Mobile 6.0 SDK, you can find the same files here:
C:\Program Files\Windows Mobile 6 SDK\Activesync\inc
Go to your project's properties and select the "C/C++" option on the left tree and add one of the above paths (please validate these paths against your system first) to the "Additional Include Directories" property. This is where you add paths for header files other than the ones that ship with the SDK and that are not in your project's source path. Make sure that both the "Release" and "Debug" configurations have these paths set before you go on.
Now, let's take a look at the HTCGServer.h and HTCGServer.cpp fils that the Project Wizard created for us. Both files contain samples of DLL exportable symbols: a variable, a function and a class. All of them are marked with the HTCGSERVER_API macro that is defined at the top of HTCGServer.h:
#ifdef HTCGSERVER_EXPORTS
#define HTCGSERVER_API __declspec(dllexport)
#else
#define HTCGSERVER_API __declspec(dllimport)
#endif
This works in a very simlple way: when you compile this header file from within the DLL project, the HTCGSERVER_EXPORTS macro is defined, so HTCGSERVER_API resolves to __declspec(dllexport), meaning that you want to export this symbol from the DLL. You can confirm this by opening your project's property pages and inspecting the C/C++ / Preprocessor / Preprocessor Definitions property: you will find the HTCGSSERVER_EXPORTS definition there (done for you again by the Project Wizard).
Now erase everything below these lines and write the following:
#include <rapi.h>
extern "C"
{
HTCGSERVER_API int HTCGSensorStream(DWORD cbInput, BYTE* pInput, DWORD* pcbOutput, BYTE** ppOutput, IRAPIStream* pStream);
};
These lines include the RAPI header file (required for the IRAPIStream declaration) and declare a "C" function named HTCGSensorStream. This is the function that will be called by the RAPI runtime and it cannot be exported with the standard C++ linker names (these are mangled in order to include parameter type information for better run-time type checking). The number and types of the parameters are defined by RAPI for DLL extension functions and these can be of two types: blocking and non-blocking. A blocking extension function works like a regular function call: does its work and returns data back to the caller. A non-blocking function (or streamed call) uses the pStream parameter to keep an open communications channel to the caller on the PC: that's exactly what we'll use.
Let's create a stub for this function on the HTCGServer.cpp file:
//
// Starts the HTC G Sensor streaming to the PC via RAPI
//
int HTCGSensorStream(DWORD cbInput, BYTE* pInput, DWORD* pcbOutput, BYTE** ppOutput, IRAPIStream* pStream)
{
*ppOutput = NULL;
*pcbOutput = 0;
LocalFree(pInput);
pStream->Release();
return 0;
}
Right now, the function does nothing but return after cleaning up. I'll get back to it after finishing the data server class. Now you can go back to last post's sample code and copy the HTCGSensor.h and HTCGSensor.cpp files to the new poject directory and add them to the project tree. This is all we need to write the final part of the extension DLL: the server class.
The server class is actually quite simple: it opens the sensor in the constructor, closes it in the destructor and contains a single function (Run) that cntinually reads data from the stream and writes back the sensor data to the listening PC software. This particular implementation uses a polling mechanism that waits for a command from the client and writes sensor data back. It's quite simple to understand how this works, but it's not the best approach performance-wise. From my own experience I have learned that you get the best performance when you put the stream reading and writing in different threads. This allows you to stream data from the server to the client without waiting for a specific command. Performance improves at the cost of complexity.
HRESULT HTCGSensorServer::Run()
{
HRESULT hr = S_OK;
bool bRun = true;
HTCGSensorData sensorData;
while(bRun)
{
DWORD dwMsg = 0, dwRead, dwWrite;
hr = m_pStream->Read(&dwMsg, sizeof(dwMsg),
&dwRead);
if(FAILED(hr) || dwRead != sizeof(dwMsg))
bRun = false;
else
{
switch(dwMsg)
{
case CMD_QUIT:
bRun = false;
break;
case CMD_READ:
if(m_sensor.Read(&sensorData))
hr = m_pStream->Write(&sensorData,
sizeof(sensorData),
&dwWrite);
else
bRun = false;
break;
}
}
}
return hr;
}
Easy, right? The loop waits for two different commands: one for reading the sensor and the other to stop the loop (thereby exiting the server). Now you just need to call this function from the HTCGSensorStream entry point and your server is ready. Add the following lines to the top of the function:
HTCGSensorServer server(pStream);
server.Run();
Done.
On my next post I will implement the PC client DLL. Meanwhile, here's the sample code: HTCGServer.zip
Thanks for this series of articles! I was planning on doin something similar to this, I guess I can re-use a lot of stuff from here (;
ReplyDeletePrabhu
www.geekswithblogs.net/techtwaddle