📄 jiurl键盘驱动 2.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0058)http://jiurl.nease.net/cn/document/KbdDriver/JiurlKbd2.htm -->
<HTML><HEAD><TITLE>JIURL键盘驱动 2</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<STYLE type=text/css>.title {
FONT-WEIGHT: bold; FONT-SIZE: 21px; LINE-HEIGHT: 48px; FONT-FAMILY: "黑体", Arial, sans-serif; TEXT-DECORATION: none
}
.author {
FONT-SIZE: 12px; LINE-HEIGHT: 16px; FONT-FAMILY: "宋体"
}
.content {
FONT-SIZE: 14px; LINE-HEIGHT: 20px
}
</STYLE>
<META content="MSHTML 6.00.2900.2668" name=GENERATOR></HEAD>
<BODY bgColor=#f7f7f7 topMargin=5>
<DIV align=center>
<CENTER>
<TABLE height=29 cellSpacing=0 cellPadding=0 width="96%" border=0>
<TBODY>
<TR>
<TD class=title width="100%" height=41>
<P align=center><FONT face=宋体>JIURL键盘驱动 2</FONT></P></TD></TR></CENTER>
<TR>
<TD class=author width="100%" height=9>
<P align=center><FONT face=宋体>作者: <A
href="mailto:jiurl@mail.china.com">JIURL</A> </FONT></P></TD></TR>
<TR>
<TD class=author width="100%" height=6>
<P align=center><FONT
face=宋体>
主页: <A href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A>
</FONT></P></TD></TR>
<TR>
<TD class=author width="100%" height=2>
<P align=center><FONT face=宋体> 日期: 2003-12-13</FONT>
</P></TD></TR></TBODY></TABLE></DIV>
<DIV align=center>
<CENTER>
<TABLE height=1 cellSpacing=0 cellPadding=0 width="96%" border=0>
<TBODY>
<TR>
<TD width="100%" height=1>
<HR color=#396da5 SIZE=3>
</TD></TR></TBODY></TABLE></CENTER></DIV>
<DIV align=center>
<TABLE class=content height=200 cellSpacing=0 cellPadding=0 width="96%"
border=0>
<TBODY>
<TR>
<TD vAlign=top width="131%" height=200>
<P><B>2 应用层基础知识</B><BR><BR> 在讨论使用键盘的应用程序这个问题之前,我们首先介绍一下
Windows 中,应用程序使用驱动,应用程序与驱动通信的一些问题。<BR><BR>2.1
应用程序如何使用驱动<BR><BR> 应用程序中使用
CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle
来指示驱动程序完成某种任务。比如我们在应用程序中使用 ReadFile 来让驱动读取硬件设备,我们在应用程序中使用 WriteFile
来让驱动写硬件设备,我们在应用程序中使用 DeviceIoContorl 来让驱动完成某些驱动支持的功能。而 ReadFile,
WriteFile, DeviceIoControl 这三个 api 都需要一个句柄作为参数,以确定他们是要哪个驱动来完成他们的请求。这个句柄是通过
CreateFile 获得的。使用 CloseHandle 关闭这个句柄。简单的说就是,应用程序中,首先要通过 CreateFile
获得一个句柄,之后应用程序可以以这个句柄为参数,使用 ReadFile,WriteFile,DeviceIoControl
让驱动程序执行某种操作。当不再使用时,通过 CloseHandle 关闭这个句柄。<BR><BR> 这几个
api 都位于 KERNEL32.DLL 中,他们最终会通过系统服务(int 2e)调用内核中的相应的函数,如
NtCreateFile,NtReadFile 等。而 NtCreateFile,NtReadFile 等函数中,会创建一个
IRP,并用传入的参数初始化这个 IRP,然后将这个 IRP 发给驱动,让驱动做处理。相应的 NtCreateFile 产生
IRP_MJ_CREATE 的 IRP ,NtReadFile 产生 IRP_MJ_READ 的 IRP。驱动得到这些 IRP
,根据情况做处理,对于 IRP_MJ_READ ,会调用驱动中处理 IRP_MJ_READ
的部分,可能最后引起读硬件的操作。<BR><BR><BR>2.2 获得指定驱动的句柄<BR><BR>
对于希望被应用程序使用的驱动,会在初始化的过程中,把能找到它设备对象的一个 SymbolicLink 放在对象管理器命名空间(Object
Manager Namespace)的 \??\ 下。这样用 "\\\\.\\那个SymbolicLink的名字" 作为 CreateFile 的
lpFileName 参数,调用 CreateFile ,得到的句柄就可以找到相应的驱动的那个设备对象(\\.\ 会被转换成
\??\)。之后以这个句柄为参数使用 ReadFile,WriteFile,DeviceIoControl,产生的 IRP
就被发到相应的设备对象。也就是说只要驱动把 设备对象的 SymbolicLink 放在 \??\ 下,并且应用程序知道这个 SymbolicLink
的名字,就可以使用 CreateFile 得到相应的句柄。<BR><BR>HANDLE CreateFile(<BR>LPCTSTR
lpFileName, // file name<BR>DWORD dwDesiredAccess, // access mode<BR>DWORD
dwShareMode, // share mode<BR>LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// SD<BR>DWORD dwCreationDisposition, // how to create<BR>DWORD
dwFlagsAndAttributes, // file attributes<BR>HANDLE hTemplateFile // handle
to template file<BR>);<BR><BR><BR>可以使用工具 WinObj 来查看 对象管理器命名空间(Object
Manager Namespace) 。WinObj 可以从 http://www.sysinternals.com
获得。关于内核对象和命名地址空间的详细介绍,可以参考《 JIURL玩玩Win2k 对象
》,这篇文章可以在我的主页上找到。<BR><BR>在驱动的初始化过程中,会通过调用 IoCreateDevice
创建设备对象,可以指定一个设备名作为IoCreateDevice 的参数(也可以不指定,那样这个设备对象就没有名字)。这样这个设备对象会被放在
对象管理器命名空间(Object Manager Namespace)的 \Device\ 下。不过应用程序只能访问命名空间的 \??\
,所以如果驱动希望把设备对象暴露给应用程序的话,会为设备对象创建一个 SymbolicLink 放在 \??\ 下。对于放在 \Device\
下的有名字的设备,其他驱动程序如果知道它的名字,就可以使用 IoGetDeviceObjectPointer
得到这个设备对象的指针。<BR><BR>驱动可以通过 IoCreateSymbolicLink ,在 \??\ 下建立设备对象的
SymbolicLink 。这样应用程序必须要也知道该 SymbolicLink 的名字,然后就可以以这个符号链接名做参数使用 CreateFile
,得到句柄。从 IoCreateSymbolicLink 的参数,我们可以知道,只能使用 IoCreateSymbolicLink
为有名字的设备对象建立 SymbolicLink。<BR><BR>另一种方法是,使用一个 GUID 来标识一个设备接口。驱动使用标识设备的 GUID
做参数调用IoRegisterDeviceInterface ,然后使用 IoSetDeviceInterfaceState ,就会为设备对象在
\??\ 下产生一个符号链接(SymbolicLink)。应用程序使用同一个 GUID 做参数,使用API:
SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces,
SetupDiGetDeviceInterfaceDetail 就可以得到创建的 \??\ 下的符号链接名,就可以以这个符号链接名做参数使用
CreateFile ,得到句柄。<BR><BR>
句柄简介。每个进程都有一个自己的句柄表,句柄表中放着内核对象的指针,句柄就是内核对象在句柄表中的索引。通过句柄就可以在进程的句柄表中找到对应的内核对象的指针。关于句柄的详细介绍,可以参考
《 JIURL玩玩Win2k 进程线程篇 HANDLE_TABLE 》,这篇文章可以在我的主页上找到。<BR><BR>2.3
一些结论<BR><BR> SymbolicLink 对象可以找到相应的 设备对象。SymbolicLink
对象中保存着相应的 设备对象的地址。设备对象不保存它的 SymbolicLink 对象的任何信息。SymbolicLink
对象的地址保存在对象管理器命名空间(Object Manager Namespace)中。也就是说只要知道 SymbolicLink
的名字,就可以在对象管理器命名空间中找到它。应用程序 CreateFile 得到的句柄,通过这个句柄在进程的句柄表中找到的是一个文件对象(File
Object)。文件对象 对应的 设备对象 中不保存这个文件对象的任何信息。对应的 SymbolicLink
对象中也不保存这个文件对象的任何信息。这个文件对象的地址,保存在应用程序的句柄表中,应用程序通过句柄可以找到这个文件对象。这个 文件对象
中保存着对应的 设备对象 的地址。可以猜到,应用程序在用 CreateFile 创建的时候,会根据参数中的 SymolicLink 名字,找到
SymolicLink 对象,进而找到该对象中保存的 设备对象 的地址,然后直接把找到的 设备对象 的地址保存在文件对象中。文件对象的 +04
struct _DEVICE_OBJECT *DeviceObject
处,保存着对应的设备对象的地址。<BR><BR> 对于需要暴露接口给应用程序的驱动。首先,驱动中需要在
对象管理器命名空间的 \??\ 下,为设备对象建立一个 SymbolicLink ,不管采取何种方式。之后,应用程序要知道这个
SymbolicLink 的名字,不管采取何种方式。然后应用程序以 "\\\\.\\那个SymbolicLink的名字" 为参数使用
CreateFile 得到一个句柄。这样,之后的 DeviceIoControl(),WriteFile(),ReadFile() 使用前面用
CreateFile 得到的句柄作为参数,他们可以通过这个句柄,找到对应的文件对象,而这个文件对象中保存有对应的 设备对象 的指针,这样就可以将
IRP 发到这些设备。 <BR><BR>
上面的结论是通过对一个小例子进行观察得到的。<BR><BR>2.4 IRP 将被发往设备栈的栈顶<BR><BR>
IRP
将无论如何被发往设备栈的顶。CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle。他们最终都会产生一个
IRP,发给一个设备对象。对于 CreateFile 来说通过 SymbolicLink的名字
来找到一个设备对象。对于其他的几个函数,通过句柄,找到一个文件对象,文件对象中保存有设备对象的指针。不过产生的 IRP
并不一定发给找到的这个设备对象,而是发给找到的这个设备对象所在设备栈的最顶上的一个设备对象。<BR><BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -