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

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

2008-1-28 11:13
27507

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

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

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

在调用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;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 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
游客
登录 | 注册 方可回帖
返回
//