Keyboard驱动介绍
最近手里面没啥事,就想看看一些Driver的MDD层。
以前改过Keyboard Driver的PDD层,但是对它的MDD层还真是一片空白,这两天随便看了看Keyboard的MDD层,赶紧把东西记录下来,以防以过段时间忘记了。
很多是个人理解,难免有错误的地方。
一.Keyboard Driver的加载过程
系统启动过程中,GWES注册表HKEY_LOCAL_MACHINE/Hardware/DeviceMap/KEYBD 下的”Drivername”下去获取Keyboard Driver的名字,如果没找到,则使用默认的名字Keybddr.dll。
加载的大概过程如下:
首先GWES会去验证Keyboard Driver的导出接口是否存在;
接下来去调用导出函数KeybdDriverInitializeEx(),对Keyboard Driver进行初始化。
二.有关Keyboard的几个概念
1.Device Layout
以PC机键盘为例,有的键盘是101键,有的是108键(呵呵,记不清楚了,反正是有按键比较多的键盘),从外观上来看,它们的按键个数和键位不同,从功能上看,按键比较多的键盘支持更多的功能,其实这就是Keyboard Layout的含义。
对于相同Layout不同厂家生产的键盘,相同按键产生的Scan Code必须是相同的,这也是PC机在不用装驱动的情况下可以支持任何市面上见到的键盘。
对于WinCE系统而言,为了支持市面上常见的PC键盘,也需要支持这些标准的Layout,这也是WinCE中存在很多标准Layout的根本原因。
具体到功能来说,Device Layout的功能就是负责Scan Code和Virtual Code的转换,以及Virtual Code的Remapping。比如,键盘上左边有一个Shift键,右边也有一个Shift键,它们的Scan Code和Virtual code不一样的,但是可以通过Remapping把它们的Virtual Code进行Remapping,转化为一样的Virtual Code。
系统中的Layout都作了哪些工作,引用Help文档中的内容来进行说明:
The following list shows, in sequence, how the Layout Manager handles scan codes:
1. PDD receives a scan code.
2. PDD sends the scan code to the Layout Manager.
3. Layout Manager converts the scan code to a virtual-key code based on the keyboard that sent the event and its current device layout.
4. Layout Manager remaps the scan code based on the keyboard that sent the event and its current device layout.
5. Layout Manager handles the auto-repeat functionality.
All keyboards share the same auto-repeat settings.
6. Layout Manager calls keybd_event to send an event or events.
Layout在注册表中的位置HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control /Layouts/{Layout name},观察会发现所有Layout下的DLL是同一个,这是因为默认情况下,DLL中包含了所有的Layout。
另外,输入法也是要在HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control /Layouts/{Layout name}记录的DLL以及UI等信息,但实际上,并不是输入法真正的Layout,它实际使用的Layout是在通过注册表项"Keyboard Layout"指出来,例如双拼的注册表配置如下:
; Make your own IMEUI dll and change "UI Module" field to your dll name.
; Only need to export one API: void CALLBACK ImeGetUIClassName(LPTSTR);
; IME will call this API to copy your UI class name.
[HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Layouts/e0010804]
; @CESYSGEN IF WCESHELLFE_MODULES_INTLL
; @CESYSGEN ENDIF
; @CESYSGEN IF WCESHELLFE_MODULES_INTLP
"Layout Display Name"="@//Windows//intlp.cpl,-20483"
; @CESYSGEN ENDIF
"Layout Text"="Microsoft CHS ShuangPin IME"
"Ime File"="msimesp.dll"
"UI Module"="msimeuic.dll"
"Keyboard Layout"="00000409"
2.Input Language
在Virtual Code和Unicode之间进行转换。
Input Language的入口函数名字是:IL_{layout序号}。
3.Local
引用Help文档来进行说明:
Pairing of an input language with an input method. The input locale identifier is a number. For example, the input locale identifier for a standard United States 101 keyboard is 00000409. The low word is the language identifier and the high word is a type identifier. The input locale identifier for a Dvorak keyboard is 00010409.
An input locale and an input locale handle differ. Once an input locale is loaded, Layout Manager generates a handle to an input locale (HKL) for the input locale that can be used with the keyboard APIs.
注册表HKEY_CURRENT_USER/Keyboard Layout/Preload下指定了默认的Local,而HKEY_CURRENT_USER/Keyboard Layout/Preload/{number no more than 15}指出了可用的Local。
三.Keyboard中相关信息的获取方式
1.MDD层如何获取Device Layout信息
理论上,Layout Manager可以管理多个PDD层,这些PDD层会组成一个链表,并用全局变量g_rgpfnPddEntries来表示所有PDD层的入口,在目前我们的项目上其值为:
PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[] =
{
PS2_NOP_Entry, Matrix_Entry, NULL
};
GWES调用函数KeybdDriverInitializeEx()初始化Keyboard Driver的时候,会去引用PDD层的入口函数,获取到KEYBD_PDD后将其填充到MDD层的指针变量g_pPdds指向的KEYBD_PDD_INFO链表中,链表的长度取决于PDD层入口的个数。
下面看一下KEYBD_PDD_INFO的定义:
typedef struct tagKEYBD_PDD_INFO {
BOOL fValid;
PKEYBD_PDD pKeybdPdd;
DEVICE_LAYOUT_INFO dli;
} KEYBD_PDD_INFO, *PKEYBD_PDD_INFO;
其成员pKeybdPdd就指向了从PDD层获取的KEYBD_PDD,接下来看一下KEYBD_PDD的定义:
typedef struct tagKEYBD_PDD {
WORD wPddMask; // Matches the keyboard layout with its PDD
LPCTSTR pszName; // Used to identify PDD to user
PFN_KEYBD_PDD_POWER_HANDLER pfnPowerHandler;
PFN_KEYBD_PDD_TOGGLE_LIGHTS pfnToggleLights;
} KEYBD_PDD, *PKEYBD_PDD;
有必要指出的是其第一个成员wPddMask,后面从注册表中查询Keyboard入口并获取Device Layout的时候,需要利用它判断系统中存在的所有Device Layout并找到最合适的一个。
好了,接下来我们看一下函数KeybdDriverInitializeEx()的代码。
//----------------------------------------------------------------------------
//
// KeybdDriverInitializeEx
//
// Initializes the layout manager and any keyboard PDDs.
//
//----------------------------------------------------------------------------
extern "C"
void
KeybdDriverInitializeEx(
PFN_KEYBD_EVENT_CALLBACK_EX pfnKeybdEventCallbackEx // @parm The callback into the input system.
)
{
SETFNAME(_T("KeybdDriverInitializeEx"));
HKL hkl;
PFN_KEYBD_PDD_ENTRY *ppfnKeybdPddEntry;
UINT uiCurrPdd;
TCHAR szDefaultName[KL_NAMELENGTH]= _T("");
PREFAST_DEBUGCHK(pfnKeybdEventCallbackEx != NULL);
InitializeCriticalSection(&g_csAccessInputLocaleInfo);
LockConfig();
// Set up critical sections, threads, events, etc.
InitializeCriticalSection(&g_csEventCallback);
g_hevBeginEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hevEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hevExitNotficationThread = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hEventThread = CreateThread(NULL, 0, KeybdEventThreadProc, NULL, 0, NULL);
HANDLE hNotificationThread = CreateThread(NULL, 0, KbdNotificationThread, NULL, 0, NULL);
CloseHandle(hNotificationThread); // We do not need this handle anymore
if ((g_hevBeginEvent == NULL) ||
(g_hevEndEvent == NULL) ||
(g_hevExitNotficationThread == NULL) ||
(g_hEventThread == NULL))
{
ERRORMSG(1, (_T("Unable to set up keyboard layout manager/r/n")));
if (g_hevBeginEvent != NULL) CloseHandle(g_hevBeginEvent);
if (g_hevEndEvent != NULL) CloseHandle(g_hevEndEvent);
if (g_hevExitNotficationThread != NULL) CloseHandle(g_hevExitNotficationThread);
if (g_hEventThread != NULL) CloseHandle(g_hEventThread);
DeleteCriticalSection(&g_csEventCallback);
goto EXIT;
}
// Initialize our PDD data structures
// Get a count of PDDs
PREFAST_DEBUGCHK(g_rgpfnPddEntries != NULL);
DEBUGCHK(g_cPdds == 0);
// 获取PDD层的全局变量g_rgpfnPddEntries
ppfnKeybdPddEntry = &g_rgpfnPddEntries[0];
// 计算PDD层总共提供了多少个Entry,意思应该是说可以支持多个Entry,即支持多个键盘
// PDD层的总数是g_cPdds
while (*ppfnKeybdPddEntry != NULL) {
++g_cPdds;
++ppfnKeybdPddEntry;
}
// Allocate our PDD list
// 在MDD层分配空间对PDD List进行管理
DEBUGCHK(g_pPdds == NULL);
g_pPdds =
(PKEYBD_PDD_INFO) LocalAlloc(LPTR, sizeof(KEYBD_PDD_INFO) * g_cPdds);
if (g_pPdds == NULL) {
ERRORMSG(1, (_T("Out of memory/r/n")));
goto EXIT;
}
// Initialize each PDD
// 通过引用pdd层的全局变量g_rgpfnPddEntries来调用pdd层的所有初始化函数
// 另外,通过调用pdd层的初始化函数从pdd层获取获取KEYBD_PDD变量的值,并放到g_pPdds中
for (uiCurrPdd = 0; uiCurrPdd < g_cPdds; ++uiCurrPdd)
{
// 获取刚才为管理PDD List而分配空间,第uiCurrPdd部分
PKEYBD_PDD_INFO pKeybdPddInfo = pKeybdPddInfoFromId(uiCurrPdd);
// 调用PDD层的第uiCurrPdd个Entry
// 其中一个目的是获取PDD层的PKEYBD_PDD,一个典型的PKEYBD_PDD如
//static KEYBD_PDD PS2NOPPdd = {
// PS2_NOP_PDD,
//_T("PS/2 NOP"),
//NULL, // Don't give the layout manager any PDD function pointers
//NULL
//};
BOOL fNoErr = (*g_rgpfnPddEntries[uiCurrPdd])
(uiCurrPdd, KeybdEventCallback, &pKeybdPddInfo->pKeybdPdd);
if (fNoErr == FALSE) {
// Something bad happened during PDD initialization. Mark it
// as invalid.
ERRORMSG(1, (_T("Keyboard: PDD %u initialization failed/r/n"),
uiCurrPdd));
pKeybdPddInfo->fValid = FALSE;
continue;
}
DEBUGCHK(pKeybdPddInfo->pKeybdPdd != NULL);
PKEYBD_PDD pKeybdPdd = pKeybdPddInfo->pKeybdPdd;
if (ValidateKeybdPdd(pKeybdPdd) == FALSE) {
ERRORMSG(1, (_T("Keyboard: Invalid keybd PDD information for PDD %u/r/n"),
uiCurrPdd));
pKeybdPddInfo->fValid = FALSE;
continue;
}