驅動與應用層異步通信

同步就是你叫我去吃飯,我聽到了就和你去吃飯;如果沒有聽到,你就不停的叫,直到我告訴你聽到了,才一起去吃飯。
異步就是你叫我,然後自己去吃飯,我得到消息後可能立即走,也可能等到下班纔去吃飯。

其實在明白了三種通信方式後,很容易使用異步方式來通信。

在調用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

在這裏我是通過手動的方式來觸發異步消息的完成,而不是由系統的消息觸發的。主要是爲了演示的方便。由於比較簡單,所以也沒有使用自旋鎖之類的東西了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章