What's the problem with MapVirtualKey[Ex], on CE and otherwise?

by Michael S. Kaplan, published on 2007/03/25 19:32 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/03/25/1948887.aspx


Also over in the microsoft.public.win32.programmer.international newsgroup, Norman Diamond (who is clearly doing a lot of stuff with CE these days!) asked:

In Windows Mobile 5 (Windows CE 5), when calling MapVirtualKey with the second parameter set to 2,
http://msdn2.microsoft.com/en-us/library/ms911789.aspx
says:

*  uCode is a virtual-key code and is translated into an unshifted character
*  value in the low-order word of the return value. Dead keys (diacritics)
*  are indicated by setting the top bit of the return value. If there is no
*  translation, the function returns 0.

The fact is that when uCode is virtual-key code 0x27 (VK_RIGHT), MapVirtualKey doesn't return 0, it returns 0x27.

So even my programming to produce a reverse translation table (since Windows CE lacks the misnamed VkScanEx API) gets screwed, and applications that use it get screwed.

Although I'm reading MSDN in English, most of the target environments are other foreign languages and the character code to VK code mapping is not constant.  I need the correct table.  How can I compute it?

Well, it has been many years since I have done any CE programming at all (the last time is when I was working as a contract PM in CE Services, over six years ago!). But I'll speak of the things I know of, and then make some informed guesses as to the rest.... :-)

First, there is the piece of this doc that is a bug -- even in the non-CE case.

The claim they always make is that for one of the mappings, "uCode is a virtual-key code and is translated into an unshifted character value in the low-order word of the return value."

Well, perhaps I am on drugs for thinking of this, but for me the definition of an unshifted character in this context is the character that is produced when you hit the key without any of the shift keys.

Beyond that, in a quick test here:

using System;
using System.Runtime.InteropServices;

namespace Testing {
    class MappingIsScrewy {
        [DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
        internal static extern IntPtr LoadKeyboardLayoutW(string pwszKLID, uint Flags); 

        [DllImport("user32.dll", ExactSpelling=true)]
        internal static extern bool UnloadKeyboardLayout(IntPtr dwhkl); 

        [DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
        internal static extern uint MapVirtualKeyExW(uint uCode, uint uMapType, IntPtr dwhkl); 

        [STAThread]
        static void Main(string[] args) {
            IntPtr hkl = LoadKeyboardLayoutW("00000419", 0);
            Console.WriteLine(hkl.ToString("x8"));
            Console.WriteLine(MapVirtualKeyExW(0x41, 2, hkl).ToString("x8"));
            Console.WriteLine(MapVirtualKeyExW(0x27, 2, hkl).ToString("x8"));
            Console.WriteLine(UnloadKeyboardLayout(hkl));
        }
    }
}

The console output of this code will be:

04190419
00000041
00000000
True

This is an uppercase letter being returned on that second line. And an uppercase A (U+0041), no less, when VK_A (0x41) is passed. Even though the Russian keyboard actually puts a U+0444 (CYRILLIC SMALL LETTER EF) at VK_A.

Thus MapVirtualKey and MapVirtualKeyEx both will return the uppercase character on Windows, and furthermore even if you call MapVirtualKeyEx with the HKL of a non-English keyboard, the uppercase "A" is still being returned. 

Which probably just means the VK is being returned. This does seem to limit the usefulness of MapVirtualKeyEx.

MSKLC never ran across a problem here as it only used mapping type 3 -- MAPVK_VSC_TO_VK_EX, not mapping type 2 -- MAPVK_VK_TO_CHAR. The more useful function for us was always ToUnicodeEx, since the tool is only ever interested in the actual character (including the shift state when applicable). It also understands properly the notion of "unshifted character". :-)

Oh well, at least on Windows if the VK does not map to a character, it will not even return the VK; the function will return 0.

On the other hand, according to Norman on CE it will not even do that. :-(

I guess that means if you to get the right character information (on ANY platform), use ToUnicodeEx and friends instead. Those mapping functions are a whole lot better at the SC <--> VK type mappings....

 

This post brought to you by ф (U+0444, a.k.a. CYRILLIC SMALL LETTER EF)


# Mike Dimmick on 26 Mar 2007 4:58 AM:

I don't know whether Norman's trying to do what I needed to, but here goes: when implementing a DOS(-like) emulator on CE, I needed to know, when a key was pressed, *both* the virtual key code *and* the character produced by that key. WM_KEYDOWN doesn't tell you the character code, and WM_CHAR doesn't tell you the virtual key code (and doesn't even occur if there is no printable character).

So I made a horrible hack: my message pump already calls TranslateMessage (which as we all know queues a WM_CHAR/WM_SYSCHAR message if the current message is WM_KEYDOWN). Therefore, in my WM_KEYDOWN handler I call PeekMessage, filtering for WM_CHAR sent to the same window. If it occurs, I assume that this character corresponds to the virtual key in the WM_KEYDOWN message. This has worked very reliably so far. I can see it will have trouble if another thread posts WM_CHAR messages rather than using SendInput but I've not seen a problem so far, and that includes using the Remote Display Control to remotely operate the device.

# Michael S. Kaplan on 26 Mar 2007 5:04 AM:

Yikes!

I'd probably prefer using ToUnicodeEx to get the character data out, rather than that TranslateMessage call. A lot less that can possibly go wrong, all things considered -- and a lot easier top fix up the buffer if there are dead keys to worry about....

# Mike Dimmick on 26 Mar 2007 5:59 PM:

CE does not have ToUnicode[Ex]. Sadly.

The design of Windows CE was originally to drop anything and everything that duplicated some other piece of functionality. In the case of conflicts, normally the simpler thing was dropped. So instead of LineTo and MoveToEx, we only had Polyline (or at least originally - looks like they were added in CE 4.0!) This is my favourite example because the .NET Compact Framework team also dropped duplicated functionality, except that they tended to select only the simplest method, so Graphics.DrawLine *was* present in CF 1.0 but Graphics.DrawLines *wasn't*. That meant that if you actually needed to draw multiple line segments you had to make multiple DrawLine calls, which in turn were calling Polyline, each of which resulted in a cross-process call to GDI, which meant that performance sucked.

[Ahem. Rant over.]

I admit I hadn't considered dead keys, but then we're generally porting software from the pseudo-US-keyboard Symbol Series 3000 to English (US) Windows CE devices so the problem shouldn't arise.

# Sam on 1 Jun 2007 10:52 PM:

I used ToUnicode in my application, but


go to newer or older post, or back to index or month or day