Search code examples
delphiexception

How to handle an exception when USB-Serial port is removed unexpectedly?


My Delphi application (using XE3) needs to handle the EInOutError exception that occurs when a USB-Serial port is removed. The application is used in a test-environment, so cannot rely on an operator to click on the OK button to close the Application Error dialog.

I have tried the following:

  • The "try .. except" method - this does not catch these exceptions. I think this does not work because the exception is not caused by the code in the "try" block. It seems to be a lower-level "system-level" exception.

  • I tried adding an "ApplicationEvents" component to my form. The OnException method catches a "Custom" exception generated by my application, but not the system-level exception.

  • I have also tried adding a global exception hook (as described in Is it possible to have a global exception hook?). This partly works - it allows me to do things before the Application Error dialog, but does not stop the error dialog.

I would appreciate any ideas!


Solution

  • Exceptions arising due to USB-Com removal are very annoying. So I'd recommend to eliminate most of reasons for them.

    You have to process Windows message WM_DEVICECHANGE and detect port removing. Then set special flag and don't do any operation with port with this flag on! If USB-Com is plugged on again, then reinit serial port. Some code to help:

        const
          DBT_DEVICEARRIVAL = $8000;
          DBT_DEVICEREMOVECOMPLETE = $8004;
          DBT_DEVICEQUERYREMOVE = $8001;
          DBT_DEVTYP_PORT = 3;
    
        type
           PDevBroadcastHdr = ^TDevBroadcastHdr;
           TDevBroadcastHdr = packed record
            dbcd_size: DWORD;
            dbcd_devicetype: DWORD;
            dbcd_reserved: DWORD;
          end;
    
          PDEV_BROADCAST_PORT = ^TDEV_BROADCAST_PORT;
          TDEV_BROADCAST_PORT = packed record
            dbcp_size: DWord;
            dbcp_devicetype: DWord;
            dbcp_reserved: DWord;
            dbcp_name: array[0..MAX_PATH] of Char;
          end;
    
        ...
        procedure WMDEVICECHANGE(var Msg: TMessage); message WM_DEVICECHANGE;
        ...
    
    procedure TForm1.WMDEVICECHANGE(var Msg: TMessage);
    var
      prt: PDEV_BROADCAST_PORT;
      s: string;
    begin
    
      if Msg.wparam =  DBT_DEVICEREMOVECOMPLETE then
        if PDevBroadcastHdr(Msg.lParam)^.dbcd_devicetype = DBT_DEVTYP_PORT then
          begin
    
            b_PortRemoved := True; //check this flag before each operation with port.
    
    
            prt := PDEV_BROADCAST_PORT(PDEV_BROADCAST_PORT(Msg.lParam));
            s := prt.dbcp_name;
            ShowMessage('ComPort ' + s + ' has been removed. What can I do?');
        end;
    
      if Msg.wparam =  DBT_DEVICEARRIVAL then
        if PDevBroadcastHdr(Msg.lParam)^.dbcd_devicetype = DBT_DEVTYP_PORT then begin
           // USB-COM plugged, you can find it and make some reinitialisation
        end;
    
    
    end;