Getting all you can out of a keyboard layout, Part #2

by Michael S. Kaplan, published on 2006/03/24 03:01 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/03/24/558715.aspx


Previous posts in this series: Part 0 and Part 1.

The most immediate and nagging problem for me is the fact that the UnloadKeyboardLayout call will unload the keyboard even if you had it loaded already as one of your many "installed" keyboard layouts.

That stinks.

But the problem is that there is really no way to know if a keyboard is already loaded other than using the following algorithm:

To get that list, there are two different approaches -- one managed and one unmanaged. The unmanaged one uses the GetKeyboardLayoutList function, and the managed one uses the static InputLanguage.InstalledInputLanguages property.

Let's take a look at two possible ways to do this (the older code from the post #1 is gray, the new code is black).

#1: The managed solution:

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace KeyboardLayouts {
    class Class1 {

        //  You'll want to insert that enumeration here!

        internal const uint KLF_NOTELLSHELL  = 0x00000080;

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

        [DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="LoadKeyboardLayoutW", ExactSpelling=true)]
        internal static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);

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

        [DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
        internal static extern int ToUnicodeEx(
            uint wVirtKey,
            uint wScanCode,
            KeysEx[] lpKeyState,
            StringBuilder pwszBuff,
            int cchBuff,
            uint wFlags,
            IntPtr dwhkl);

        [STAThread]
        static void Main(string[] args) {
            InputLanguageCollection rgil = InputLanguage.InstalledInputLanguages;
            IntPtr hkl = LoadKeyboardLayout(args[0], KLF_NOTELLSHELL);
            if(hkl == IntPtr.Zero) {
                Console.WriteLine("Sorry, that keyboard does not seem to be valid.");
            }
            else {
                KeysEx[] lpKeyState = new KeysEx[256];

                for(uint sc = 0x01; sc <= 0x7f; sc++) {
                    uint vk = MapVirtualKeyEx(sc, 1, hkl);
                    if(vk != 0) {
                        StringBuilder sb = new StringBuilder(10);
                        int rc = ToUnicodeEx(vk, sc, lpKeyState, sb, sb.Capacity, 0, hkl);
                        if(rc == 1) {
                            Console.WriteLine("{0:x2}\t{1:x4}\t{2:x2}\t{3}\t{4}",
                                sc, ((ushort)sb.ToString()[0]), vk, ((KeysEx)vk).ToString(), ((Keys)vk).ToString());
                        }
                    }
                }
                foreach(InputLanguage il in rgil) {
                    if(hkl == il.Handle) {
                        hkl = IntPtr.Zero;
                        break;
                    }
                }

                if(hkl != IntPtr.Zero) {
                    UnloadKeyboardLayout(hkl);
                }
            }
        }
    }
}

And #2, the unmanaged one:

 

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace KeyboardLayouts {
    class Class1 {

        //  You'll want to insert that enumeration here!

        internal const uint KLF_NOTELLSHELL  = 0x00000080;

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

        [DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="LoadKeyboardLayoutW", ExactSpelling=true)]
        internal static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);

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

        [DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
        internal static extern int ToUnicodeEx(
            uint wVirtKey,
            uint wScanCode,
            KeysEx[] lpKeyState,
            StringBuilder pwszBuff,
            int cchBuff,
            uint wFlags,
            IntPtr dwhkl);

        [DllImport("user32.dll", ExactSpelling=true)]
        public static extern int GetKeyboardLayoutList(int nBuff, [Out, MarshalAs(UnmanagedType.LPArray)] IntPtr[] lpList);

        [STAThread]
        static void Main(string[] args) {
            int cKeyboards = GetKeyboardLayoutList(0, null);
            IntPtr[] rghkl = new IntPtr[cKeyboards];
            GetKeyboardLayoutList(cKeyboards, rghkl);
            IntPtr hkl = LoadKeyboardLayout(args[0], KLF_NOTELLSHELL);
            if(hkl == IntPtr.Zero) {
                Console.WriteLine("Sorry, that keyboard does not seem to be valid.");
            }
            else {
                KeysEx[] lpKeyState = new KeysEx[256];

                for(uint sc = 0x01; sc <= 0x7f; sc++) {
                    uint vk = MapVirtualKeyEx(sc, 1, hkl);
                    if(vk != 0) {
                        StringBuilder sb = new StringBuilder(10);
                        int rc = ToUnicodeEx(vk, sc, lpKeyState, sb, sb.Capacity, 0, hkl);
                        if(rc == 1) {
                            Console.WriteLine("{0:x2}\t{1:x4}\t{2:x2}\t{3}\t{4}",
                                sc, ((ushort)sb.ToString()[0]), vk, ((KeysEx)vk).ToString(), ((Keys)vk).ToString());
                        }
                    }
                }
                foreach(IntPtr i in rghkl) {
                    if(hkl == i) {
                        hkl = IntPtr.Zero;
                        break;
                    }
                }

                if(hkl != IntPtr.Zero) {
                    UnloadKeyboardLayout(hkl);
                }
            }
        }
    }
}

Unfortunately, the managed solution cannot make good use of the InputLanguageCollection.Contains method, which feels a bit more elegant to me -- since that method is expecting an InputLanguage object, not an HKL. It seems like this would be a good overload to consider adding.

Though if they were thinking along those lines (or if they are reading this post and thinking about features for a future version!) I would not mind having them wrap the LoadKeyboardLayout and UnloadKeyboardLayout calls, too....

In the meantime, which of the two approaches is better?

Some people kind of religiously want to avoid p/invoke when they can, and the irony of their belief given the veritable buttload of p/invokes built into the .NET Framework is something they usually miss.

I didn't look at the source in InputLanguage, but I suspect that it looks pretty similar to that call to GetKeyboardLayoutList anyway. And since I did not want to tie the sample to managed code only, I figured putting both in there as options would make it easier to look at the one you like best. :-)

The important issue is to do your best to make sure the program does not affect the list of installed keyboards in any discernable way, and both of these methods are pretty much equivalent....

Tune in to the next post in the series for our ever expanding keyboard interrogator....

 

This post brought to you by "2" (U+0032, DIGIT TWO)
A Unicode character that is in the very small family of those whose VK value is the same as it's code point!


no comments

referenced by

2008/09/29 What a tangled web we weave when a KLID from an HKL we must receive

2006/04/22 Getting all you can out of a keyboard layout, Part #10a

2006/04/13 Getting all you can out of a keyboard layout, Part #9b

2006/04/12 Getting all you can out of a keyboard layout, Part #9a

2006/04/10 Getting all you can out of a keyboard layout, Part #8

2006/04/06 Getting all you can out of a keyboard layout, Part #7

2006/03/31 Getting all you can out of a keyboard layout, Part #6

2006/03/28 Getting all you can out of a keyboard layout, Part #5

2006/03/27 Getting all you can out of a keyboard layout, Part #4

2006/03/24 Getting all you can out of a keyboard layout, Part #3

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