GPS Programming Tips for Windows Mobile - Part 1
NETCF: Memory leak... now what??
Supporting Kiosk-Applications on Windows Mobile (Technically achievable vs. supported)
Wireless Programming on Windows Mobile: supported or not supported?
Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes
Disable WebBrowser's Context-Menu in NETCF applications
MAPI on Windows Mobile 6: Programmatically retrieve mail BODY (sample code)
Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control
The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)
Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity
Support Boundaries for Windows Mobile Programming (Developing Drivers, for example... Or even WiFi Programming)
Miei post in italiano sul team-blog del Supporto Tecnico agli Sviluppatori
I think this comes with experience... when you're facing a very odd issue, chances are that you're watching at a BUG in action. For example, last time it was a WebService call over GPRS which seemed to be processed successfully on the server, but returned an InvalidOperationException on the device. The problem was not with the GPRS Operator, it was not specific to any OEM-customization and it wasn't specific to Windows Mobile's version. So it must probably have been something with the NETCF itself...
Before starting deeply troubleshooting in whatever direction (in this case it might have been network traces to look at SOAP messages, instrument the code, try to reproduce with Cellular Emulator...), you may verify if the issue is reproduced when the application runs on top of the NETCF v3.5. Note that a re-compilation of the code through VS2008 is NOT needed – you can use a .CONFIG file to instruct the CLR to load v3.5 assemblies even for an application developed against v2. See here for a post by David Kline about this. The configuration file must be named “yourApplication.EXE.CONFIG” (note the “.EXE”) and must be placed in the same folder of the “yourApplication.EXE” (in a NETCF Loader Log this is what's called "Policy file"), and it must contain:
<configuration> <startup> <supportedRuntime version="v3.5.7283"/> </startup> </configuration>
To make sure the application is running on top of the v3.5, you should check if the mscoree3_5.dll is a loaded module. If you can't attach Visual Studio's debugger and can't even connect the device to a PC with Remote Tools installed (Remote Process Viewer), then you can for example use JShell directly on the device, which is a sort of command-line tool available through the "Windows Mobile Developer Power Toys". Remember to completely exit the application before running the test or even better to soft-reset the device.
Obviously you need to install the NETCF v3.5 on the device before doing so: the redistributable is available here.
Cheers,
~raffaele
This post is somehow related to a previous one (Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes), at least for the Windows Mobile-part. On Windows Mobile, Connection Manager is smart enough to the use the less expensive network available at the moment: hence, it'll open a GPRS connection only if the device is not connected through ActiveSync\WMDC's Desktop Passthrough and WiFi is not available. This depends on how you've provisioned the CM_Planner Configuration Service Provider, however that's the usual configuration.
However, how can I debug my code when it invokes ConnMgr APIs? It's simple (when you know it...): just don't use ActiveSync\WMDC. I'm talking about:
- GPS Signal Quality
- Error Reduction
- Avoid problems with localizations
- Data Layer: do NOT use XML files...
- Hints about showing itinerary with GPS Data independently on current device resolution\orientation
(- links to this project on Codeplex and to webcast Creating Location-Aware Applications for Windows Mobile Devices (Level 300))
[Part 1 and 2 are available here and here.]
You may start from the NMEA basic parser sample provided by GeoFrameworks through their Mastering GPS Programming. When GPS signal quality is low, the GPS sends some data anyway [100Bytes - 300Bytes], so checking if you're reading 0 Bytes is not a good mean for signal quality. The values you can monitor within the NMEA sentences are:
Then, there may be various ways you can reduce the error, which is there even if the signal quality is good. A quite straightforward one is to calculate the average of X positions received in a timeframe (imagine a buffer filled with NMEA sentences retrieved from the antenna). However this is dependent on the average speed and sincerely for the position I would not stress on finding an average. This is not true for example for speed values coming from the antenna, because their range may vary a bit even if you're running at the same speed (and I remembered I've read somewhere in the SportsDo website that their program uses a NASA's algorythm for speed values! - this was out of scope in my case... [I found it (see "Data Correction") - it says that the algorithm is for altitude, but I'd assume it's the same for speed as well])
On top of this, consider extracting the GPS location from GPRMC, GPGGA and GPGLL at the same time. Note that not all the GPS Antenna rely on the same NMEA sentences, and not all have the same "sentence-pattern" - for example Sirfstar doesn't have GPGLL, and its pattern is:
5x $GPRMC - Recommended Minimum specific GPS/TRANSIT data $GPVTG - Track Made Good and Ground Speed $GPGGA - Global Positioning System fix data $GPGSA - GPS DOP and Satellites Active 3x $GPGSV - Satellites in View
In my case I was simply interested on the following and it sufficed:
So, on top of the NMEA Parser, I considered an "intermediate" layer responsible for notifying upper UI with location changes. Regarding this, remember that if you have to parse Double values, then you must take in account the CurrentCulture. The NMEA Interpreter sample provided by GeoFrameworks simply set the current culture to en-US, and I basically did the same for all the UIs and intermediate layers: this avoids problems with different cultures and also provides a simple way to store data in the database consistently. Also, it allows you to be able to move the database from a device to another, or even to a PC if you want (SportsDo, for example, allows you to upload the data of the activities to their server).
Regarding the database: for a very initial draft of the application I had used the old way, i.e. store the data into a XML file. I chose so also in order not to ask my peers to install additional software's CABs (SQL Compact 3.5 or at least 3.1, which consist of 3 CABs) on low-resources old devices. The code was working fine, but after some "activities" we realized that the file was growing very fast, and writing was slower after each saved activity... we've even got OutOfMemoryExceptions sometimes! (see my previous post about NETCF Memory Leaks here) So, lesson learned: use SQL Compact, even if it'll require additional CABs when installing the application.
So, to recap, imagine:
So far we're running (or going bycicle, or rafting, whatsoever)... at the end of the activity we want to see data and show to friends to let them see how cool our Windows Mobile device is... Well, playing with Graphics can be straightforward or painful depending on your goals. At the end of the day we're talking about a matrix of points representing the pairs latitude\longitude, speed\time, distance\time, altitude\time, etc... that you want to display, hopefully independently on device resolution and orientation. Adapting the application to the actual device capabilities can be done in many ways (and many others before me posted about that), see for example:
Remember that Graphic objects internally held a reference to NATIVE resources, therefore you must explicitly call .Dispose or use the "using" directive, which automatically call .Dispose when finished with the object for you. A possible draft-code is the following: even if it does what it's meant to do, I'm sure there are better ways... (for example, it may show info about speed during the activity - as SportsDo does, and technically-speaking it doesn't use double-buffering, nor even SuspendLayout\ResumeLayout, etc., etc., etc...) I'm pasting here as it might give some ideas anyway:
/// <summary> /// Show data details: takes a datatable (it could have been a hashtable or a dictionary), and depending on category, calculates ranges for X and Y /// positions and creates a Points[] structure. Then use this.CreateGraphics to draw lines and polygons /// </summary> /// <param name="category">data category to show</param> /// <param name="table">table containing data</param> internal void Show(string category, DataTable table) { Cursor.Current = Cursors.WaitCursor; //this is done at the beginning so that this.CreateGraphics can work this.Show(); //let's make sure the Message Pump is not blocked Application.DoEvents(); //table has 2 columns //when showing speed, distance, altitude, etc over time //Column1 is where relevant data is saved //Column2 is where time data is saved //when showing path, Column1 is the latitude and Column2 the longitude int Column1 = 0; int Column2 = 0; //when showing speed, distance, altitude a string will be drawn with max value string unit = string.Empty; //boolean values set depending on data to be shown //bool bSpeed = false; //bool bDistance = false; //bool bAltitude = false; bool bPosition = false; //depending on the category to "show", previous variables may assume different values #region switch (category) switch (category) { case "speed": Column1 = 1; Column2 = 0; unit = "kmph"; //bSpeed = true; //bDistance = false; //bAltitude = false; bPosition = false; break; case "distance": Column1 = 1; Column2 = 0; unit = "km"; //bSpeed = false; //bDistance = true; //bAltitude = false; bPosition = false; break; case "altitude": Column1 = 1; Column2 = 0; unit = "m"; //bSpeed = false; //bDistance = false; //bAltitude = true; bPosition = false; break; case "position": Column1 = 0; Column2 = 1; //bSpeed = false; //bDistance = false; //bAltitude = false; bPosition = true; break; //default: // //... // break; } #endregion #region check if data was saved if (table.Rows.Count == 0) //no data has been saved { using (Graphics g = this.CreateGraphics()) { Font f = new Font(FontFamily.GenericSansSerif, 16.0f, FontStyle.Bold); SolidBrush b = new SolidBrush(Color.Black); g.DrawString("NO DATA", f, b, 0.0f, 0.0f); b.Dispose(); f.Dispose(); } return; } #endregion //to maintain the code independent on the orientation of the device int ShortestSideOfTheDevice = Math.Min(this.ClientSize.Width, this.ClientSize.Height); //array to store points to be drawn Point[] points; //min,max, range values for latitude and longitude double minX, maxX, rangeX, minY, maxY, rangeY; #region fill points[] and calculate minX, maxX, rangeX, minY, maxY, rangeY when bPosition = false if (!bPosition) { //assume last row contains the total elapsed seconds maxX = double.Parse(table.Rows[count - 1][Column2].ToString(), frmMain.Instance.DataCultureInfo); minX = 0; rangeX = maxX - minX; maxY = FindMax(table, Column1); minY = FindMin(table, Column1); rangeY = maxY - minY; //initialize Point array points = new Point[table.Rows.Count + 2]; //adding 2 points [0, Height] and [Width, Height] because we want a polygon //first point points[0].X = 0; points[0].Y = this.ClientSize.Height; //last point points[table.Rows.Count + 1].X = this.ClientSize.Width; points[table.Rows.Count + 1].Y = this.ClientSize.Height; //fill Point array int i = 1; double percentX, percentY; foreach (DataRow row in table.Rows) { //handle range = 0, otherwise possible DivideByZeroException if (rangeX == 0.0f) percentX = 100.0f; else percentX = (double.Parse(row[Column2].ToString(), frmMain.Instance.DataCultureInfo) - minX) / rangeX; //handle range = 0, otherwise possible DivideByZeroException if (rangeY == 0.0f) percentY = 100.0f; else percentY = (double.Parse(row[Column1].ToString(), frmMain.Instance.DataCultureInfo) - minY) / rangeY; //discard the point if NaN if (!(double.IsNaN(percentX) || double.IsNaN(percentY))) { points[i].X = Convert.ToInt32(this.ClientSize.Width * percentX); points[i].Y = this.ClientSize.Height - Convert.ToInt32(this.ClientSize.Height * percentY); ++i; } } } #endregion #region fill points[] and calculate minX, maxX, rangeX, minY, maxY, rangeY when bPosition = true else //bPosition = true { //find Min\Max for latitude\longitude so that we can scale the path //note that coordinates may be negative, when represented by doubles //this applies to minX, minY, maxX, maxY as well maxX = FindMax(table, Column2); minX = FindMin(table, Column2); rangeX = maxX - minX; maxY = FindMax(table, Column1); minY = FindMin(table, Column1); rangeY = maxY - minY; //initialize Point array points = new Point[table.Rows.Count]; //in order to put the "image" at the center: //1. calculate current center in terms of latitude\longitude double centerX = minX + rangeX / 2; double centerY = minY + rangeY / 2; //2. calculate position in pixel of the current center double percentCenterX = (centerX - minX) / Math.Max(rangeX, rangeY); double percentCenterY = (centerY - minY) / Math.Max(rangeX, rangeY); double PixelCenterX = Convert.ToInt32(ShortestSideOfTheDevice * percentCenterX); double PixelCenterY = ShortestSideOfTheDevice - Convert.ToInt32(ShortestSideOfTheDevice * percentCenterY); //3. shift to center screen double PixelToShiftX = (ShortestSideOfTheDevice / 2) - PixelCenterX; double PixelToShiftY = (ShortestSideOfTheDevice / 2) - PixelCenterY; //now for each point: // points[j].X += Convert.ToInt32(PixelToShiftX); // points[j].Y -= Convert.ToInt32(PixelToShiftY); //calculate percentage of the positions respect to the range //note that max(rangeX, rangeY) will be "mapped" to the min(this.ClientSize.Width, this.ClientSize.Height) //so that we can handle different orientations int i = 0; double percentX, percentY; //to make the calculations hemisphere-independent, calculate percentages with absolute values foreach (DataRow row in table.Rows) { percentX = Math.Abs( (Math.Abs(double.Parse(row[Column2].ToString(), frmMain.Instance.DataCultureInfo)) - Math.Abs(minX)) / Math.Max(rangeX, rangeY) ); percentY = Math.Abs( (Math.Abs(double.Parse(row[Column1].ToString(), frmMain.Instance.DataCultureInfo)) - Math.Abs(minY)) / Math.Max(rangeX, rangeY) ); //discard the point if NaN if (!(double.IsNaN(percentX) || double.IsNaN(percentY))) { //now fill the Point array: percentX and percentY are non-negative values //PixelToShiftX and PixelToShiftY allow to center the image points[i].X = Convert.ToInt32(ShortestSideOfTheDevice * percentX) + Convert.ToInt32(PixelToShiftX); points[i].Y = ShortestSideOfTheDevice - Convert.ToInt32(ShortestSideOfTheDevice * percentY) - Convert.ToInt32(PixelToShiftY); ++i; } } } #endregion #region ACTUAL DRAWING using (Graphics g = this.CreateGraphics()) { using (SolidBrush b = new SolidBrush(Color.Indigo)) { g.Clear(Color.White); //if showing speed, distance, altitude: draw a polygon if (!bPosition) { g.FillPolygon(b, points); using (Font f = new Font(FontFamily.GenericSansSerif, 10.0f, FontStyle.Bold)) { b.Color = Color.Black; g.DrawString(string.Format("Max: {0} {1}", maxY.ToString("F3", frmMain.Instance.DataCultureInfo), unit), f, b, 0.0f, 0.0f); } } else //bPosition == true { //draw path area - this is a square independently on the device b.Color = Color.Honeydew; g.FillRectangle(b, 0, 0, ShortestSideOfTheDevice, ShortestSideOfTheDevice); //draw path grid, one line for each km (horizontal and vertical) double KmInRange = GpsLocation.CalculateLinearDistance( new GpsLocation(0.0f, 0.0f, 0.0f, string.Empty, string.Empty), new GpsLocation(0.0f, Math.Max(rangeX, rangeY), 0.0f, string.Empty, string.Empty)); using (Pen p = new Pen(Color.PaleTurquoise, 0.2f)) { for (int i = 0; i < Math.Ceiling(KmInRange); i++) { g.DrawLine(p, 20 + i * Convert.ToInt32(ShortestSideOfTheDevice / KmInRange), 0, 20 + i * Convert.ToInt32(ShortestSideOfTheDevice / KmInRange), ShortestSideOfTheDevice); g.DrawLine(p, 0, 20 + i * Convert.ToInt32(ShortestSideOfTheDevice / KmInRange), ShortestSideOfTheDevice, 20 + i * Convert.ToInt32(ShortestSideOfTheDevice / KmInRange)); } } //draw path using (Pen p = new Pen(Color.Blue, 1.0f)) { g.DrawLines(p, points); } //Start point b.Color = Color.Green; g.FillEllipse(b, points[0].X - 3, points[0].Y - 3, 6, 6); //Stop point b.Color = Color.Red; g.FillEllipse(b, points[points.GetLength(0) - 1].X - 2, points[points.GetLength(0) - 1].Y - 2, 4, 4); } } } #endregion Cursor.Current = Cursors.Default; }
The results of that on different platforms are, for path and speed:
NEXT STEP: now that I have played with representation of GPS data, I want to see how difficult it can be to show ON THE DEVICE a map by using Virtual Earth (through GPX file format)...
After that, now that I can really appreciate what the GPS Intermediate Driver can do for me (no need of Bluetooth and NMEA Intermediate layers!), I'll write a new version of the application: this time I'll also use SSCE 3.5 and probably the OrientationAware Application Block of the Mobile Client Software Factory... ehy! I won't re-invent the wheel: check out this project on Codeplex!!
P.S. Check out what the MVP Maarten Struys has to say in this recent Level 300-Webcast!
Creating Location-Aware Applications for Windows Mobile Devices (Level 300)
Ehy, check this out!! I felt really honored when my friend Chris proposed to interview me: he gave me the wonderful opportunity not only to present myself but above all to talk about the opportunities developers have with Microsoft Technical Support: make sure you get what you've paid for! This is one of the interviews Chris is conducting with some players in the Windows Mobile Development arena: really a good idea mate!!
- Useful samples on the web
- Bluetooth Shared Source possible enhancements
As I wrote in a previous post, in my case I wanted to play (once in my life!) with NMEA sentences and therefore didn't want to use GPS Intermediate Driver, at least for the first "release" of the (for-my-own-fun-) application: but now that I've done it (and the application is working indeed!), I'm sure that the next version will be based on it. Only when you don't have something you can really appreciate it when finally getting it...
Now, let's talk about useful samples on the web, not related to the GPS Intermediate Driver. A very good place to look for info for GPS-programming is GeoFrameworks. Their Mastering GPS Programming is a very good guide if you want to start programming location-aware applications. They even sell a set of controls, but I can't express on them since I didn't test them. Jon Person also made it available here.
Also, as usual when looking for NETCF sample code, OpenNETCF is the first place. Microsoft MVPs have done a really extraordinary job on providing NETCF developers with what was and is missing respect to the Desktop Framework through their Smart Device Framework, and some other components as well. For GPS they created the Shared Source Serial and GPS Library.
In any case, if you don't want to use GPSID, then you must provide a sort of "physical" layer responsible for retrieving data from the GPS antenna. In this case Microsoft comes to help, through the Windows Embedded Source Tools for Bluetooth Technology (my GPS Antenna was a paired Bluetooth one). I slightly modified it in order to better accomplish my goals, and probably it's worth mentioning here.
For example, in my Configuration Form I wanted to have a ComboBox showing the currently paired devices. In order to easily use the .DataSource property of the combo (or any other ListControl-based control) you may add a read-only "PairedDevicesArrayList" property:
/// <summary> /// ArrayList needed to easily fill a ListControl-based control (such as a Combo) through its .DataSource property /// </summary> public ArrayList PairedDevicesArrayList { get { ArrayList temp = new ArrayList(); foreach (BluetoothDevice dev in btRadio.PairedDevices) { temp.Add(new BluetoothDevice(dev.Name, dev.Address)); } return temp; } }
so that the UI could use something like:
cmbPairedDevices.DataSource = BtLayer.PairedDevicesArrayList; cmbPairedDevices.DisplayMember = "Name"; if (cmbPairedDevices.Items.Count != 0) cmbPairedDevices.SelectedIndex = 0;
The previous property is based on BluetoothDeviceCollection.PairedDevices, and I noticed that under the registry key containing paired devices info, there were some so-called "devices" I wasn't interested about, such as "activesync", "headset", "modem", etc. So I slightly modified the sample code here as well:
/// <summary> /// A collection representing Bluetooth devices which have been previously paired with this device. /// </summary> public BluetoothDeviceCollection PairedDevices { get { BluetoothDeviceCollection pairedDevices = new BluetoothDeviceCollection(); const string BT_DEVICE_KEY_NAME = "Software\\Microsoft\\Bluetooth\\Device"; IntPtr btDeviceKey = Registry.OpenKey(Registry.GetRootKey(Registry.HKey.LOCAL_MACHINE), BT_DEVICE_KEY_NAME); ArrayList subKeyNames = Registry.GetSubKeyNames(btDeviceKey); foreach (string deviceAddr in subKeyNames) { string deviceName = ""; byte[] deviceAddress = new byte[8]; IntPtr currentDeviceKey = Registry.OpenKey(btDeviceKey, deviceAddr); deviceName = (string)Registry.GetValue(currentDeviceKey, "name"); //RAFFAEL //2 possible checks: //1. the key name has some numbers //2. the key has a subkey "Services" <-- choose this, good enough for now (but this can be done better) ArrayList subKeyCheckList = Registry.GetSubKeyNames(currentDeviceKey); if (subKeyCheckList.Count != 0) //at least one subkey { foreach(object subkeyName in subKeyCheckList) { if ((string)subkeyName != "Services") //Services is not there break; //exit foreach else //Service is there { long longDeviceAddress = long.Parse(deviceAddr, System.Globalization.NumberStyles.HexNumber); BitConverter.GetBytes(longDeviceAddress).CopyTo(deviceAddress, 0); BluetoothDevice currentDevice = new BluetoothDevice(deviceName, deviceAddress); pairedDevices.Add(currentDevice); } } } Registry.CloseKey(currentDeviceKey); //END RAFFAEL } Registry.CloseKey(btDeviceKey); return pairedDevices; } }
Then, it's a matter of connecting to the selected paired device, through BluetoothDevice.Connect() method:
/// <summary> /// Connect to the Serial Port service of the paired device whose index within the Paired Devices collection is 'selectedIndex' /// </summary> /// <param name="selectedIndex">index of the paired devices collection</param> /// <returns></returns> public bool ConnectToSerialPortOfSelectedPairedDevice(int selectedIndex) { bool res = true; //check if paired device is correctly opened if (null == (gpsDevice = (BluetoothDevice)BtRadio.PairedDevices[selectedIndex])) { //MessageBox.Show("Can't find any paired device."); //or throw an exception that is caught by upper layers return false; } //try to connect to the Serial Port service for MAX_RETRIES times int count = 0; while ((null == (gpsStream = gpsDevice.Connect(StandardServices.SerialPortServiceGuid))) && (count < MAX_RETRIES)) { ++count; if (count >= MAX_RETRIES) { res = false; break; } } return res; }
And finally you can use the data to fill a buffer, for example, through the NetworkStream.Read() method. Note that if you then pass the buffer to a NMEA Interpreter, you must remember to "clean" its contents in case your algorithm doesn't guarantee that the buffer is always filled the same way. And remember that NMEA sentences don't have a fixed length, also because this depends on how antennas-manufacturers chose the "output pattern" and at which point in time the buffer was started being filled. If you forget to "reset" the buffer (as I was doing...) you may see that the application will start thinking that you're going back and forward from a location you were in a previous point in time...
Here you should also implement an algorithm to handle the loss of Bluetooth signal (which is something different from the "quality of the GPS signal").
//gpsStream is a NetworkStream object, representing the stream which reading data from. //BluetoothRadio and BluetoothDevice use WINSOCK to connect to the antenna ... bCanRead = this.gpsStream.CanRead; if (bCanRead) { //clean the buffer for (int i = 0; i<buffer.Length; ++i) buffer[i] = 0; //now read try { bRead0Bytes = (0 == this.gpsStream.Read(buffer, 0, BUFFERSIZE - 1)); } catch (System.IO.IOException ioex) { //If you receive an IOException, check the InnerException property to determine if it was caused by a SocketException. //If so, use the ErrorCode property to obtain the specific error code, and refer to the Windows Sockets version 2 API //error code documentation in MSDN for a detailed description of the error. if (ioex.InnerException.GetType() == typeof(SocketException)) { int socketerr = ((SocketException)(ioex.InnerException)).ErrorCode; throw new Exception(string.Format("SocketException - error code: {0}", socketerr)); } throw new Exception("IOException while trying to read from GPS"); } catch (Exception ex) { //throw exception to upper layers } }
In the next post I'll talk a bit about the intrinsic limitations of the GPS Signal Quality and possible way to reduce the errors. Among other concepts I'll show how to possibly exhibit location data through Drawing objects, in order to receive graphs like for example the following ones:
Some time ago I worked with a colleague of mine, a proud member of the CSS for Developers (Alejandro Campos Magencio) on a case where the developer wanted to encrypt and decrypt data on Windows Mobile 5.0 devices by using PFX certificates enrolled on the device. Crypto APIs are quite limited on this platform, however by using some basic (i.e. supported) APIs we were able to reach a satisfactory solution.
In very few words, PFX certificates contain a pair of public and private key: through the public you can encrypt, through the private you can decrypt. But... first of all, you need to register the certificate on the device's ROOT or Personal store. On Windows Mobile 6, an enroller was added to the platform therefore you can register PFX certificate by simply tapping on it, as it was already with .CER files on previous versions of the OS; but on Windows Mobile 5.0, you can't add a PFX certificate by doing so, nor you can use the documented way to add certificates by using the CertificateStore Configuration Service Provider, because contrarily to CER certificates, you can't export a .PFX to a Base-64 encoded X.509 stream (see Creating a Provisioning XML Document For The Root Certificate).
So, how to add a PFX certificate to the ROOT store of a Windows Mobile 5.0-based device? The answer is: develop your own enroller. Or, find out if anyone else already did it... and a sample 3rd party enroller for .PFX files is the one described by Personal Certificate Import Utility for Pocket PC 2003 and Windows Mobile, which seems to be still valid for 5.0 as well. You can also use directly that 3rd party open-source product if you want, but in case you’ll have problems we couldn’t be able to support as this is not a Microsoft product.
In any case, remember that "[…] Whether a root certificate can be installed on the device depends on how the device was configured by the original equipment manufacturer (OEM) or by the Mobile Operator", in terms of Security Configuration (this was an excerpt from the KB Article How to install root certificates on a Windows Mobile-based device), so if you can't even install the SDK Certificates, for example, then you have a locked device and therefore you should ask you OEM or Mobile Operator how they require you to install new certificates, if they even provide this opportunity.
Recommended readings:
Finally, here it is some pseudo-code (provided 'AS IS', for didactic purposes, no error-checking and no example data included - just look at which Crypto APIs are needed):
- for encrypting:
// Variables HCERTSTORE hStoreHandle = NULL; PCCERT_CONTEXT pSubjectCert = NULL; HCRYPTKEY hPubKey = NULL; wchar_t * SubjectName; DWORD dwEncryptedLen = 0; BYTE* pbData = NULL; // Open the certificate store hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, NULL); // Get a certificate that matches the search criteria pSubjectCert = CertFindCertificateInStore(hStoreHandle, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, SubjectName, pSubjectCert); // Get the CSP bResult = CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); // Get the public key from the certificate bResult = CryptImportPublicKeyInfoEx(hCryptProv, X509_ASN_ENCODING, &pSubjectCert->pCertInfo->SubjectPublicKeyInfo, CALG_RSA_KEYX, 0, NULL, &hPubKey); // Get lenght for encrypted data bResult = CryptEncrypt(hPubKey, NULL, TRUE, 0, NULL, &dwEncryptedLen, 0); // Create a buffer for encrypted data pbData = (BYTE *)malloc(pbData, dwEncryptedLen); // Encrypt data bResult = CryptEncrypt(hPubKey, NULL, TRUE, 0, pbData, &dwEncryptedLen, dwEncryptedLen); //CertFreeCertificateContext //CertCloseStore
- for decrypting:
// Variables HCERTSTORE hStoreHandle = NULL; PCCERT_CONTEXT pSubjectCert = NULL; wchar_t * SubjectName; HCRYPTPROV* phCryptProv = NULL; DWORD* pdwKeySpec = NULL; BOOL* pfCallerFreeProv = NULL; HCRYPTKEY* phPrivateKey; DWORD dwEncryptedLen = 0; BYTE* pbData = NULL; // Open the certificate store hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, NULL); // Get a certificate that matches the search criteria pSubjectCert = CertFindCertificateInStore(hStoreHandle, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, SubjectName, pSubjectCert); // Acquire the Private Key bResult = CryptAcquireCertificatePrivateKey(pSubjectCert, CRYPT_ACQUIRE_COMPARE_KEY_FLAG, NULL, &phCryptProv, &pdwKeySpec, &pfCallerFreeProv); // Get Private Key bResult = CryptGetUserKey(phCryptProv, AT_KEYEXCHANGE, &phPrivateKey); // Decrypt data bResult = CryptDecrypt(phPrivateKey, 0, TRUE, 0, &pbData, &dwEncryptedLen); //CertFreeCertificateContext //CertCloseStore
Other sample code is available from MSDN, not specific to PFX Certificates: