Published on by

I needed my custom keyboard layout "ported" to x64 edition of Windows and that is where the trouble started. MSKLC (Microsoft Keyboard Layout Creator) wasn't able to produce anything but 32-bit version of DLL which of course didn't work.

I started digging around and figured out that MSKLC is actually just a front-end for a C compiler, resource compiler and a linker, which were distributed along with it in the i386 folder. I had my custom layout as a .klc file and I believed that there must be some way to translate it to C code. I was right, there was also a command line tool named kbdutool.exe. I ran it from command prompt and it said:

KbdTool v3.13 - convert keyboard text file to C file

Usage: KbdUTool [-v] [-n] [-w] [-k] [-n] [-u|a] [-i|x] file

	[-?] display this message
	[-a] Uses non-Unicode source files (default)
	[-i] Builds for IA64
	[-m] Builds for AMD64
	[-n] no logo or normal build information displayed
	[-u] Uses Uncode source files
	[-v] verbose diagnostics (and warnings, with -w)
	[-w] display extended warnings
	[-x] Builds for x86 (default)

Aha! There it is, and it even has an AMD64 as an option. Neat? Not exactly. I tried various combination of switches and some of them worked while others didn't. For example:

kbdutool.exe -u -x my_layout.klc

produced my_layout.dll without any apparent intermediate steps or files. Changing -x to -i for example gave the following error message:

Error 2008 ((null), line 0):
Missing compiler CL.EXE (expected at 'C:\MSKLC\bin\i386\win64\cl.exe'). Please check your configuration.

It was clear that it was looking for IA64 cross-compiler but it couldn't find it since it wasn't shipped with it. But what about AMD64 you say? I tried using -m instead of -x and -i and the tool wrote out several files and exited.

I find it curious that kbdutool.exe has not complained about missing cl.exe for AMD64 this time like it did for IA64, but instead it produced the following files:

my_layout.C
my_layout.DEF
my_layout.H
my_layout.RC

Great, I have the source. Now just to figure out how to compile it correctly. That turned out to be the hardest part. It wasn't hard to obtain the right compiler and linker, they are part of the Platform SDK, but what are the correct compiler and linker options? After a bit of experimenting and not getting anywhere because .DLL I got refused to load I wrote a cl.exe, rc.exe and link.exe replacement (actually just one program which I renamed) that just opens a file in the root of drive C: named after the corresponding tool and containing the command line passed by the kbdutool.exe. So I ran kbdutool.exe again with -u -x and to cut the long story short I got the following commands:

C:\MSKLC\bin\i386\rc.exe -r -iC:\MSKLC\inc -DSTD_CALL -DCONDITION_HANDLING=1 -DNT_UP=1 -DNT_INST=0 -DWIN32=100 -D_NT1X_=100 -DWINNT=1 -D_WIN32_WINNT=0x0502 /DWINVER=0x0502 -D_WIN32_IE=0x0600 -DWIN32_LEAN_AND_MEAN=1 -DDEVL=1 -DFPO=1 -DNDEBUG -l 409 -FoC:\TEMP\my_layout.res C:\my_layout\my_layout.rc

C:\MSKLC\bin\amd64\cl.exe -nologo -IC:\MSKLC\inc -DNOGDICAPMASKS -DNOWINMESSAGES -DNOWINSTYLES -DNOSYSMETRICS -DNOMENUS -DNOICONS -DNOSYSCOMMANDS -DNORASTEROPS -DNOSHOWWINDOW -DOEMRESOURCE -DNOATOM -DNOCLIPBOARD -DNOCOLOR -DNOCTLMGR -DNODRAWTEXT -DNOGDI -DNOKERNEL -DNONLS -DNOMB -DNOMEMMGR -DNOMETAFILE -DNOMINMAX -DNOMSG -DNOOPENFILE -DNOSCROLL -DNOSERVICE -DNOSOUND -DNOTEXTMETRIC -DNOWINOFFSETS -DNOWH -DNOCOMM -DNOKANJI -DNOHELP -DNOPROFILER -DNODEFERWINDOWPOS -DNOMCX -DWIN32_LEAN_AND_MEAN -DRoster -DSTD_CALL -D_WIN32_WINNT=0x0502 /c /Zp8 /Gy /W3 /WX /Gz /Gm- /EHs-c- /GR- /GF -Z7 /Zl /Oxs -FoC:\TEMP\my_layout64.obj C:\my_layout\my_layout.C

C:\MSKLC\bin\i386\link.exe -nologo -base:0x5FFE0000 -merge:.edata=.data -merge:.rdata=.data -merge:.text=.data -merge:.bss=.data -section:.data,re -MERGE:_PAGE=PAGE -MERGE:_TEXT=.text -MACHINE:AMD64 -SECTION:INIT,d -OPT:REF -OPT:ICF -IGNORE:4039,4078 -noentry -dll -subsystem:native,5.2 -merge:.rdata=.text -PDBPATH:NONE -STACK:0x40000,0x1000 /opt:nowin98 -debugtype:cv,fixup -debug -osversion:5.2 -version:5.2 /release -def:C:\my_layout\my_layout.DEF -out:C:\my_layout\amd64\my_layout.dll C:\TEMP\my_layout.res C:\TEMP\my_layout64.obj

And that's it. Note that what you see above is the fixed version. I did have to change few things, and here is what I had to change:

  • -D_WIN32_WINNT and -DWINVER from 0x0501 to 0x0502
  • In C:\MSKLC\inc\winnt.h I had to comment out lines 1860 and 1861
  • I fixed a few deprecated cl.exe and link.exe switches
  • I found actual -base:0x5FFE0000 by disassembling one of the existing layouts
  • -subsystem, -osversion and -version from 5.1 to 5.2
  • -MACHINE:IX86 to -MACHINE:AMD64

Of course you have to use AMD64 or x86_AMD64 version of cl.exe with the above commands so make amd64 folder next to the i386 folder and put the compiler files there. Here is the list of files needed for cl.exe:

C:\MSKLC\bin\amd64\1033\clui.dll
C:\MSKLC\bin\amd64\c1.dll
C:\MSKLC\bin\amd64\c1xx.dll
C:\MSKLC\bin\amd64\c2.dll
C:\MSKLC\bin\amd64\cl.exe
C:\MSKLC\bin\amd64\mspdb80.dll

Now you are able to build your keyboard layout for x64 without having to resort to that clumsy DDK.

UPDATE 29.09.2006:

While I was doing all this I have stumbled upon this MSDN blog where MSKLC and its inability to produce keyboard layouts for x64 were mentioned. I suspected that the blog author has to do something with the tool since he got asked about x64 compatibility a lot. Therefore, I contacted Michael S. Kaplan, the Technical Lead from Globalization Infrastructure, Fonts, and Tools at Microsoft to let him know what I did here.

I wasn't expecting a response, I just thought it might be usefull for him to send the impatient people here so they can at least try to help themselves while waiting for the updated version of the tool. I was pleasantly surprised to find out that he actually posted about it!

If you just don't think you can hold it (64-bit style!)

After reading his post in which he commended my efforts (thank you Michael!), this part drew my attention:

Now of course the real support plan will include the real setup, and will also include the WOW64 dll as well (Mr Levicki's does not, which will cause some headaches for 32-bit applications running on 64-bit).

After contacting him again I realized that I have unconciously solved that problem too, only I forgot to write about it here because it didn't seem relevant. Actually, without making additional 32-bit DLL your layout wouldn't work at all for 32-bit applications and at the time of this writing 64-bit Word still didn't exist.

So, here is the missing part people. You need to make 32-bit version of DLL but it is a special one so it takes an extra step which makes it different from the 32-bit DLL MSKLC produces by default. Here are the commands to use:

C:\MSKLC\bin\i386\cl.exe -nologo -IC:\MSKLC\inc -DBUILD_WOW6432 -DNOGDICAPMASKS -DNOWINMESSAGES -DNOWINSTYLES -DNOSYSMETRICS -DNOMENUS -DNOICONS -DNOSYSCOMMANDS -DNORASTEROPS -DNOSHOWWINDOW -DOEMRESOURCE -DNOATOM -DNOCLIPBOARD -DNOCOLOR -DNOCTLMGR -DNODRAWTEXT -DNOGDI -DNOKERNEL -DNONLS -DNOMB -DNOMEMMGR -DNOMETAFILE -DNOMINMAX -DNOMSG -DNOOPENFILE -DNOSCROLL -DNOSERVICE -DNOSOUND -DNOTEXTMETRIC -DNOWINOFFSETS -DNOWH -DNOCOMM -DNOKANJI -DNOHELP -DNOPROFILER -DNODEFERWINDOWPOS -DNOMCX -DWIN32_LEAN_AND_MEAN -DRoster -DSTD_CALL -D_WIN32_WINNT=0x0502 /c /Zp8 /Gy /W3 /WX /Gz /Gm- /EHs-c- /GR- /GF -Z7 /Zl /Oxs -FoC:\TEMP\my_layout32.obj C:\my_layout\my_layout.C

C:\MSKLC\bin\i386\link.exe -nologo -base:0x5FFF0000 -merge:.edata=.data -merge:.rdata=.data -merge:.text=.data -merge:.bss=.data -section:.data,re -MERGE:_PAGE=PAGE -MERGE:_TEXT=.text -MACHINE:IX86 -SECTION:INIT,d -OPT:REF -OPT:ICF -IGNORE:4039,4078 -noentry -dll -subsystem:native,5.2 -merge:.rdata=.text -PDBPATH:NONE -STACK:0x40000,0x1000 /opt:nowin98 -debugtype:cv,fixup -debug -osversion:5.2 -version:5.2 /release -def:C:\my_layout\my_layout.DEF -out:C:\my_layout\i386\my_layout.dll C:\TEMP\my_layout.res C:\TEMP\my_layout32.obj

What is different here from the 64-bit build?

  • We use 32-bit compiler again.
  • Additional macro BUILD_WOW6432 is defined for compilation
  • Base address is 0x5FFF0000 instead of 0x5FFE0000
  • Machine type is IX86 again.

So how did I figure out that I need BUILD_WOW6432?

While researching this I noticed that the assembler code for the function exported from 32-bit DLL which resides in WINDOWS\SYSWOW64 folder differs from the one in the 32-bit DLL produced by MSKLC:

.data:5FFF2188 ; __stdcall KbdLayerDescriptor()
.data:5FFF2188                 public _KbdLayerDescriptor@0
.data:5FFF2188 _KbdLayerDescriptor@0 proc near
.data:5FFF2188                 mov     eax, offset off_5FFF2090
.data:5FFF218D                 cdq
.data:5FFF218E                 retn
.data:5FFF218E _KbdLayerDescriptor@0 endp

It had one instruction more — CDQ. CDQ means "Convert Double(Word) to Quad(Word)" meaning that the function returns its return value in EDX:EAX instead just in EAX — it returns 64-bit pointer. So I patched my DLL, fixed its checksum, put it in SYSWOW64 and forgot about it until I read Michael's post.

After he wrote that my solution is incomplete and after realizing that I have fixed it unconciously just for me, I wanted to find the right way of getting the same result without the need for hex editing. So I checked the KbdLayoutDescriptor() function definition in my_layout.C file:

PKBDTABLES KbdLayerDescriptor(VOID)
{
	return &KbdTables;
}

Then I searched the include directory for PKBDTABLES and I found the structure in kbd.h:

typedef struct tagKbdLayer {
	...
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;

Another search but this time for KBD_LONG_POINTER led me to the final solution:

#if defined(BUILD_WOW6432)
	#define KBD_LONG_POINTER __ptr64
#else
	#define KBD_LONG_POINTER
#endif

And that about sums it up. But what to do with the files? After you get your DLLs in C:\my_layout\i386 and C:\my_layout\amd64, you have to put them into the right place.

Remember that you can't use 32-bit file managers like Total Commander for example because they get redirected from SYSTEM32 to SYSWOW64 so you won't be able to put 64-bit DLL into the right folder.

Use the Windows Explorer instead and put the DLL from C:\my_layout\i386 to \WINDOWS\SYSWOW64 and the DLL from C:\my_layout\amd64 to \WINDOWS\SYSTEM32.

I will leave the details of how to activate the new layout to you. In my case, I just modified a registry key to point to my DLL instead to the system default DLL for my language because I am currently not aware of any better way to do it.

So there you are, now you have it all. What are you waiting for?

UPDATE 01.08.2007:

As of January 30, 2007 this tip has purely educational value because MSKLC 1.4 is now available for download, and you can use it to build keyboard layouts for all recent Microsoft operating systems including Windows Vista.