Windows 10驱动开发入门(三):DeviceIoControl实现上层通讯

wdm驱动程序采用分层的结构模型,最下层的设备对象称为物理设备对象(Physical Device Object)简称为PDO,在中间层有一个设备对象称为功能设备对象(Functional Device Object)简称FDO

通常来说一个物理设备有两个设备对象:PDO是系统的代表,FDO是设备驱动程序中的代表。

PDO中创建一个FDO,并进行相关的通讯。

可能上面的概念比较难于理解,我们可以通过一个简单的例子来理解上面的意思。

新建一个空的wdm工程,driver.cpp有如下的代码:

extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
	DbgPrint("DriverEntry\r\n");

	pDriverObject->DriverUnload = DriverUnload;//注册驱动卸载函数

	//注册默认派遣函数
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = DefDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_READ] = DefDispatchRoutine;

	//注册设备控制派遣函数
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatchRoutine;

	NTSTATUS status;
	PDEVICE_OBJECT pDevObj;
	PDEVICE_EXTENSION pDevExt;

	//创建设备名称的字符串
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName, L"\\Device\\MyDC");

	//创建设备
	status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
	if (!NT_SUCCESS(status))
		return status;

	pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲I/O设备,关于缓冲I/O设备将会在下一篇博文中讲!
	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展

	//创建符号链接
	UNICODE_STRING symLinkName;
	RtlInitUnicodeString(&symLinkName, L"\\??\\MyDC_link");
	pDevExt->SymLinkName = symLinkName;
	status = IoCreateSymbolicLink(&symLinkName, &devName);
	if (!NT_SUCCESS(status))
	{
		IoDeleteDevice(pDevObj);
		return status;
	}
	return STATUS_SUCCESS;
}

PDRIVER_OBJECT 结构体在这里又被用到了,而且用的还挺多,这里我们主要关心两个,一在注册派遣函数时,对于一些当前不需要的函数,我们采取默认的方式,默认为同一个函数,二对IRP_MJ_DEVICE_CONTROL是我们需要处理 的派遣函数,他是用来跟上层程序进行通讯的。

接下来我们通过IoCreateDevice创建了设备对象,并把这个设备对象关联为名MyDC_link,这样我们上层程序就能够找到他。

既然我们处理了IRP_MJ_DEVICE_CONTROL,看看我们做了什么事情。

extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	DbgPrint("Enter IoctlDispatchRoutine\r\n");
	NTSTATUS status = STATUS_SUCCESS;

	//得到I/O堆栈的当前这一层,也就是IO_STACK_LOCATION结构的指针
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

	ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength;//得到输入缓冲区的大小
	ULONG out_size = stack->Parameters.DeviceIoControl.OutputBufferLength;//得到输出缓冲区的大小
	ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;//得到控制码

	PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到缓冲区指针

	const char src[50] = "hi, from wdm";

	switch (code)
	{						// process request
	case IOCTL1:
		DbgPrint("[GO2CODING] Get ioctl code 1\r\n");
		//显示输入缓冲区数据
		DbgPrint("[GO2CODING]  input:%s",(PCSTR)buffer);

		//将输出缓冲区填充字符

		
		memcpy(buffer, src, strlen(src) + 1);
		out_size = strlen(src) + 1;

		break;
	default:
		status = STATUS_INVALID_VARIANT;
		//如果是没有处理的IRP,则返回STATUS_INVALID_VARIANT,这意味着用户模式的I/O函数失败,但并不会设置GetLastError
	}

	// 完成IRP
	pIrp->IoStatus.Status = status;//设置IRP完成状态,会设置用户模式下的GetLastError
	pIrp->IoStatus.Information = out_size;//设置操作的字节
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP,不增加优先级
	return status;
}

代码很简单,也就是打印出上层程序输入来的字符串,并把输出区都填充为0xAA,发给上层程序。

在上层程序中,如何进行通讯呢?

在windows 中采用DeviceIoControl 向某个已经打开的设备的驱动程序的派遣函数发送IRP:IRP_MJ_DEVICE_CONTROL

DeviceIoControl 的原型如下:

BOOL WINAPI DeviceIoControl(
  _In_        HANDLE       hDevice,
  _In_        DWORD        dwIoControlCode,
  _In_opt_    LPVOID       lpInBuffer,
  _In_        DWORD        nInBufferSize,
  _Out_opt_   LPVOID       lpOutBuffer,
  _In_        DWORD        nOutBufferSize,
  _Out_opt_   LPDWORD      lpBytesReturned,
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);

第一个是打开的设备,第二个是控制码,这里的控制码必须按照这种格式进行#define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x001,METHOD_BUFFERED,FILE_ANY_ACCESS),接下来的是输入输出的缓存区内存和字节数。

我们的上层程序,可以这样来写:

int main()
{
	HANDLE handle = CreateFileA("\\\\.\\MyDC_link", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (handle == INVALID_HANDLE_VALUE) {
		std::cout << "open device fail" << std::endl;
		return 0;
	}
	unsigned char buffer[50] = { 0 };
	unsigned char buffer2[50] = { 0 };
	DWORD len;
	sprintf((char*)buffer, "hello, driver\r\n");
	if (DeviceIoControl(handle, IOCTL1, buffer, strlen((char*)buffer), buffer2, 49, &len, NULL)) {
		printf("outbuffer: %s\n", buffer2);
		
	}
	getchar();
	CloseHandle(handle);
}

驱动程序接收到字符串,log上有显示:

在这里插入图片描述
上层程序打印出来之驱动的消息:

在这里插入图片描述
在这个例子中我们创建了一个设备对象,使用DeviceIoControl 来发送IRP,实现了上层程序和驱动之间的通讯。

如果需要 demo的源代码,可以私信我。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go2coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值