Output on customer input on MSLU's ReadConsoleInputW

by Michael S. Kaplan, published on 2007/01/13 06:01 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/01/13/1460724.aspx


There is a bug in the Microsoft Layer for Unicode on Win9x Systems in the ReadConsoleInput function.

This was actually just recently reported for the very first time (that I have ever heard of) this last Novermber. The report from Ross Ridge in the MSLU newsgroup said:

ReadConsoleInputW() seems to be broken using MSLU. The KEY_EVENT event, the only one that needs translation isn't handled. The event doesn't get copied to the destination buffer. Here's an example:

#include <windows.h>
#include <stdio.h>

int main() {
    int i;
    HANDLE console = GetStdHandle(STD_INPUT_HANDLE);

    if (console == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "GetStdHandle failed (%ld)", GetLastError());
        return 1;
    }

#if 1
    HMODULE unicows = LoadLibraryA("unicows.dll");

    if (unicows == NULL) {
        fprintf(stderr, "LoadLibraryA failed (%ld)", GetLastError());
        return 1;
    }
    BOOL _stdcall (*ReadConsoleInputW)(HANDLE, INPUT_RECORD *, DWORD, DWORD *) =
        ((BOOL _stdcall (*)(HANDLE, INPUT_RECORD *, DWORD, DWORD *))GetProcAddress(unicows, "ReadConsoleInputW"));
#endif

    for (i = 0; i < 10; i++) {
        static INPUT_RECORD ir;
        DWORD events;
        ir.EventType = -1;
        if (ReadConsoleInputW(console, &ir, 1, &events) == 0) {
            fprintf(stderr, "ReadConsoleInputW failed (%ld)", GetLastError());
            return 1;
        }
        printf("event %d\n", ir.EventType);
    }
    return 0;
}

If you type something on the keyboard you'll see output like:

event 65535
event 65535
event 65535
event 65535
event 65535
event 65535
event 65535
event 65535
event 65535
event 65535

instead of "event 1", which is KEY_EVENT.

The example code dynamically loads UNICOWS.DLL because ultimately I'm trying to use ReadConsoleInputW in Python using ctypes to call the function.

Well, Ross is at least partially correct here -- there is a bug in MSLU's ReadConsoleInput. Though it is not exactly the bug that Ross thought it was....

It turns out that MSLU is doing all the approproate work to copy over every piece of information of the INPUT_RECORD from its own call to ReadConsoleInputA, except for the EventType in the case where it is a KEY_EVENT. But everything other than the EventType value is copied, and in al other event types even that one WORD is copied too.

To work around this, all you have to do is make sure that you put a value in the EventType that is guaranteed to never exist on Win9x (-1 or 0xFFFF makes a good example of such a value), and if you get that value back when the function returns success, then you can be sure that MSLU did it's job.

One thought (that won't help the Python case of course) but in the C/C++ case you can even provide an override for the function that does this work and then calls the MSLU version; this will allow you to just write one bit of code for all platforms in the actual callers of ReadConsoleInput. Kind of like Ross actually did in his test (though assigning -1 to a WORD will not fare so well in all languages; 0xFFFF is safer!).

This is the kind of bug that, had it been reported several years ago, reallly any time before the last update that was done, would almost certainly have been fixed given the low degree of risk of assigning a WORD value. It hadd never been reported, however, and the tests on the function turned out to not be exhaustive as they might have been. And this is not a bug that at this point that is likely to ever be fixed in unicows.dll, given that MSLU is no longer officially supported and all.

But I figured it would at least be worth mentioning the bug and how to work around it in case anyone else ran across the problem. :-)

 

This post brought to you by W (U+0057, a.k.a. LATIN CAPITAL LETTER W)


# Yuhong Bao on 11 Jan 2009 3:39 PM:

"One thought (that won't help the Python case of course) but in the C/C++ case you can even provide an override for the function that does this work and then calls the MSLU version; this will allow you to just write one bit of code for all platforms in the actual callers of ReadConsoleInput. "

I just wrote that override:

typedef BOOL (WINAPI *PREADCONSOLEINPUTW)(
   __in   HANDLE hConsoleInput,
   __out  PINPUT_RECORD lpBuffer,
   __in   DWORD nLength,
   __out  LPDWORD lpNumberOfEventsRead
);

BOOL WINAPI HookReadConsoleInputW(
    __in   HANDLE hConsoleInput,
    __out  PINPUT_RECORD lpBuffer,
    __in   DWORD nLength,
   __out  LPDWORD lpNumberOfEventsRead
)
{
   if (!ReadConsoleInputA(hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead))
      return 0;

   DWORD i;
   for (i=0; i < *lpNumberOfEventsRead; i++)
   {
      if (lpBuffer[i].EventType == KEY_EVENT)
         MultiByteToWideChar(CP_ACP, 0, &lpBuffer[i].Event.KeyEvent.uChar.AsciiChar, 1, &lpBuffer[i].Event.KeyEvent.uChar.UnicodeChar, 1);
      }

   return 1;
}

extern "C" PREADCONSOLEINPUTW Unicows_ReadConsoleInputW = &HookReadConsoleInputW;

BTW, this was easy because KEY_EVENT_RECORD uses a union instead of using KEY_EVENT_RECORDW and KEY_EVENT_RECORDA, which means I can convert in-place instead of copying.

BTW, I am not sure for ReadConsoleInput if I should use CP_ACP or CP_OEMCP for the conversion.

Yuhong Bao on 16 Dec 2010 4:02 PM:

"BTW, I am not sure for ReadConsoleInput if I should use CP_ACP or CP_OEMCP for the conversion."

It is GetConsoleCP().


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