绑定设备的内核API函数:
NTSTATUS IoAttachDevice(
IN PDEVICE_OBJECT SourceDevice , //调用者生成的用来过滤的虚拟设备
IN PUNICODE_STRING TargetDevice , //要被绑定的目标设备(设备名字)
OUT PDEVICE_OBJECT *AttachedDevice//返回指针的指针
);
没有设备名字的设备绑定:
IoAttachDeviceToDeviceStack适用于Windows 2000以下低版本,IoAttachDeviceToDeviceStackSafe适用于Windows 2000SP4以上版本,更加安全。
NTSTATUS IoAttachDeviceToDeviceStackSafe(
IN PDEVICE_OBJECT SourceDevice ,// 过滤设备
IN PDEVICE_OBJECT TargetDevice, // 要被绑定的设备栈中的设备
IN OUT PDEVICE_OBJECT *AttachedToDeviceObject //返回最终被绑定的设备
);
IoCreateDevice函数用于生成设备:
NTSTATUS   IoCreateDevice(   
IN PDRIVER_OBJECT  DriverObject,//本驱动的驱动对象,系统提供DriverEntry传入    IN ULONG  DeviceExtensionSize,//设备扩展,简单设为0   
IN PUNICODE_STRING  DeviceName  OPTIONAL,//设备名称,过滤一般不需要   
IN DEVICE_TYPE  DeviceType,//设备类型   
IN ULONG  DeviceCharacteristics,//设备特征,0   
IN BOOLEAN  Exclusive,//是否互斥,FALSE   
OUT PDEVICE_OBJECT  *DeviceObject   
);
在绑定一个设备之前,应该把这个设备对象的多个子域设置成和要绑定的目标对象一致,包括标志和特征。以下例子函数可以生成一个设备,然后绑定到另一个设备上。
NTSTATUS
ccpAttachDevice(
PDRIVER_OBJECT  driver,
PDEVICE_OBJECT oldobj,
PDEVICE_OBJECT *fltobj,
PDEVICE_OBJECT *next)
{
NTSTATUS status;
PDEVICE_OBJECT topdev=NULL;
//生成设备,然后绑定
Status = IoCreateDevice(driver,
0,
NULL,
Oldobj->DeviceType,
0,
FALSE,
fltobj);
If(status != STATUS_SUCCESS)
return status;
//拷贝重要标志位
if(oldobj->Flags & DO_BUFFERED_IO)
(*fltobj->Flags != DOBUFFERED_IS;
if(oldobj->Flags & DO_DIRECT_IO)
(*fltobj->Flags != DO_DIRECT_IO;
if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
(*fltobj->Characteristics != FILE_DEVICE_SECURE_OPEN;
(*fltobj->Flags != DO_POWER_PAGABLE;
//将一个设备绑定到另一设备上
Topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj);
if (topdev == NULL)
{
//如果绑定失败了,销毁设备,返回错误
IoDeleteDevice(*fltobj);
*fltobj = NULL;
status = STATUS_UNSUCCESSFUL;
return status;
}
*next = topdev;
//设置这个设备已经启动
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
Return STATUS_SUCCESS;
}
3.14重设备获取设备对象
使用函数IoGetDeviceObjectPointer可以获得这个设备对象的指针,函数原型:
NTSTATUS
  IoGetDeviceObjectPointer(
    IN PUNICODE_STRING  ObjectName,//设备名字
    IN ACCESS_MASK  DesiredAccess,//访问权限
    OUT PFILE_OBJECT  *FileObject,//返回参数,得到设备对象的同时会得到的一个文件对象(File Object)
    OUT PDEVICE_OBJECT  *DeviceObject
    );
实例:
// 打开一个端口设备
PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)
{
//外面输入的是串口的id,这里会改写成字符串的形式
UNICODE_STRING name_str;
static WCHAR name[32] = { 0 };
PFILE_OBJECT fileobj = NULL;
PDEVICE_OBJECT devobj = NULL;
// 输入字符串。根据id转换成串口的名字
memset(name,0,sizeof(WCHAR)*32);
RtlStringCchPrintfW(
name,32,
L"\\Device\\Serial%d",id);
RtlInitUnicodeString(&name_str,name);
// 打开设备对象
*status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
//如果打开成功了,记得一定要把文件对象解除引用,不能忽略
if (*status == STATUS_SUCCESS)
ObDereferenceObject(fileobj);
//返回设备对象
return devobj;
}
3.15绑定所有串口
为了过滤,这里必须把过滤设备和被绑定的设备对象指针都保存起来。
//假如最大32个端口
#define CCP_MAX_COM_ID 32
// 过滤设备和真实设备
//保存所有过滤设备指针
static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 };
//保存所有真实设备指针
static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 };
// 这个函数绑定所有的串口。
void ccpAttachAllComs(PDRIVER_OBJECT driver)
{
ULONG i;
PDEVICE_OBJECT com_ob;
NTSTATUS status;
for(i = 0;i<CCP_MAX_COM_ID;i++)
{
// 获得object引用。
com_ob = ccpOpenCom(i,&status);
if(com_ob == NULL)
continue;
// 在这里绑定。并不管绑定是否成功。
ccpAttachDevice(driver,com_ob,&s_fltobj[i],&s_nextobj[i]);
// 取消object引用。
}
}
没有必要关心这个是否绑定成功,看下s_fltobj即可。这个数组中不为NULL表示绑定成功。
3.2获得实际数据
根据请求发送给串口设备的数据,则包含要接受的数据。
2.31请求的区分
每个驱动程序只有一个驱动对象,每个驱动程序可以生成若干个设备对象,这些设备对象从属一个驱动对象。
若干个设备依次绑定一个设备栈,总是最顶端的设备接收到请求。
串口设备接收到的请求都是IRP,因此只要对所有IRP进行过滤,就可以得到串口上流过的所有数据。这时只需关心读请求和写请求。
请求的主功能号为IRP_MJ_READ,而写请求的主功能号为IRP_MJ_WRITE。
//这里irpsp成为LRP的栈空间,IoGetCurrentIrpStackLocation获得当前栈空间,
//栈空间是非常重要的数据结构
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
if(irpsp->MajorPunction==IPR_MJ_READ)
{
//如果是写..........
}
Else if{irpsp->MajorPunction==IRP_MJ_READ}
{
//如果读.............
}
3.22请求的结局
1、允许通过
2、否决
3、过滤完成这个请求。
串口过滤要捕获两种数据:一种是写请求中的数据,另一种是读接受的数据。这里简单就捕获发送出的数据。
首先调用IoSkipCurrentIrpStackLocation跳到当前栈空间,然后用IoCallDriver吧这个请求发给真实的设备。
3.2写请求的数据
Irp ->MDLAddress,内存描述符链
Irp ->UserBufer,追求效率的解决方案
Irp ->SystemBuffer不追求效率情况下的解决方案
以下方法哪一种多可以把数据正确的读出来:
PUCHAR buf = NULL;
if(irp->MdlAddress != NULL)
   buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp-&gt;MdlAddress,NormalPagePriority);
Else buf = (PUCHAR)irp->UserBuffer;
if(buf == NULL)
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
MmGetSystemAddrssForMdlSafe宏返回指定的MDL描述的缓冲区的非分页系统的虚拟地址空间。
此外是缓冲区有多长的问题,对于一个写操作而言,长度可以如下获得:
ULONG length = irpsp-&gt;Parameters.Write.Length;
3.3完整的代码
3.3.1完整的分发函数
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
{
    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
    NTSTATUS status;
    ULONG i,j;
    // 首先得知道发送给了哪个设备。设备一共最多CCP_MAX_COM_ID
    // 个,是前面的代码保存好的,都在s_fltobj中。
    for(i=0;i<CCP_MAX_COM_ID;i++)
    {
        if(s_fltobj[i] == device)
        {
            // 所有电源操作,全部直接放过。
            if(irpsp->MajorFunction == IRP_MJ_POWER)
            {
                // 直接发送,然后返回说已经被处理了。
                PoStartNextPowerIrp(irp);
                IoSkipCurrentIrpStackLocation(irp);
                return PoCallDriver(s_nextobj[i],irp);
            }
            // 此外我们只过滤写请求。写请求的话,获得缓冲区以及其长度。
            // 然后打印一下。
            if(irpsp->MajorFunction == IRP_MJ_WRITE)
            {
                // 如果是写,先获得长度
                ULONG len = irpsp->Parameters.Write.Length;
                // 然后获得缓冲区
                PUCHAR buf = NULL;
                if(irp->MdlAddress != NULL)
                    buf =
                    (PUCHAR)
                    MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
                else
                    buf = (PUCHAR)irp->UserBuffer;
                if(buf == NULL)
                    buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
                // 打印内容
                for(j=0;j<len;++j)
                {
                    DbgPrint("comcap: Send Data: %2x\r\n",
                        buf[j]);
                }
            }
            // 这些请求直接下发执行即可。我们并不禁止或者改变它。
            IoSkipCurrentIrpStackLocation(irp);
            return IoCallDriver(s_nextobj[i],irp);
        }
    }
    // 如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误。
    irp->IoStatus.Information = 0;
    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    IoCompleteRequest(irp,IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
3.3.2如何动态卸载
必须解除绑定的功能,否则蓝屏。用到3个API一个是IoDetachDevice负责把绑定的设备解除绑定,另一个是:IoDeleteDevice负责把IoCreateDevice生成个设备删除掉回收内存,最后一个是KeDelayExecutionThread负责延时。
#define  DELAY_ONE_MICROSECOND  (-10)
#define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
void ccpUnload(PDRIVER_OBJECT drv)
{
ULONG i;
LARGE_INTEGER interval;
// 首先解除绑定
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_nextobj[i] != NULL)
IoDetachDevice(s_nextobj[i]);
}
// 睡眠5秒。等待所有irp处理结束
interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode,FALSE,&interval);
// 删除这些设备
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_fltobj[i] != NULL)
IoDeleteDevice(s_fltobj[i]);
}
}