当前位置:C++技术网 > 精选软件 > 键盘OEM扫描码深入分析以及OEM扫描码与键盘虚拟键码的关系

键盘OEM扫描码深入分析以及OEM扫描码与键盘虚拟键码的关系

更新时间:2016-01-06 19:21:41浏览次数:1+次

    OEM即Original Equipment Manufacturer,原始设备制造商。OEM就是指设置制造者的意思。键盘为了确定每一个按键被按下时能够准确的识别是哪个按键,并知道哪个按键代表什么意义,因此键盘厂商给没有给键定义了一个编号。每一次按下按键的时候,键盘都会通过电子电路来确定哪个按键被按下了。通常叫这个过程为扫描按键的过程。而扫描得到的按下的按键的编码,就被称为键盘的扫描码。
    每一个键盘都由一个厂商生产,因为每一个键盘安排的扫描码一般都不会一样。这扫描码就是键盘厂商定义的,所以也称为OEM扫描码。微软为了提高兼容性和扩展性,并没有将识别按键的任务给键盘厂商。让各个厂商来协商确定一个标准,这已不是微软要做的事情,而且不会这么做的,没必要。所以微软定义的一套键码就是我们常常听说的虚拟键码。这个虚拟键码就对应着唯一表示这一个按键的意义,而虚拟键码又和OEM扫描码来映射一次。微软提供标准的映射接口,键盘驱动程序扫描得到扫描码之后,向上提供到对应的接口即可。虚拟键码的分析,请参考文章《虚拟键码的所蕴含的Windows系统的设计思想之一》。

    因为键盘的按键的扫描码是与按键所在的位置有关的,这通常由电子电路设计来确定的。这样的话,不同的键盘布局,扫描码就不会一样。所以,虚拟键码就避开了这个问题。如果你的程序是直接检测OEM扫描码的话,就与硬件依赖了。如果换成其他的键盘,布局就不一样,那么你的程序就工作不好了。在同一个位置的扫描码都不一样。下面是我的键盘的扫描码图:

键盘OEM扫描码分布图,只标注了部分

【键盘OEM扫描码分布图,只标注了部分】
    在图中可以看到,我的键盘的扫描码得到的ESC键的OEM扫描码是1,但是你的键盘就可能不是1了。这个和具体的键盘有关。这就是与硬件相关的了。
    所以,你想你编写的程序不受具体的键盘硬件种类影响,就只要使用虚拟键码即可。虚拟键码在WM_KEYDOWN和WM_SYSKEYDOWN消息的wParam中携带着。你只要将这个参数与Windows定义的虚拟键码宏比较即可实现与键盘硬件设备无关。只要是键盘,就可以正常的工作。
    一般人不懂OEM扫描码自然也用不到扫描码,也不会出现设备相关带来的问题。这是小白的幸福。然而如果你想处理OEM扫描码,也是可以的。只不过一般不推荐使用,Windows中基本上都不会用OEM扫描码来确定按键的。但是如果你确实想使用OEM扫描码为某种键盘编写程序,也是可以的。
    在键盘按下的消息的lParam参数中,携带有OEM扫描码的值。OEM扫描码是lParam参数的高字部分的低字节。所以,我们要使用一些宏来帮助我们来提取这个字节,宏的使用有HIWORD和LOBYTE,使用方法请阅读《DWORD、WORD的高低部分提取和合成宏代码示例》。
    那么我们就使用如下代码即可提取OEM扫描码:
BYTE btOEM = LOBYTE(HIWORD(lParam));//提取OEM扫描码
    提取出来的OEM扫描码和虚拟键码差不多,也是一个数字编码。系统按键需要在WM_SYSKEYDOWN消息中提取,比如F10、Alt等。
    程序的效果图如下:
【键盘产生的OEM扫描码】
    下面是提取OEM扫描码的完整代码:
#include "windows.h"
#include <tchar.h>
#define MAX 100
TCHAR tip[MAX]=_T("");

// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static int iPosInput=0;
    TEXTMETRIC tm;
    switch (message)
    {
        case WM_PAINT:
            hdc = BeginPaint(hwnd,&ps);
            TextOut(hdc,0,0,tip,lstrlen(tip));
            EndPaint(hwnd,&ps);
        return 0;
        case WM_SYSKEYDOWN:
        {
            BYTE btOEM = LOBYTE(HIWORD(lParam));
            wsprintf(tip,_T("OEM码:%d"),btOEM);
        }
        InvalidateRect(hwnd,NULL,TRUE);
        return 0;
        case WM_KEYDOWN:
        {
            BYTE btOEM = LOBYTE(HIWORD(lParam));
            wsprintf(tip,_T("OEM码:%d"),btOEM);
        }
        InvalidateRect(hwnd,NULL,TRUE);
        return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
        break;//跳出到默认处理
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_HAND);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wndClass))return 0;
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,650,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}