首页
社区
课程
招聘
[原创][成果3.6]驱动和应用层的异步通信
发表于: 2008-1-28 11:13 27398

[原创][成果3.6]驱动和应用层的异步通信

2008-1-28 11:13
27398
这里来简单的讲解下驱动和应用层的异步通信,上次我写了驱动和应用层的三种基本通信方法,但是那三种方法都是通过同步的方法来实现的,就是说,在应用层向驱动层发送消息后,就堵死在那里等待驱动层的返回了,而异步的概念就是,应用层向驱动发送消息后,就马上返回了,而在驱动层的消息触发后,再将该消息反馈给应用层。

给个网上的例子:
同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。

其实在明白了三种通信方式后,很容易使用异步方式来通信。

在调用DeviceIoControl时,不指定FILE_FLAG_OVERLAPPED标志,表示以同步方式调用,调用线程将被阻塞直到控制操作完成.
当指定FILE_FLAG_OVERLAPPED标志调用DeviceIoControl,表示以异步方式调用,调用线程不立即阻塞,直到调用线程需要结果时,调用者调用事件等待函数,等待驱动发出事件.
在异步调用时,得使用事件(event)来通信.

下面看应用层的代码:
// 初始化时创建设备
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  //创建设备
  try
	  m_hCommDevice := CreateFile('\\.\Overlapp',	GENERIC_READ or GENERIC_WRITE,	FILE_SHARE_READ, nil,
		                	OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0);
    //执行异步I/O的设备必须用FILE_FLAG_OVERLAPPED标志打开

    hEvent := CreateEvent( nil, False, False, nil);     //创建自动重置的事件
  except
    MessageBox(Handle, '创建设备失败', '驱动应用层通信', MB_OK + MB_ICONWARNING);
  end;
end;

//异步通信按钮,创建线程和驱动通信
procedure TfrmMain.btnBufferdClick(Sender: TObject);
var
  hThread:THandle;
  dwThreadID:DWORD;
begin
  hThread  := CreateThread(nil, 0, @WaitForNotify, self, 0, dwThreadID);
	CloseHandle(hThread);
end;

//线程的处理代码
//初始化一个OVERLAPPED 结构,然后用DeviceIoControl来通信
//在WaitForSingleObject等待返回
procedure WaitForNotify;
var
  dwReturn: DWORD;
  inData:array[0..1023] of char;
  outData:array[0..1023] of char;
  ol:OVERLAPPED ;
begin
  StrPCopy(inData, Trim(frmMain.edtBufferd_in.Text));

  //OVERLAPPED结构的Offset,OffsetHigh和hEvent成员必须被初始化。在这里初始化为0
  FillMemory(@ol, sizeof(OVERLAPPED), 0);
  ol.hEvent := frmMain.hEvent;

  if frmMain.m_hCommDevice <> 0 then
    DeviceIoControl(frmMain.m_hCommDevice, IOCTL_OVERLAP_BUFFERED_IO, @inData,	Length(inData), @outData, Length(outData), dwReturn, @ol);


  //调用WaitForSingleObject并传递设备内核对象的句柄。WaitForSingleObject会挂起调用线程直至内核对象变成有信号态。
  //设备驱动在它完成I/O后使内核对象有信号。在WaitForSingleObject返回后,I/O被完成 .
  while WaitForSingleObject(frmMain.hEvent,  100) = WAIT_TIMEOUT do
  begin
  end;

  GetOverlappedResult(frmMain.m_hCommDevice, ol, dwReturn, TRUE);
  frmMain.edtBufferd_out.Text := outData;
end;

//这里触发驱动将数据传输回来,异步消息得以完成
procedure TfrmMain.btnNotifyClick(Sender: TObject);
var
  dwReturn:DWORD;
begin
  if m_hCommDevice <> 0 then
    DeviceIoControl(m_hCommDevice, IOCL_OVERLAP_NOTIFY, nil, 0, nil, 0, dwReturn, nil);
end;

//退出时,关闭句柄,这时候如果irp还未处理,即btnNotifyClick这个函数没有触发
//则驱动中会触发取消irp的请求。
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if(m_hCommDevice <> 0) then
		CloseHandle(m_hCommDevice);
  if (hEvent <> 0) then
    CloseHandle(hEvent);
end;


驱动层代码:
//接受到消息的处理函数
NTSTATUS Overlap_IoControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS status = STATUS_NOT_SUPPORTED;
    PIO_STACK_LOCATION irpStack = NULL;
    UINT sizeofWrite = 0;

    DbgPrint("Overlap_IoControl\r\n");

    irpStack = IoGetCurrentIrpStackLocation(Irp);    

    if(irpStack)
    {
        switch(irpStack->Parameters.DeviceIoControl.IoControlCode)
        {
            case IOCTL_OVERLAP_BUFFERED_IO:   //异步通信消息
				 status = Irp->IoStatus.Status = STATUS_PENDING; 
				 Irp->IoStatus.Information = 0;
				 IoMarkIrpPending(Irp); 
				 gUserMessageIrp = Irp;//保存请求的irp
				 IoSetCancelRoutine(Irp, DriverCancelIrp); //设置irp取消例程,在应用程序退出时,触发
				 return status; 
			case IOCL_OVERLAP_NOTIFY:                                //获取数据事件 
				 COMM_BufferedIo(gUserMessageIrp, irpStack);         //处理原来的irp,将传进来的数据传输出去
                 return status;
			default:
				return status;
        }
    }

	return status;
}

//当通知要获取数据时,获得异步的irp,然后传输数据
NTSTATUS COMM_BufferedIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
	ULONG  outputLength, inputLength;

    DbgPrint("COMM_BufferedIo\r\n");

	outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;   //输出缓冲区
    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;    //输入缓冲区

    if(pInputBuffer && pOutputBuffer)
    {              
		DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
		Irp->IoStatus.Status = STATUS_SUCCESS; 
		Irp->IoStatus.Information = sizeof(pOutputBuffer); 
		IoCompleteRequest(Irp,IO_NO_INCREMENT); //设置该irp已经完成
        status = STATUS_SUCCESS;
    }
    return status;
}

//取消irp的例程
VOID DriverCancelIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) 
{ 
	//UNREFERENCED_PARAMETER这个宏用于去掉一个函数的参数未用或函数中定义了一个局部变量却从未用过的编译警告
    UNREFERENCED_PARAMETER(DeviceObject);

    KdPrint(("UserMessageCancelIrp....\n"));
    
    if ( Irp == gUserMessageIrp)
        gUserMessageIrp = NULL; 

    Irp->IoStatus.Status = STATUS_CANCELLED; 
    Irp->IoStatus.Information = 0; 
    IoCompleteRequest(Irp,IO_NO_INCREMENT); 
} 



流程如下:
1、
应用层:
   通知异步消息
   等待返回
驱动层:
   收到消息
   设置irp取消例程
   保存该irp

2、
应用层:
   通知获取数据,即完成异步消息
驱动层:
   拷贝数据到输出缓冲区,完成该irp

在这里我是通过手动的方式来触发异步消息的完成,而不是由系统的消息触发的。主要是为了演示的方便。由于比较简单,所以也没有使用自旋锁之类的东西了。下面是代码了:

[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (21)
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
2
好,分析的很透
2008-1-28 11:46
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
3
嗯, 设置FILE_FLAG_OVERLAPPED标志后, ZwWriteFile等就可以实现异步调用了.

APC...

呵呵. 顶个~
2008-1-28 12:15
0
雪    币: 325
活跃值: (97)
能力值: ( LV13,RANK:530 )
在线值:
发帖
回帖
粉丝
4
   只有学习的份。
2008-1-28 12:32
0
雪    币: 332
活跃值: (30)
能力值: ( LV12,RANK:460 )
在线值:
发帖
回帖
粉丝
5
学习一下
2008-1-28 18:41
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
6
还有一份是羡慕
2008-1-28 19:03
0
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
7
设置FILE_FLAG_OVERLAPPED,用于实现重叠IO,是基于内核事件对象的,与APC是两回事
2008-1-28 19:24
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
8
哦.学习.

俺最近学习APC. 嘿嘿, 混了混了
2008-1-28 20:37
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
不错啊,多谢楼主!!
2009-4-24 00:13
0
雪    币: 209
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
学习写驱动....
2009-4-25 16:35
0
雪    币: 773
活跃值: (442)
能力值: ( LV9,RANK:200 )
在线值:
发帖
回帖
粉丝
11
晕 我早看见这个程序就好了 我这几天百度 GOOGLE 猛个搜索
2009-10-1 07:58
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
谢谢楼主分享
2010-11-2 20:35
0
雪    币: 163
活跃值: (75)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
13
其实就是用户层建一个线程在那阻塞着等
2010-11-2 21:35
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
爱死你了LZ。太给力了,竟然是DelphiDE
2010-11-6 05:35
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
这用情况下应该怎么调试应用层程序呢???
2010-11-6 11:00
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
先安装驱动程序,在调试应用层序,要是崩了但是没有来得及关闭驱动程序怎么办?我看还是在虚拟机中安装一个应用层序开发的环境还是保险吧……
2010-11-6 11:23
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
在调试驱动层代码的时候,我发现如下的结果好像有问题?
//当通知要获取数据时,获得异步的irp,然后传输数据

//-----驱动层代码
NTSTATUS COMM_BufferedIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
  ULONG  outputLength, inputLength;

    DbgPrint("COMM_BufferedIo\r\n");

  outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;   //输出缓冲区
    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;    //输入缓冲区

    if(pInputBuffer && pOutputBuffer)
    {              
    DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = sizeof(pOutputBuffer);
    IoCompleteRequest(Irp,IO_NO_INCREMENT); //设置该irp已经完成
        status = STATUS_SUCCESS;
    }
    return status;
}
//---------------

在上面这段代码中outputLength 和inputLength的值都为0;而输入缓冲和输出缓冲为同一个缓冲区。
这样的话拷贝缓冲的代码
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
不会执行,或者说执行完后结果不受影响。
2010-11-6 13:03
0
雪    币: 249
活跃值: (25)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
18
在虚拟机上面放一个debugview,在应用层程序里面用OutputDebugString来输出,用debugview来看结果.这样麻烦一些,但是免去虚拟机安装开发环境了,毕竟那玩意儿很慢
2010-12-7 09:58
0
雪    币: 249
活跃值: (25)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
19
  
 
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength; //[COLOR=red]为零[/COLOR]    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;  //[COLOR=red]为零[/COLOR]    if(pInputBuffer && pOutputBuffer)
    {              
    DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength); //很奇怪为什么它可以复制成功呢
    Irp->IoStatus.Status = STATUS_SUCCESS; 
    Irp->IoStatus.Information = sizeof(pOutputBuffer);   [COLOR=red]//指针大小为4,所以返回只能有4个字符.[/COLOR]
 
  
 


还有就是应用程序退出不了是怎么回事儿啊~不知道那个取消IRP的函数什么时候调用
2010-12-7 17:56
0
雪    币: 249
活跃值: (25)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
20
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;

pIoStackIrp这个是错误的,因为这个应该是IOCL_OVERLAP_NOTIFY的栈顶指针,并不是IOCTL_OVERLAP_BUFFERED_IO的,应该定义一个全局的PIO_STACK_LOCATION,像保存PIRP一样保存起来:gUserMessageIrp = Irp;//保存请求的irp  girpStack = irpStack;这样在后面取得的Inbuff和OutBuff的长度才是正确的.

刚学驱动,如果哪里讲错,楼主别介意...都两年了,不知道楼主还看得到不?
2010-12-8 10:02
0
雪    币: 249
活跃值: (25)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
21
case IOCL_OVERLAP_NOTIFY:                                //获取数据事件 
       COMM_BufferedIo(gUserMessageIrp, irpStack);   //处理原来的irp,将传进来的数据传输出去
       return status;

上面的分支处理有点问题,会导致在应用层无法CloseHandle,原因是这个IRP没有正确处理.改成以下就没有问题了:

case IOCL_OVERLAP_NOTIFY:                                //获取数据事件 
	COMM_BufferedIo(gUserMessageIrp, girpStack);         //处理原来的irp,将传进来的数据传输出去

	[COLOR="Red"]Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);[/COLOR]                
                return STATUS_SUCCESS;


希望楼主在帖子上把这些问题改掉
2010-12-8 11:22
0
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
2010-12-8 14:31
0
游客
登录 | 注册 方可回帖
返回
//