Code Repo    |     RSS
MD's Technical Sharing



Saturday, July 24, 2010

An RTF Editor Control for .NET Compact Framework

As you may have known, there is no RichTextBox control in .NET Compact Framework. This creates a lot of headaches for developers who want to display RTF-formatted text in their program. The only way is to use the RichInk control. However, there is no good free managed wrapper for it, even the OpenNetCF's InkX wrapper is still untidy and requires a lot of P/Invoke calls to make it work properly. IntelliProg used to provide a working control but the company has seemingly disappeared (their domain is parked). Until 2011, DSRTech also sold an expensive commercial solution but the page for this control has since been removed from their website.

AgileNotesTouch with RichInk control

After some research, I found a freeware called AgileNotes Touch which allows user to write notes and save it into TXT, PWI or RTF. Since the source code is not available, I decided to go ahead decompiling the executable using Reflector. This was an easy task - the decompiled source code, with only some minor modifications, compiled and ran as if it were the original. It turned out AgilesNotes Touch also uses the RickInk control, with some nice managed .NET wrappers which can be re-used easily.

Creating RTF programmatically: NRtfTree

Since I need to create the RTF document programmatically, I decided to use the open source project NRtfTree. Some basic formatting worked well until I tried to create an RTF document with images such as this. The document was created properly but to my disappointment the image was not displayed when the document was open with my program and with Pocket Word. On the other hand, Wordpad, and Office Word, displayed the image just fine. It took me a long time before I figured out the cause - the RichInk control (and the RichEd50W control used by Pocket Word) only supports binary images, and not hex images. For example the following picture will be discarded:

{\pict\pngblip\picw10449\pich3280\picwgoal5924\pichgoal1860 hex data}

but the following will work:

{\pict\pngblip\picw10449\pich3280\picwgoal5924\pichgoal1860\bin binary data}

And since NRtfTree only supports hex image, I had to modify the library to support binary image. What a hassle, but in the end everything worked well and images are displayed properly.

From RichInk to RichEdit50W

Next, I noticed that my control could not display images on the same line as text and seem to drop tables, while Pocket Word displayed them just fine. This is a hint that Pocket supports higher RTF versions then RichInk, so I use Remote Spy to find out which control Pocket is using:


PocketWord never used RichInk, but RichEd50W, similiar to what RichTextBox is using. I changed my code to use this window class (it stays in RichEd20.dll):

WindowsAPIs.LoadLibrary("RichEd20.dll");
this.m_RichInk = new WindowHost("RICHEDIT50W", 0, 0);

and images are displayed as intended. Surprisingly that's all I need - my program is now as good as Pocket Word.

Making the document read-only

My main purpose was to display documents - and not allowing user to modify them so I applied ES_READONLY to the window style:

this.m_RichInk = new WindowHost("RICHEDIT50W", 0, ES_READONLY);

There is still a minor problem: the window display may be distorted or incomplete after some extensive usage. I have yet to figure out the cause of the problem or how to fix it.

Source code

The sample source code is here. It includes the modified NRtfTree library, the decompiled source code of AgilesNotes Touch, and a sample form showing you how to read and write RTF files. NRtfTree was modified to insert binary images, instead of hex, and to support unicode characters. I have commented clearly where I modified the code.

I hope this will help other developers facing the same problem.
Read More »

Saturday, July 17, 2010

Beware when using DES/TripleDES for data encryption

I recently worked on a secure device application which requires a PIN code to be entered as startup.The application database also needs to be encrypted. My first approach was to use a DESCryptoServiceProvider, which is among the few supported encryption methods in the .NET Compact Framework,  to encrypt the database using the PIN code itself as the secret key.

My full code can be found here.

Soon after, I discovered the problem. The encrypted database can be decrypted if the secret key provided is slightly wrong. For example, if the secret key I used when encrypting is 12345678, the decryption will work with both 12345678 and 12345778 (wrong value). Thinking that this was almost certainly a bug, I went ahead to file a report at Microsoft Connect.

I did not have to wait until Microsoft responsed me (which was 3 days later) for an answer. Soon after I read the Wikipedia article on DES, I immediate realized my mistake. The key ostensibly consists of 64 bits; however, only 56 of these are actually used by the algorithm. Eight bits are used solely for checking parity, and are thereafter discarded. This explains why the wrong secret key can be used to decrypt the data as described above.

I changed to TripleDES only to observe the same behaviour. As AES is not supported on the compact framework, I end up encrypting the database using an MD5 hash of the PIN code. Although you may argue that MD5 has some chance of collision, the above hash + encryption combination will ensure, at least with ultimate possibility, that there is no way for the database to be decrypted without the correct password.
Read More »

Thursday, July 15, 2010

Intercept incoming SMS message on HTC Phones with HTC Sense

In my previous post I have explained the problem with MessageInterceptor on new HTC Phones and how to temporary fix it it by installing the patch. Although this allows existing applications to work without any code changes, user will have to use Pocket Outlook, which has an inferior user interface compared to HTC Messages, to send SMS. MMS are not supported until the Arcsoft MMS client is installed. HTC Sense will not be able to show new incoming text messages, which is a major limitation.

Luckily there is a way to intercept incoming text messages without installing any patch. However, you will have to make some code changes to your application. The idea is that, although the .NET MessageInterceptor class does not work, the SDK MapiRule sample (C:\Program Files\Windows Mobile 6.5.3 DTK\Samples\Common\CPP\Win32\MapiRule) works just fine. This post shows you how to modify the MAPIRule sample code to integrate it with your .NET project

Modifying MAPIRule.dll

You will not have to touch most of the MAPIRule code, except for modifying the GUID (important!) and the ProcessMessage() function (the sample just displays the incoming text message received in a MessageBox). Although you can parse the incoming message and process it directly inside ProcessMessage(), I decided on the following to make things clearer:
  1. When MapiRule.dll received a new SMS message in ProcessMessage(), it simply send a Win32 message via SendMessage to the host application.
  2. Upon received the specific Win32 message, host application, which is a .NET application, will decide on whether or not to intercept the new SMS message (it will not appear in phone Inbox) or let the default messaging application handle it.
  3. Based on the result of SendMessage(), MapiRule.dll will act accordingly.
This interprocess communication can be easily done by using the Win32 API SendMessage() and the .NET Compact Framework's MessageWindow class. Although you can specify your own message ID, I have chosen WM_COPYDATA since MapiRule.dll needs to send the SMS text and sender number to the host application, otherwise string pointers to message text and sender numbers cannot be shared. The sample code is below. Notice that newMsgWin is the handle to the host application's window. The sender number and message text is concatenated into a single string to be sent out.

HRESULT CMailRuleClient::ProcessMessage(IMsgStore *pMsgStore, ULONG cbMsg, LPENTRYID lpMsg, ULONG cbDestFolder, LPENTRYID lpDestFolder, ULONG *pulEventType, MRCHANDLED *pHandled)
{
............
        TCHAR msgInfo[500];
        COPYDATASTRUCT cds;

        // we concatenate the sender number and the message text, to be sent to the host application
        memset(msgInfo, 0, sizeof(msgInfo));
        wcscpy(msgInfo, pspvEmail->Value.lpszW);       
        wcscat(msgInfo, L"\n");
        wcscat(msgInfo, pspvSubject->Value.lpszW);
              
        //length for the recipient to know
        cds.dwData = wcslen(pspvEmail->Value.lpszW) + wcslen(pspvSubject->Value.lpszW) + 1;

        cds.cbData = sizeof(msgInfo);          //msg size in bytes
        cds.lpData = &msgInfo;            //pointer to the information to be sent

        // tell the main app about the text message
        if (SendMessage(newMsgWin, WM_COPYDATA, 0, (LPARAM) &cds) == (LRESULT) 1)
        {
            // a LRESULT of 1 means that the message was processed by parent application
            // so we delete the message and mark it as handled so it won't show up in Inbox
            hr = DeleteMessage(pMsgStore, pMsg, cbMsg, lpMsg, cbDestFolder, lpDestFolder, pulEventType, pHandled);
        }
        else
        {
            // other LRESULT means message not handled by main app, pass it on
            *pHandled = MRC_NOT_HANDLED;               
        }
............
}

Everything is straightforward, except that the lpData member of COPYDATASTRUCT structure has to be part of the structure memory area. This explains why an array of TCHAR, an not LPWSTR is used. If this is not followed, application may work or crash randomly without any indication why.

UPDATE (26 May 2012): As suggested by a reader, the above code may have problem with Unicode messages since sizeof(wchar_t) is 2 on Windows, resulting in incorrect value for the data length, dwData. If you have problems with truncated messages, try this:

cds.dwData = 2*wcslen(pspvEmail->Value.lpszW) + 2*wcslen(pspvSubject->Value.lpszW) + 1;

The updated MAPI DLL with the fix can be downloaded from here

The .NET code is as follows:

    [StructLayout(LayoutKind.Sequential)]
    private struct CopyDataStruct
    {
        public int IntData;
        public int Length;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string Data;
    }

public class NewMsgWindow : MessageWindow
{
..........
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_COPYDATA) //we received a message telling us that there is an incoming SMS
        {
            CopyDataStruct cs = (CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(CopyDataStruct));

            if (cs.Data.Contains(delim))
            {
                string sender = cs.Data.Split(delim)[0];
                string messageText = cs.Data.Split(delim)[1];

                if (true)
                {
                    // a LRESULT of 1 marks message as processed.
                    // and native msg app will not show msg in Inbox
                    m.Result = new IntPtr(1);
                }
                else
                    // Other LRESULT indicates we did not intercept the message
                    // and the message will be passed on to native messaging application.
                    m.Result = new IntPtr(0);
            }
        }
.......
}

The challenge is to receive the WM_COPYDATA message and marshal it back to a string. Most sample codes use GetLParam(), but as this is not supported by .NET CF, we have to use Marshal.PtrToStructure(). As commented, the .NET code will response with a result of either 1 or 0.

Source code

The sample code is here. Some part of the code was taken from the Remote Tracker open source project which also intercepts incoming text messages. Remote Tracker source code, however, parses the text message directly inside MapiRule.dll.

Setting up

Some registry keys need to be modified for MapiRule.dll to be used. This can either be done via a CAB file, or via the CreateInterceptorMethod2() method in the code. A reboot is needed for changes to take effect. If you need to change MapiRule.dll, you will have to remove the registry keys via RemoveInterceptorMethod2(), reboot the device, update the dll, call CreateInterceptorMethod2() and reboot again! Terminating poutlook.exe and tmail.exe may also help to reduce the number of restarts on some devices. This makes the development process very time consuming. Debugging MapiRule.dll is possible by attaching the debugger to tmail.exe.

Issues

There are still some minor issues yet to be solved. On HTC HD2, for some reasons, all text messages received will have the string " - GSM" appended at the end (refer to this discussion). Also, the total length of the sender name and the message text cannot exceed 500 since the TCHAR array length is hard coded. If you need to receive longer text message, the interprocess communication algorithm has to be modified to send and receive the text message part by part. You cannot simply increase the array length, as it will cause a stack fault.

Last but not least, the approach described here works on all devices, including HTC Devices. So it can be used as a replacement for the MessageInterceptor class. In a sense, it's better as you can decide, at the point of receiving, which message to be processed, instead of pre-creating a set of MessageInterceptor conditions and re-creating them should the interception conditions change.
Read More »

Wednesday, July 7, 2010

Common problems with .NET CF WebBrowser controls

Although most .NET developers will immediately think of the WebBrowser control when it comes to displaying HTML-formatted document, that seems to be more true for the full .NET framework rather than the compact framework. In fact, if your smart device application has to support a wide variety of devices, below are some reasons why the use of the WebBrowser control is discouraged:

1. Inconsistent HTML support across different devices. Although simple formatting like bold, italic, underline and tables tend to work well, more complicated constructs (css, javascripts) will be dropped or simplified by many devices. There is no way to know for sure except for testing.

2. WebBrowser.Navigate() opens a new window. On many devices, especially on devices where Opera is the default web browser, calling WebBrowser.Navigate() on a local file will simply open a new Opera browser window and leave the application. This will prevent many applications relying on the WebBrowser control from working.

A workaround is to set back IE as the default web browser by modifying registry key, see this. Another workaround is not to use the extension ".htm" or ".html" when calling .Navigate(). For example, instead of

WebBrowser1.Navigate("\\test.htm");

use:

WebBrowser1.Navigate("\\test.tmp");

The document contents will still be rendered properly despite the ".tmp" extension. This trick, however, does not help if you want to the web browser to navigate to an external URL, e.g. http://www.google.com.

Also, on many devices, the WebBrowser.Navigating event never fires, which I could not find a workaround for it.

3. Local images not rendered when DocumentText is set. This may seem a trivial problem with image path or format, but it's actually not. The problem is particular to .NET Compact Framework 3.5 running on Windows Mobile 6.5. The web browser control will refuse to render any local images and only display placeholder if the HTML is set via the DocumentText properly. Strangely it will display remote images from a web server. Images are also displayed correctly if the DocumentText is written to a file and loaded by calling Navigate(). The problem never appears on older .NET Compact Framework or Windows Mobile versions.

All the above seemingly trivial problems are just making life harder for the Windows Mobile developers.
Read More »

Saturday, July 3, 2010

Blackberry and phone numbers with text

Recently I received an advertisement SMS from Standard Chartered Bank having "StanChart" as the Sender ID. I accidentally pressed the dial (green) key and to my surprise my phone started to call 782624278. All I could get was, however, an automatic IVR announcement saying that the number is invalid. For a moment I wondered where the phone retrieved the number from, as I did not have an entry name 'StanChart' in my phonebook, and certainly would never store an invalid number in my phone book.

The answer is that, Blackberry supports auto-detection of text in phone numbers, For example, you can have a contact with 1800-CALL-LTA as its number, and the phone will automatically call 1800-2255582  (Singapore Land Transport Authority) for you. My case was a similiar example, the phone automatically translated "StanChart" to 782624278 and dialled the number for me.

Although the first case (1800-CALL-LTA) is no doubt very useful to assist users in memorising the phone number, automatically conversion of an SMS sender ID to a phone number is sort of an overkill and result in the wrong number being called.

The same behaviour can be seen on iPhone as well. Windows Mobile, however, does not support the feature.
Read More »