The Serial Framework's wclSerialMonitor class gives your application a live view of the hardware. It enumerates serial ports and USB devices, notifies you when a device is plugged in or unplugged, and — on Windows — lets you enable or disable a USB device from code. This article walks through a complete Serial Monitor sample implemented in Delphi, C++ Builder, C#, and VB.NET.

Serial Monitor architecture — your application subscribes to events from wclSerialMonitor, which watches serial and USB devices on Windows and macOS

The wclSerialMonitor class is the entry point for everything related to device enumeration and hot-plug tracking in the Serial Framework. It works together with the rest of the library: once you have a device name (for example, COM4 on Windows or /dev/cu.usbserial-1420 on macOS), you can hand it over to the serial client class and start a communication session. See Serial Framework How-To for the connection workflow, and this article for everything you need to discover the hardware in the first place.


What is the Serial Monitor

The wclSerialMonitor is a small, self-contained class whose job is to answer one question continuously: what serial and USB devices are attached right now, and which of them changed since the last check? It exposes three groups of functionality:

  • Enumeration. EnumSerialDevices returns the list of serial ports (including modems and Bluetooth/USB virtual COM ports). EnumUsbDevices returns the list of USB devices with their instance paths, VID/PID, class GUID, and manufacturer string.
  • Hot-plug events. OnSerialDeviceAdded, OnSerialDeviceRemoved, OnUsbDeviceAdded, and OnUsbDeviceRemoved fire whenever the OS reports a hardware change. You can react immediately instead of polling.
  • USB control (Windows). EnableUsbDevice and DisableUsbDevice change the driver state of a USB device by its instance path. This is handy for tools that need to reset a misbehaving adapter.

Because the monitor is implemented on top of the platform APIs, it behaves identically on Windows and macOS from your application's point of view. Only the set of available features differs slightly — USB enumeration and control are Windows-only; serial enumeration and all events are cross-platform.


Getting started

The sample we are building is a simple Windows Forms (or VCL) application with a handful of buttons and a log window. The essential pieces are the same in every language: create the monitor, subscribe to events, then call Start when you are ready to receive them.

Creating the monitor

The monitor is created once, usually in the form's Create or Load handler. All event subscriptions happen here as well. There is nothing to configure before Start — sensible defaults are used.

procedure TfmMain.FormCreate(Sender: TObject);
begin
  FMonitor := TwclSerialMonitor.Create;
  FMonitor.OnStarted := MonitorStarted;
  FMonitor.OnStopped := MonitorStopped;
  FMonitor.OnSerialDeviceAdded := MonitorSerialDeviceAdded;
  FMonitor.OnSerialDeviceRemoved := MonitorSerialDeviceRemoved;
  FMonitor.OnUsbDeviceAdded := MonitorUsbDeviceAdded;
  FMonitor.OnUsbDeviceRemoved := MonitorUsbDeviceRemoved;
end;
void __fastcall TfmMain::FormCreate(TObject *Sender)
{
  FMonitor = new TwclSerialMonitor();
  FMonitor->OnStarted = MonitorStarted;
  FMonitor->OnStopped = MonitorStopped;
  FMonitor->OnSerialDeviceAdded = MonitorSerialDeviceAdded;
  FMonitor->OnSerialDeviceRemoved = MonitorSerialDeviceRemoved;
  FMonitor->OnUsbDeviceAdded = MonitorUsbDeviceAdded;
  FMonitor->OnUsbDeviceRemoved = MonitorUsbDeviceRemoved;
}
private void fmMain_Load(Object sender, EventArgs e)
{
  FMonitor = new wclSerialMonitor();
  FMonitor.OnStarted += MonitorStarted;
  FMonitor.OnStopped += MonitorStopped;
  FMonitor.OnSerialDeviceAdded += MonitorSerialDeviceAdded;
  FMonitor.OnSerialDeviceRemoved += MonitorSerialDeviceRemoved;
  FMonitor.OnUsbDeviceAdded += MonitorUsbDeviceAdded;
  FMonitor.OnUsbDeviceRemoved += MonitorUsbDeviceRemoved;
}
Private Sub fmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  FMonitor = New wclSerialMonitor()
  AddHandler FMonitor.OnStarted, AddressOf MonitorStarted
  AddHandler FMonitor.OnStopped, AddressOf MonitorStopped
  AddHandler FMonitor.OnSerialDeviceAdded, AddressOf MonitorSerialDeviceAdded
  AddHandler FMonitor.OnSerialDeviceRemoved, AddressOf MonitorSerialDeviceRemoved
  AddHandler FMonitor.OnUsbDeviceAdded, AddressOf MonitorUsbDeviceAdded
  AddHandler FMonitor.OnUsbDeviceRemoved, AddressOf MonitorUsbDeviceRemoved
End Sub

Starting and stopping

Enumeration works at any time, but the hot-plug events are only delivered after Start has been called. Call Stop before the form is destroyed — for example, in the form's Destroy or FormClosed handler — so the OS notification handler is released cleanly.

procedure TfmMain.btStartClick(Sender: TObject);
var
  Res: Integer;
begin
  Res := FMonitor.Start;
  if Res <> WCL_E_SUCCESS then
    lbLog.Items.Add('Start failed: 0x' + IntToHex(Res, 8));
end;

procedure TfmMain.btStopClick(Sender: TObject);
var
  Res: Integer;
begin
  Res := FMonitor.Stop;
  if Res <> WCL_E_SUCCESS then
    lbLog.Items.Add('Stop failed: 0x' + IntToHex(Res, 8));
end;

procedure TfmMain.FormDestroy(Sender: TObject);
begin
  FMonitor.Stop;
  FMonitor.Free;
end;
void __fastcall TfmMain::btStartClick(TObject *Sender)
{
  int Res = FMonitor->Start();
  if (Res != WCL_E_SUCCESS)
    lbLog->Items->Add("Start failed: 0x" + IntToHex(Res, 8));
}

void __fastcall TfmMain::btStopClick(TObject *Sender)
{
  int Res = FMonitor->Stop();
  if (Res != WCL_E_SUCCESS)
    lbLog->Items->Add("Stop failed: 0x" + IntToHex(Res, 8));
}

void __fastcall TfmMain::FormDestroy(TObject *Sender)
{
  FMonitor->Stop();
  FMonitor->Free();
}
private void btStart_Click(Object sender, EventArgs e)
{
  Int32 Res = FMonitor.Start();
  if (Res != wclErrors.WCL_E_SUCCESS)
    lbLog.Items.Add("Start failed: 0x" + Res.ToString("X8"));
}

private void btStop_Click(Object sender, EventArgs e)
{
  Int32 Res = FMonitor.Stop();
  if (Res != wclErrors.WCL_E_SUCCESS)
    lbLog.Items.Add("Stop failed: 0x" + Res.ToString("X8"));
}

private void fmMain_FormClosed(Object sender, FormClosedEventArgs e)
{
  FMonitor.Stop();
}
Private Sub btStart_Click(sender As Object, e As EventArgs) Handles btStart.Click
  Dim Res As Int32 = FMonitor.Start()
  If Res <> wclErrors.WCL_E_SUCCESS Then
    lbLog.Items.Add("Start failed: 0x" + Res.ToString("X8"))
  End If
End Sub

Private Sub btStop_Click(sender As Object, e As EventArgs) Handles btStop.Click
  Dim Res As Int32 = FMonitor.Stop()
  If Res <> wclErrors.WCL_E_SUCCESS Then
    lbLog.Items.Add("Stop failed: 0x" + Res.ToString("X8"))
  End If
End Sub

Private Sub fmMain_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
  FMonitor.Stop()
End Sub

Device events

Six events are available. They are delivered on the same thread that called Start, so if your UI thread is the one that started the monitor you can update the UI directly from the handlers. Each event carries a single argument — the device name for serial devices, the instance path for USB devices, or no argument at all for OnStarted / OnStopped.

  • OnStarted, OnStopped — the monitor has begun or ended listening for hardware notifications.
  • OnSerialDeviceAdded, OnSerialDeviceRemoved — a serial port appeared or disappeared.
  • OnUsbDeviceAdded, OnUsbDeviceRemoved — a USB device appeared or disappeared (Windows only).
procedure TfmMain.MonitorStarted(Sender: TObject);
begin
  lbLog.Items.Add('Monitor started');
end;

procedure TfmMain.MonitorStopped(Sender: TObject);
begin
  lbLog.Items.Add('Monitor stopped');
end;

procedure TfmMain.MonitorSerialDeviceAdded(Sender: TObject;
  const DeviceName: String);
begin
  lbLog.Items.Add('Device added: ' + DeviceName);
end;

procedure TfmMain.MonitorSerialDeviceRemoved(Sender: TObject;
  const DeviceName: String);
begin
  lbLog.Items.Add('Device removed: ' + DeviceName);
end;

procedure TfmMain.MonitorUsbDeviceAdded(Sender: TObject;
  const Instance: String);
begin
  lbLog.Items.Add('USB added: ' + Instance);
end;

procedure TfmMain.MonitorUsbDeviceRemoved(Sender: TObject;
  const Instance: String);
begin
  lbLog.Items.Add('USB removed: ' + Instance);
end;
void __fastcall TfmMain::MonitorStarted(TObject *Sender)
{
  lbLog->Items->Add("Monitor started");
}

void __fastcall TfmMain::MonitorStopped(TObject *Sender)
{
  lbLog->Items->Add("Monitor stopped");
}

void __fastcall TfmMain::MonitorSerialDeviceAdded(TObject *Sender,
  const String DeviceName)
{
  lbLog->Items->Add("Device added: " + DeviceName);
}

void __fastcall TfmMain::MonitorSerialDeviceRemoved(TObject *Sender,
  const String DeviceName)
{
  lbLog->Items->Add("Device removed: " + DeviceName);
}

void __fastcall TfmMain::MonitorUsbDeviceAdded(TObject *Sender,
  const String Instance)
{
  lbLog->Items->Add("USB added: " + Instance);
}

void __fastcall TfmMain::MonitorUsbDeviceRemoved(TObject *Sender,
  const String Instance)
{
  lbLog->Items->Add("USB removed: " + Instance);
}
private void MonitorStarted(Object sender, EventArgs e)
{
  lbLog.Items.Add("Monitor started");
}

private void MonitorStopped(Object sender, EventArgs e)
{
  lbLog.Items.Add("Monitor stopped");
}

private void MonitorSerialDeviceAdded(Object sender, String DeviceName)
{
  lbLog.Items.Add("Device added: " + DeviceName);
}

private void MonitorSerialDeviceRemoved(Object sender, String DeviceName)
{
  lbLog.Items.Add("Device removed: " + DeviceName);
}

private void MonitorUsbDeviceAdded(Object sender, String Instance)
{
  lbLog.Items.Add("USB added: " + Instance);
}

private void MonitorUsbDeviceRemoved(Object sender, String Instance)
{
  lbLog.Items.Add("USB removed: " + Instance);
}
Private Sub MonitorStarted(sender As Object, e As EventArgs)
  lbLog.Items.Add("Monitor started")
End Sub

Private Sub MonitorStopped(sender As Object, e As EventArgs)
  lbLog.Items.Add("Monitor stopped")
End Sub

Private Sub MonitorSerialDeviceAdded(sender As Object, DeviceName As String)
  lbLog.Items.Add("Device added: " + DeviceName)
End Sub

Private Sub MonitorSerialDeviceRemoved(sender As Object, DeviceName As String)
  lbLog.Items.Add("Device removed: " + DeviceName)
End Sub

Private Sub MonitorUsbDeviceAdded(sender As Object, Instance As String)
  lbLog.Items.Add("USB added: " + Instance)
End Sub

Private Sub MonitorUsbDeviceRemoved(sender As Object, Instance As String)
  lbLog.Items.Add("USB removed: " + Instance)
End Sub

Enumerating serial devices

EnumSerialDevices fills an array of wclSerialDevice structures. Each entry carries the device's OS name (for example, COM4 on Windows or /dev/cu.usbserial-1420 on macOS), a human-readable friendly name, and a flag indicating whether the device is a modem. The friendly name comes from the driver metadata and is the string you should display in the UI; the raw device name is what you pass to the serial client when opening the port.

procedure TfmMain.btEnumSerialClick(Sender: TObject);
var
  Res: Integer;
  Devices: TwclSerialDevices;
  i: Integer;
  Item: TListItem;
  Column: TListColumn;
begin
  lvDevices.Items.Clear;
  lvDevices.Columns.Clear;

  Column := lvDevices.Columns.Add;
  Column.Caption := 'Device name';
  Column.Width := 80;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'Friendly name';
  Column.Width := 350;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'IsModem';
  Column.Width := 70;

  Res := FMonitor.EnumSerialDevices(Devices);
  if Res <> WCL_E_SUCCESS then
    lbLog.Items.Add('Enum serial devices failed: 0x' + IntToHex(Res, 8))
  else begin
    if Length(Devices) = 0 then
      lbLog.Items.Add('No serial devices found')
    else begin
      lbLog.Items.Add('Found ' + IntToStr(Length(Devices)) +
        ' serial devices');
      for i := 0 to Length(Devices) - 1 do begin
        Item := lvDevices.Items.Add;
        Item.Caption := Devices[i].DeviceName;
        Item.SubItems.Add(Devices[i].FriendlyName);
        Item.SubItems.Add(BoolToStr(Devices[i].IsModem, True));
      end;
    end;
  end;
end;
void __fastcall TfmMain::btEnumSerialClick(TObject *Sender)
{
  lvDevices->Items->Clear();
  lvDevices->Columns->Clear();

  TListColumn* Column = lvDevices->Columns->Add();
  Column->Caption = "Device name";
  Column->Width = 80;
  Column = lvDevices->Columns->Add();
  Column->Caption = "Friendly name";
  Column->Width = 350;
  Column = lvDevices->Columns->Add();
  Column->Caption = "IsModem";
  Column->Width = 70;

  TwclSerialDevices Devices;
  int Res = FMonitor->EnumSerialDevices(Devices);
  if (Res != WCL_E_SUCCESS)
  {
    lbLog->Items->Add("Enum serial devices failed: 0x" + IntToHex(Res, 8));
    return;
  }

  if (Devices.Length == 0)
  {
    lbLog->Items->Add("No serial devices found");
    return;
  }

  lbLog->Items->Add("Found " + IntToStr(Devices.Length) + " serial devices");
  for (int i = 0; i < Devices.Length; i++)
  {
    TListItem* Item = lvDevices->Items->Add();
    Item->Caption = Devices[i].DeviceName;
    Item->SubItems->Add(Devices[i].FriendlyName);
    Item->SubItems->Add(BoolToStr(Devices[i].IsModem, true));
  }
}
private void btEnumSerial_Click(Object sender, EventArgs e)
{
  lvDevices.Items.Clear();
  lvDevices.Columns.Clear();

  ColumnHeader Column = lvDevices.Columns.Add("Device name");
  Column.Width = 80;
  Column = lvDevices.Columns.Add("Friendly name");
  Column.Width = 350;
  Column = lvDevices.Columns.Add("IsModem");
  Column.Width = 70;

  List<wclSerialDevice> Devices;
  Int32 Res = FMonitor.EnumSerialDevices(out Devices);
  if (Res != wclErrors.WCL_E_SUCCESS)
  {
    lbLog.Items.Add("Enum serial devices failed: 0x" + Res.ToString("X8"));
    return;
  }

  if (Devices == null || Devices.Count == 0)
  {
    lbLog.Items.Add("No serial devices found");
    return;
  }

  lbLog.Items.Add("Found " + Devices.Count.ToString() + " serial devices");
  foreach (wclSerialDevice Device in Devices)
  {
    ListViewItem Item = lvDevices.Items.Add(Device.DeviceName);
    Item.SubItems.Add(Device.FriendlyName);
    Item.SubItems.Add(Device.IsModem.ToString());
  }
}
Private Sub btEnumSerial_Click(sender As Object, e As EventArgs) Handles btEnumSerial.Click
  lvDevices.Items.Clear()
  lvDevices.Columns.Clear()

  Dim Column As ColumnHeader = lvDevices.Columns.Add("Device name")
  Column.Width = 80
  Column = lvDevices.Columns.Add("Friendly name")
  Column.Width = 350
  Column = lvDevices.Columns.Add("IsModem")
  Column.Width = 70

  Dim Devices As List(Of wclSerialDevice) = Nothing
  Dim Res As Int32 = FMonitor.EnumSerialDevices(Devices)
  If Res <> wclErrors.WCL_E_SUCCESS Then
    lbLog.Items.Add("Enum serial devices failed: 0x" + Res.ToString("X8"))
    Return
  End If

  If Devices Is Nothing Or Devices.Count = 0 Then
    lbLog.Items.Add("No serial devices found")
    Return
  End If

  lbLog.Items.Add("Found " + Devices.Count.ToString() + " serial devices")
  For Each Device As wclSerialDevice In Devices
    Dim Item As ListViewItem = lvDevices.Items.Add(Device.DeviceName)
    Item.SubItems.Add(Device.FriendlyName)
    Item.SubItems.Add(Device.IsModem.ToString())
  Next
End Sub

Enumerating USB devices

EnumUsbDevices is the USB counterpart. It returns a list of wclUsbDevice records, each containing the instance path (a system-wide unique string), the friendly name, the vendor and product IDs (VID / PID), the device class GUID, the manufacturer string, and an Enabled flag that tells you whether the device's driver is currently active. This method is Windows-specific: on macOS, the call returns an empty list and no error, since the platform does not expose a comparable device tree.

procedure TfmMain.btEnumUSBClick(Sender: TObject);
var
  Column: TListColumn;
  Res: Integer;
  Devices: TwclUsbDevices;
  i: Integer;
  Item: TListItem;
begin
  lvDevices.Items.Clear;
  lvDevices.Columns.Clear;

  Column := lvDevices.Columns.Add;
  Column.Caption := 'Instance';
  Column.Width := 250;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'Friendly name';
  Column.Width := 250;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'VID';
  Column.Width := 50;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'PID';
  Column.Width := 50;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'Class';
  Column.Width := 250;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'Manufacturer';
  Column.Width := 200;
  Column := lvDevices.Columns.Add;
  Column.Caption := 'Enabled';
  Column.Width := 70;

  Res := FMonitor.EnumUsbDevices(Devices);
  if Res <> WCL_E_SUCCESS then
    lbLog.Items.Add('Enum USB devices failed: 0x' + IntToHex(Res, 8))
  else begin
    if Length(Devices) = 0 then
      lbLog.Items.Add('No USB devices found')
    else begin
      lbLog.Items.Add('Found ' + IntToStr(Length(Devices)) + ' USB devices');
      for i := 0 to Length(Devices) - 1 do begin
        Item := lvDevices.Items.Add;
        Item.Caption := Devices[i].Instance;
        Item.SubItems.Add(Devices[i].FriendlyName);
        Item.SubItems.Add(IntToHex(Devices[i].VendorId, 4));
        Item.SubItems.Add(IntToHex(Devices[i].ProductId, 4));
        Item.SubItems.Add(GUIDToString(Devices[i].ClassGuid));
        Item.SubItems.Add(Devices[i].Manufacturer);
        Item.SubItems.Add(BoolToStr(Devices[i].Enabled, True));
      end;
    end;
  end;
end;
void __fastcall TfmMain::btEnumUSBClick(TObject *Sender)
{
  lvDevices->Items->Clear();
  lvDevices->Columns->Clear();

  TListColumn* Column = lvDevices->Columns->Add();
  Column->Caption = "Instance";       Column->Width = 250;
  Column = lvDevices->Columns->Add();
  Column->Caption = "Friendly name";  Column->Width = 250;
  Column = lvDevices->Columns->Add();
  Column->Caption = "VID";            Column->Width = 50;
  Column = lvDevices->Columns->Add();
  Column->Caption = "PID";            Column->Width = 50;
  Column = lvDevices->Columns->Add();
  Column->Caption = "Class";          Column->Width = 250;
  Column = lvDevices->Columns->Add();
  Column->Caption = "Manufacturer";   Column->Width = 200;
  Column = lvDevices->Columns->Add();
  Column->Caption = "Enabled";        Column->Width = 70;

  TwclUsbDevices Devices;
  int Res = FMonitor->EnumUsbDevices(Devices);
  if (Res != WCL_E_SUCCESS)
  {
    lbLog->Items->Add("Enum USB devices failed: 0x" + IntToHex(Res, 8));
    return;
  }

  if (Devices.Length == 0)
  {
    lbLog->Items->Add("No USB devices found");
    return;
  }

  lbLog->Items->Add("Found " + IntToStr(Devices.Length) + " USB devices");
  for (int i = 0; i < Devices.Length; i++)
  {
    TListItem* Item = lvDevices->Items->Add();
    Item->Caption = Devices[i].Instance;
    Item->SubItems->Add(Devices[i].FriendlyName);
    Item->SubItems->Add(IntToHex(Devices[i].VendorId, 4));
    Item->SubItems->Add(IntToHex(Devices[i].ProductId, 4));
    Item->SubItems->Add(Sysutils::GUIDToString(Devices[i].ClassGuid));
    Item->SubItems->Add(Devices[i].Manufacturer);
    Item->SubItems->Add(BoolToStr(Devices[i].Enabled, true));
  }
}
private void btEnumUsb_Click(Object sender, EventArgs e)
{
  lvDevices.Items.Clear();
  lvDevices.Columns.Clear();

  ColumnHeader Column = lvDevices.Columns.Add("Instance");
  Column.Width = 250;
  Column = lvDevices.Columns.Add("Friendly name");
  Column.Width = 250;
  Column = lvDevices.Columns.Add("VID");
  Column.Width = 50;
  Column = lvDevices.Columns.Add("PID");
  Column.Width = 50;
  Column = lvDevices.Columns.Add("Class");
  Column.Width = 250;
  Column = lvDevices.Columns.Add("Manufacturer");
  Column.Width = 200;
  Column = lvDevices.Columns.Add("Enabled");
  Column.Width = 70;

  List<wclUsbDevice> Devices;
  Int32 Res = FMonitor.EnumUsbDevices(out Devices);
  if (Res != wclErrors.WCL_E_SUCCESS)
  {
    lbLog.Items.Add("Enum USB devices failed: 0x" + Res.ToString("X8"));
    return;
  }

  if (Devices == null || Devices.Count == 0)
  {
    lbLog.Items.Add("No USB devices found");
    return;
  }

  lbLog.Items.Add("Found " + Devices.Count.ToString() + " USB devices");
  foreach (wclUsbDevice Device in Devices)
  {
    ListViewItem Item = lvDevices.Items.Add(Device.Instance);
    Item.SubItems.Add(Device.FriendlyName);
    Item.SubItems.Add(Device.VendorId.ToString("X4"));
    Item.SubItems.Add(Device.ProductId.ToString("X4"));
    Item.SubItems.Add(Device.ClassGuid.ToString());
    Item.SubItems.Add(Device.Manufacturer);
    Item.SubItems.Add(Device.Enabled.ToString());
  }
}
Private Sub btEnumUsb_Click(sender As Object, e As EventArgs) Handles btEnumUsb.Click
  lvDevices.Items.Clear()
  lvDevices.Columns.Clear()

  Dim Column As ColumnHeader = lvDevices.Columns.Add("Instance")
  Column.Width = 250
  Column = lvDevices.Columns.Add("Friendly name")
  Column.Width = 250
  Column = lvDevices.Columns.Add("VID")
  Column.Width = 50
  Column = lvDevices.Columns.Add("PID")
  Column.Width = 50
  Column = lvDevices.Columns.Add("Class")
  Column.Width = 250
  Column = lvDevices.Columns.Add("Manufacturer")
  Column.Width = 200
  Column = lvDevices.Columns.Add("Enabled")
  Column.Width = 70

  Dim Devices As List(Of wclUsbDevice) = Nothing
  Dim Res As Int32 = FMonitor.EnumUsbDevices(Devices)
  If Res <> wclErrors.WCL_E_SUCCESS Then
    lbLog.Items.Add("Enum USB devices failed: 0x" + Res.ToString("X8"))
    Return
  End If

  If Devices Is Nothing Or Devices.Count = 0 Then
    lbLog.Items.Add("No USB devices found")
    Return
  End If

  lbLog.Items.Add("Found " + Devices.Count.ToString() + " USB devices")
  For Each Device As wclUsbDevice In Devices
    Dim Item As ListViewItem = lvDevices.Items.Add(Device.Instance)
    Item.SubItems.Add(Device.FriendlyName)
    Item.SubItems.Add(Device.VendorId.ToString("X4"))
    Item.SubItems.Add(Device.ProductId.ToString("X4"))
    Item.SubItems.Add(Device.ClassGuid.ToString())
    Item.SubItems.Add(Device.Manufacturer)
    Item.SubItems.Add(Device.Enabled.ToString())
  Next
End Sub

Enabling and disabling USB devices

On Windows, a USB device can be temporarily disabled and re-enabled at the driver level without physically unplugging it. This is useful for resetting a device that has entered an inconsistent state, for power management, or for testing. EnableUsbDevice and DisableUsbDevice take the device's instance path — the first column of the USB enumeration — and return a WCL error code. Note that elevation (UAC) is required for both operations.

procedure TfmMain.btEnableClick(Sender: TObject);
begin
  SwitchUsbDevice(True);
end;

procedure TfmMain.btDisableClick(Sender: TObject);
begin
  SwitchUsbDevice(False);
end;

procedure TfmMain.SwitchUsbDevice(const Enable: Boolean);
var
  Instance: string;
  Res: Integer;
begin
  if lvDevices.Selected = nil then begin
    ShowMessage('Select USB device');
    Exit;
  end;

  Instance := lvDevices.Selected.Caption;
  if Enable then
    Res := FMonitor.EnableUsbDevice(Instance)
  else
    Res := FMonitor.DisableUsbDevice(Instance);
  if Res <> WCL_E_SUCCESS then begin
    if Enable then
      ShowMessage('Error enabling USB: 0x' + IntToHex(Res, 8))
    else
      ShowMessage('Error disabling USB: 0x' + IntToHex(Res, 8));
  end else begin
    if Enable then
      ShowMessage('Device enabled')
    else
      ShowMessage('Device disabled');
  end;
end;
void __fastcall TfmMain::btEnableClick(TObject *Sender)
{
  SwitchUsbDevice(true);
}

void __fastcall TfmMain::btDisableClick(TObject *Sender)
{
  SwitchUsbDevice(false);
}

void __fastcall TfmMain::SwitchUsbDevice(const bool Enable)
{
  if (lvDevices->Selected == NULL)
  {
    ShowMessage("Select USB device");
    return;
  }

  int Res;
  String Instance = lvDevices->Selected->Caption;
  if (Enable)
    Res = FMonitor->EnableUsbDevice(Instance);
  else
    Res = FMonitor->DisableUsbDevice(Instance);
  if (Res != WCL_E_SUCCESS)
  {
    if (Enable)
      ShowMessage("Error enabling USB: 0x" + IntToHex(Res, 8));
    else
      ShowMessage("Error disabling USB: 0x" + IntToHex(Res, 8));
    return;
  }

  ShowMessage(Enable ? "Device enabled" : "Device disabled");
}
private void btEnable_Click(Object sender, EventArgs e)
{
  SwitchUsbDevice(true);
}

private void btDisable_Click(Object sender, EventArgs e)
{
  SwitchUsbDevice(false);
}

private void SwitchUsbDevice(Boolean Enable)
{
  if (lvDevices.SelectedItems.Count == 0)
  {
    MessageBox.Show("Select USB device");
    return;
  }

  String Instance = lvDevices.SelectedItems[0].Text;
  Int32 Res = Enable
    ? FMonitor.EnableUsbDevice(Instance)
    : FMonitor.DisableUsbDevice(Instance);
  if (Res != wclErrors.WCL_E_SUCCESS)
  {
    MessageBox.Show(
      (Enable ? "Error enabling USB: 0x" : "Error disabling USB: 0x")
      + Res.ToString("X8"));
    return;
  }

  MessageBox.Show(Enable ? "Device enabled" : "Device disabled");
}
Private Sub btEnable_Click(sender As Object, e As EventArgs) Handles btEnable.Click
  SwitchUsbDevice(True)
End Sub

Private Sub btDisable_Click(sender As Object, e As EventArgs) Handles btDisable.Click
  SwitchUsbDevice(False)
End Sub

Private Sub SwitchUsbDevice(Enable As Boolean)
  If lvDevices.SelectedItems.Count = 0 Then
    MessageBox.Show("Select USB device")
    Return
  End If

  Dim Instance As String = lvDevices.SelectedItems(0).Text
  Dim Res As Int32
  If Enable Then
    Res = FMonitor.EnableUsbDevice(Instance)
  Else
    Res = FMonitor.DisableUsbDevice(Instance)
  End If
  If Res <> wclErrors.WCL_E_SUCCESS Then
    If Enable Then
      MessageBox.Show("Error enabling USB: 0x" + Res.ToString("X8"))
    Else
      MessageBox.Show("Error disabling USB: 0x" + Res.ToString("X8"))
    End If
    Return
  End If

  If Enable Then
    MessageBox.Show("Device enabled")
  Else
    MessageBox.Show("Device disabled")
  End If
End Sub

Cross-platform notes: macOS

The wclSerialMonitor class is fully cross-platform. On macOS the same API enumerates serial ports exposed as /dev/cu.* and /dev/tty.* nodes; the DeviceName field contains the full path you would pass to CreateFile / open, and FriendlyName typically carries the driver-supplied product string. All four events — OnStarted, OnStopped, OnSerialDeviceAdded, and OnSerialDeviceRemoved — work identically. USB enumeration and the EnableUsbDevice / DisableUsbDevice methods are Windows-only, and will return an appropriate error code on macOS.

The Serial Framework itself does not ship a UI, so the same core code runs everywhere. The sample application's UI layer, however, is platform-specific: the Windows demos use WinForms and VCL, while the macOS demo builds a native Cocoa UI through the Objective-C runtime. The only part that matters for the SDK is the core — and that core is byte-for-byte identical on both operating systems:

// macOS (C#) — the Serial Framework API is identical to Windows
wclSerialMonitor monitor = new wclSerialMonitor();
monitor.OnStarted += (s, e) => Console.WriteLine("Monitor started");
monitor.OnSerialDeviceAdded += (s, name) =>
  Console.WriteLine("Added: " + name);
monitor.OnSerialDeviceRemoved += (s, name) =>
  Console.WriteLine("Removed: " + name);

monitor.Start();

List<wclSerialDevice> devices;
int res = monitor.EnumSerialDevices(out devices);
if (res == wclErrors.WCL_E_SUCCESS && devices != null)
{
  foreach (wclSerialDevice dev in devices)
    Console.WriteLine(dev.DeviceName + " — " + dev.FriendlyName);
}

monitor.Stop();

On macOS the printed DeviceName values will look like /dev/cu.usbserial-1420 or /dev/cu.Bluetooth-Incoming-Port — that is, the full device node path. On Windows they will be COM4, COM5, and so on. Everything else in your application logic is unaffected.


Frequently asked questions

Why do I need to call Start() before enumerating devices?
Enumeration works at any time, but only after Start() will the OnSerialDeviceAdded and OnUsbDeviceAdded events fire when a device is plugged in at runtime. If you only need a one-shot list of attached devices, Start() is not strictly required — but it costs nothing, and having it always on keeps your UI in sync with reality.
Does the Serial Monitor work on macOS?
Yes. The wclSerialMonitor class is cross-platform. On macOS, the same API enumerates serial ports exposed as /dev/cu.* and /dev/tty.* nodes. USB enumeration and the EnableUsbDevice / DisableUsbDevice methods are Windows-specific and return an error on macOS.
Which events does wclSerialMonitor expose?
OnStarted, OnStopped, OnSerialDeviceAdded, OnSerialDeviceRemoved, OnUsbDeviceAdded, and OnUsbDeviceRemoved.
Do I need administrator privileges for USB enumeration?
Enumeration itself is available to standard users. Enabling or disabling a USB device (EnableUsbDevice / DisableUsbDevice) requires elevation because it changes the device's driver state.