文章目录

  • 介绍
  • 知识前奏
    • 内核方面编程
      • 设备对象和符号链接
      • 分发函数
    • 应用方面编程
      • 打开设备
      • 设备控制请求
  • 代码
    • 应用层代码
    • 内核层代码
    • 完整工程代码
  • 测试效果

介绍

Windows应用程序(Ring3层)和内核驱动(Ring0层)是运行在Windows权限的不同级别,简单来说各有优势。内核层权限较大 能做很多 应用程序办不到的事情 不直接面向程序使用的用户,Windows应用程序在Ring3层 直接面向用户,界面友好。当应用层办不到的时候就需要借助内核层了,所以 win32应用程序和Windows内核驱动通信是有必要的。Windows应用程序和Windows内核驱动程序直接是可以进行双向通信的,相互都是可以发送信息的

本篇博客,将使用一个最简单的例子来讲解 Win32程序和内核驱动程序通信编程。这里把内核驱动当做一个Server,应用程序当做一个客户端。客户端向内核驱动发生一个 小写字符串,内核驱动将小写字符串转成大写 然后回射回来。

知识前奏

内核方面编程

设备对象和符号链接

如果驱动需要和应用程序通信,首先必须要生成一个设备对象(Device Object)。设备对象和分发函数构成了整个内核体系的基本框架设备对象用来暴露给应用层,应用层可以像操作文件一样操作它。用于和应用程序通信的设备往往用来"控制"这个内核驱动,所以往往称之为**“控制设备对象”**(Control Device Object,CDO)。生成设备对象使用IoCreateDevice函数,原型如下:

/*return STATUS_SUCCESS成功
*/
NTSTATUS IoCreateDevice(// 可直接从DriverEntry参数中获得IN PDRIVER_OBJECT  DriverObject,// 表示设备扩展大小(应用设备扩展)会专门讲述IN ULONG  DeviceExtensionSize,// 设备名IN PUNICODE_STRING  DeviceName  OPTIONAL,// 设备类型,Windows已经规定了一系列设备类型IN DEVICE_TYPE  DeviceType,// 表示一组设备属性IN ULONG  DeviceCharacteristics,// 表示是否一个独占设备,设置独占,这个设备将在同一个时刻只能被打开一个句柄。一般都不会设置为独占设备IN BOOLEAN  Exclusive,//  返回结果OUT PDEVICE_OBJECT  *DeviceObject);

控制设备需要有一个名字,这样才会被暴露出来,供其他程序打开与之通信。设备的名字可以在IoCreateDevice或IoCreateDeviceSecure时指定。但是,应用层是无法直接通过设备的名字来打开对象的,必须建立一个暴露给应用层的符号链接。符号链接是记录一个字符串对应到另一个字符串的简单结构。函数原型IoCreateSymbolicLink如下:

NTSTATUS IoCreateSymbolicLink(// 符号链接名,如果该符号链接名存在 则创建不成功IN PUNICODE_STRING  SymbolicLinkName,// 设备名IN PUNICODE_STRING  DeviceName);

控制设备和符号链接的删除很简单,一一对应,IoDeleteDevice、IoDeleteSymbolicLink

分发函数

分发函数是一组用来处理发送给设备对象(当然包括控制设备)的请求的函数。这些函数当然由内核驱动的开发者编写,以便处理这些请求并返回给Windows。分发函数是设置在驱动对象上的。Windows的IO管理器在收到请求时,会根据请求发送的目标,也就是一个设备对象,来调用这个设备对象所从属的驱动对象上的分发函数。最简单的3种请求:

  • 打开(Create):在试图访问一个设备对象之前,必须先用打开请求打开它。只有得到成功的返回,才可以发送其他的请求。
  • 关闭(Close):在结束访问一个设备对象之后,发送关闭请求将它关闭。关闭之后,就必须再次打开才能访问。
  • 设备控制(Device Control):设备控制请求是一种即可以用来输入(应用到内核),又可以用来输出(从内核到应用)的请求。

分发函数原型:

NTSTATUS MyDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{// 在分发函数内部进行 请求的处理。一般有如下几个步骤// 第1步,判断请求是否发送给该驱动的设备对象// 第2步,获取请求的当前栈空间(空间中含有请求相关的信息)// 第3步,获取请求的功能号,不同的功能号做不同的处理(这里就可以对输入输出做操作了)。// 第4步,结束请求
}

应用方面编程

打开设备

在应用程序中 打开驱动中创建的设备。和打开文件没什么区别,使用 CreateFile即可,要注意文件的路径。

/*
文件的路径就是符号链接的路径,但是符号链接的路径在应用看来,是以"\\.\"开头的。注意,这些"\"在C语言中要使用"\\"来转义
*/
#define MY_DEVOBJ_SYB_NAME (L"\\\\.\\lcx10000")HANDLE deviceHandle = CreateFile(MY_DEVOBJ_SYB_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);

设备控制请求

设备控制请求即可以进行输入也可进行输出。每个设备控制请求都会有一个功能号,用来区分不同的设备控制请求。这个功能号体现在CTL_CODE中。

CTL_CODE是一个宏,是SDK里头文件提供的。我们要做的是直接利用这个宏来生成一个自己的设备控制请求功能号。CTL_CODE有4个参数。

  • 参数1,设备类型,这里的控制设备与任何硬件都没有关系,所以直接定义为未知类型FILE_DEVICE_UNKNOWN。
  • 参数2,生成这个功能号的核心数字,这个数字直接用来和其他参数“合成”功能号?0x0~0x7ff被微软预留了,同时也不能超过0xfff。如果要定义超过一个的功能号,那么不同的功能号就靠这个数字进行区分。
  • 参数3,METHOD_BUFFERED是说用缓存方式。用缓存方式,输入输出缓存会在用户和内核之间拷贝。这是比较简单和安全的一种方式
  • 参数4,是这个操作需要的权限。当需要将数据发送到设备时,相当于往设备写入数据,所以标志为拥有写数据权限(FILE_WRITE_DATA)。

设备控制请求函数原型:

BOOL DeviceIoControl(// 设备句柄HANDLE       hDevice,// Control codeDWORD        dwIoControlCode,// 输入缓冲区LPVOID       lpInBuffer,// 输入缓冲区长度DWORD        nInBufferSize,// 输出缓冲区LPVOID       lpOutBuffer,// 输出缓冲区长度DWORD        nOutBufferSize,// 接受到的有效数据长度LPDWORD      lpBytesReturned,// 指向OVERLAPPED结构体的指针LPOVERLAPPED lpOverlapped
);#define CTL_CODE( DeviceType, Function, Method, Access )

代码

代码中有很详细的注释。

应用层代码

这里使用简单MFC界面操作代码


// 设备名对应的符号链接名,用于暴露给应用层。符号链接在应用看来是在\\.\ 的
#define MY_DEVOBJ_SYB_NAME (L"\\\\.\\lcx10000")//CTL_CODE创建控制码
#define IOCTL_SEND_AND_REC_STR\CTL_CODE(FILE_DEVICE_UNKNOWN\, 0x801, METHOD_BUFFERED,\FILE_READ_DATA | FILE_WRITE_DATA)void CMy02_Win32ToDriverDlg::OnBnClickedSendtodriver()
{UpdateData(TRUE);int len = m_uiSendToDriverString.GetLength();char* pInStr = new char[len+1];char *pOutStr = new char[len+1];memset(pInStr,0,len+1);memset(pOutStr,0,len+1);for(int i = 0; i < len ; i++){pInStr[i] = m_uiSendToDriverString.GetAt(i);}//char* pStr = (char*)m_uiSendToDriverString.GetBuffer(0);int ret_len = 0;// 1.打开驱动设备HANDLE deviceHandle = CreateFile(MY_DEVOBJ_SYB_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);if(deviceHandle == INVALID_HANDLE_VALUE){DWORD errCode = ::GetLastError();m_uiDriverResponse = _T("CreateFile 失败!驱动未加载");m_uiDriverResponse.AppendFormat(_T(" errCode=%d"),errCode);}else{// 2.向驱动设备发送设备控制请求if( DeviceIoControl(deviceHandle,IOCTL_SEND_AND_REC_STR,pInStr,len,pOutStr,len+1,(LPDWORD)&ret_len,NULL)){m_uiDriverResponse = _T("suc.");m_uiDriverResponse.AppendFormat(_T("retLen=%d,response=%s"),ret_len,CString(pOutStr));}}UpdateData(FALSE);
}

内核层代码

#include <Wdm.h>
#include <wdmsec.h>// 全局设备对象
PDEVICE_OBJECT g_devObj = NULL;// 设备名对应的符号链接名,用于暴露给应用层。符号链接一般都是在\??\路径下
#define MY_DEVOBJ_SYB_NAME (L"\\??\\lcx10000")// 设备一般都是位于 \Device\这个路径下的
#define MY_DEVOBJ_NAME (L"\\Device\\lcx10000")// 可用VS自带的GUID生成器生成 {D1AC1F58-AAC4-45DD-AEC4-A9670DC47B29}
static const GUID g_devGUID =
{ 0xd1ac1f58, 0xaac4, 0x45dd, { 0xae, 0xc4, 0xa9, 0x67, 0xd, 0xc4, 0x7b, 0x29 } };//CTL_CODE创建控制码
#define IOCTL_SEND_AND_REC_STR\CTL_CODE(FILE_DEVICE_UNKNOWN\, 0x801, METHOD_BUFFERED,\FILE_READ_DATA | FILE_WRITE_DATA)NTSTATUS MyDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{NTSTATUS status = STATUS_SUCCESS;ULONG ret_len = 0,i = 0,temp = 0;// 第1步,判断请求是否发送给该驱动的设备对象,如果不是 简单返回成功if(DeviceObject == g_devObj){// 第2步,获取请求的当前栈空间(空间中含有请求相关的信息)PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);// 第3步,获取请求的功能号,不同的功能号做不同的处理// 打开请求的主功能号是 IRP_MJ_CREATE// 关闭请求的主功能号是 IRP_MJ_CLOSE// 设备控制请求的主功能号是IRP_MJ_DEVICE_CONTROL// 处理打开和关闭IRP,可以简单返回成功即可if( irpStack->MajorFunction == IRP_MJ_CREATE ||irpStack->MajorFunction == IRP_MJ_CLOSE ){status = STATUS_SUCCESS;}// 处理设备控制请求DeviceIoControlelse if( irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL){// 当前是一个缓存方式的设备控制请求,直接从IRP请求参数中获取缓冲区buffer// 同时 这里输入缓冲区、输出缓冲区是共享的PVOID buffer = Irp->AssociatedIrp.SystemBuffer;ULONG inLen = irpStack->Parameters.DeviceIoControl.InputBufferLength;ULONG outLen = irpStack->Parameters.DeviceIoControl.OutputBufferLength;//KdBreakPoint();// 断点设置,使用windbg调试时可以打开//控制码由这个宏函数CTL_CODE创建if(irpStack->Parameters.DeviceIoControl.IoControlCode== IOCTL_SEND_AND_REC_STR){if(inLen > 0){// 做一个简单打印DbgPrint("str=%s",(char*)buffer);// 要求输出缓存要多于输入缓存if(outLen >= inLen){// 这里转大写后返回//_strupr((char*)buffer);ret_len = inLen;for(; i <= inLen ; i++){temp = (ULONG)((char*)buffer)[i];if( temp >= 97 && temp <= 122){((char*)buffer)[i] -= 32;}}}else{status = STATUS_INVALID_PARAMETER;}}else{status = STATUS_INVALID_PARAMETER;}}else{// 其他控制码请求,一律返回非法参数错误。status = STATUS_INVALID_PARAMETER;}}}// KdBreakPoint(); // 断点设置,使用windbg调试时可以打开// 第4步,结束请求// 这个Informatica用来记录这次返回到底使用了多少输出空间Irp->IoStatus.Information = ret_len;// 用于记录这个请求的完成状态Irp->IoStatus.Status = status;// 用于结束这个请求IoCompleteRequest(Irp,IO_NO_INCREMENT);return status;
}VOID DriverUnload(__in struct _DRIVER_OBJECT  *DriverObject)
{   UNICODE_STRING DeviceLinkName = RTL_CONSTANT_STRING(MY_DEVOBJ_SYB_NAME);DbgPrint("DriverUnload enter \n");// 删除符号链接IoDeleteSymbolicLink(&DeviceLinkName);// 删除设备对象IoDeleteDevice(g_devObj);
}NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{int i;NTSTATUS status;// 设备名UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(MY_DEVOBJ_NAME);UNICODE_STRING SDDLString = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");UNICODE_STRING DeviceLinkName = RTL_CONSTANT_STRING(MY_DEVOBJ_SYB_NAME);DbgPrint("DriverEntry enter \n");// 如果驱动需要和应用程序通信,首先必须要生成一个设备对象status = IoCreateDevice(driver,0,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&g_devObj);// 由于IoCreateDevice函数生成的设备具有默认的安全,那么必须具有管理员权限的进程才能打开它// 可用如下函数替换,不过下面的函数在 WinXP中无法使用//status = IoCreateDeviceSecure(driver,0,//    &DeviceName,FILE_DEVICE_UNKNOWN,//  FILE_DEVICE_SECURE_OPEN,FALSE,//    &SDDLString,// 设备对象安全设置//   &g_devGUID, // 设备guid// &g_devObj);if(!NT_SUCCESS(status)){return status;}// 创建符号链接status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceName);if(!NT_SUCCESS(status)){// 一旦失败,之前生成的设备对象也要删掉,防止内存泄漏IoDeleteDevice(g_devObj);return status;}// 设置驱动对象的分发函数,分发函数是一组用来处理发送给设备对象的请求的函数// 这里driver->MajorFunction是一个数组,不同的请求可以设置不同的处理函数// 这里为了方便所有的请求都用一个处理函数,在函数内部去区分请求,再做不同的逻辑处理//int i; // 变量定义要放在最前面,放这里不行for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++){//driver->MajorFunction[i] = NULL;driver->MajorFunction[i] = MyDispatch;}// 支持动态卸载。driver->DriverUnload = DriverUnload;return STATUS_SUCCESS;
}

完整工程代码

笔者用vs2010进行的编译 win32应用程序和驱动程序,在Win XP下测试正常。完整项目工程可以在这里下载。

测试效果

Windows驱动—Windows应用程序和Windows驱动通信编程相关推荐

  1. 笔记本linux蓝牙驱动怎么安装程序,笔记本蓝牙驱动,教您笔记本蓝牙驱动怎么安装...

    现在很多的电脑上都有蓝牙,蓝牙可以让我们的电脑和手机通过无线的方式进行传输文件,但是笔记本上的蓝牙和手机上的又有很大的不同,传输文件也需要一定的操作规范,难么该如何使用呢?下面,小编给大家带来了安装笔 ...

  2. 笔记本linux蓝牙驱动怎么安装程序,笔记本蓝牙驱动怎么安装不上怎么办

    蓝牙是最近几年时间里面开始流行起来的,支持设备短距离无线通信技术,不论是台式电脑还是笔记本电脑,都是支持蓝牙使用的.不过有一些新手小白用户在使用笔记本蓝牙的时候,却出现了蓝牙驱动安装不上的问题,在这里 ...

  3. 在ThinkPad W500 A98上升级Windows 7以及安装硬件驱动和相关程序(2/2)

    在ThinkPad W500 A98上升级Windows 7以及安装硬件驱动和相关程序(2/2) 升级硬件固件 在安装了升级硬件固件所必要的驱动程序后,就可以升级硬件固件了,在下载的硬件驱动和相关程序 ...

  4. 在ThinkPad W500 A98上升级Windows 7以及安装硬件驱动和相关程序

    在ThinkPad W500 A98上升级Windows 7以及安装硬件驱动和相关程序 备份数据 先备份数据,由于原来的Windows XP已经不能正常启动,因此就使用光驱启动Windows XP P ...

  5. Windows应用程序如何调用驱动:打印机举例

    1. 什么是打印语言? 打印机语言指的是控制打印机工作的命令,它告诉打印机如何组织被打印的文档,打印机按照这些命令来处理计算机传来的打印数据,并最终准确的打印出文字与图像. 打印机语言大体上可分为两类 ...

  6. 安信Windows驱动开发教程:什么是通用 Windows 平台 (UWP) 应用程序?有什么功能?

    安信Windows驱动开发教程:什么是通用 Windows 平台 (UWP) 应用程序?有什么功能? UWP 是为 Windows 创建客户端应用程序的众多方法之一.UWP 应用使用 WinRT AP ...

  7. windows驱动开发7:应用程序和驱动程序的通信

    应用程序和驱动程序的通信 一.基础介绍 1.1 设备与驱动的关系 设备由驱动去创建,访问一个设备,是首先得访问驱动.如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的. 驱动程序和系统其他组 ...

  8. MongoDb Windows linux平台环境及主流编程语言驱动安装同时配置mongoDb的远程连接

    MongoDb Windows linux平台环境及主流编程语言驱动安装同时配置mongoDb的远程连接 <一,>MongoDB 简介篇Ruiy; MongoDB是一个高性能,开源,无模式 ...

  9. Windows Server 2012 +WDK7600.16385.1+VS2010驱动开发环境搭建

    本帖通过Augusdi的一篇博文进行重新总结 第一步:安装Visual stdio 2010 1.安装VS2010 第二步:安装WDK安装包 2.安装WindowsDriverKit7-GRMWDK_ ...

最新文章

  1. C语言:gcc编译过程及make命令、makefile语法规则
  2. Pandas高级教程之:统计方法
  3. linux sh脚本 递增,Linux shell 脚本实现进度框
  4. spring中注解的通俗解释
  5. Memcached未授权访问漏洞记录(CVE-2013-7239、危害级别全版本、端口:11211)
  6. python输出文字和数字加法_用c语言或者python将文件中特定字符串后面的数字相加...
  7. AJAX 简单例程示例
  8. php正则表达式替换ubb,自定义ubb代码,preg_replace()函数的一些代码
  9. html求相关系数,相关系数,确定系数(R^2)计算公式与在线计算器_三贝计算网_23bei.com...
  10. 《麦田里的守望者》感
  11. 基于web的小票打印
  12. 03-链表(Linked List)应用分析
  13. 企业能源管控平台在轧钢行业能源管理中的应用
  14. Python图像识别-Opencv07 异或运算,图像加密
  15. ListView与ListView适配器
  16. 读书笔记 - 《战天京》
  17. python输出平行四边
  18. 猫和老鼠服务器维护多久结束,《猫和老鼠》母猫玩家遇到4个杰瑞,5分钟后,她想挂机了!...
  19. 病原菌基因组快速搜索算法实现
  20. shell之未找到命令

热门文章

  1. 让你的Mac读给你听,还能听写,用来练习英语口语!
  2. fcpx插件Corporate Story for Mac(商务公司视频宣传片头模板)
  3. java实现相同分数排名_java做成绩排名表,如何实现同分同名次
  4. 第3课 攀天梯(ladder)--记忆化搜索(python3实现)
  5. 第27课 老狼老狼几点钟 《小学生C++趣味编程》
  6. phpexcel的使用方法详细介绍
  7. Java笔记-以系统时间为基准15分钟运行一次指定代码
  8. 前端笔记-vue cli为web添加底纹
  9. Micsorft文档阅读笔记-Run-Time Type Information解析及使用
  10. Qt文档阅读笔记-Qt Concurrent介绍及简单使用