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.
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.
EnumSerialDevicesreturns the list of serial ports (including modems and Bluetooth/USB virtual COM ports).EnumUsbDevicesreturns the list of USB devices with their instance paths, VID/PID, class GUID, and manufacturer string. - Hot-plug events.
OnSerialDeviceAdded,OnSerialDeviceRemoved,OnUsbDeviceAdded, andOnUsbDeviceRemovedfire whenever the OS reports a hardware change. You can react immediately instead of polling. - USB control (Windows).
EnableUsbDeviceandDisableUsbDevicechange 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 theOnSerialDeviceAddedandOnUsbDeviceAddedevents 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
wclSerialMonitorclass is cross-platform. On macOS, the same API enumerates serial ports exposed as/dev/cu.*and/dev/tty.*nodes. USB enumeration and theEnableUsbDevice/DisableUsbDevicemethods are Windows-specific and return an error on macOS. - Which events does
wclSerialMonitorexpose? OnStarted,OnStopped,OnSerialDeviceAdded,OnSerialDeviceRemoved,OnUsbDeviceAdded, andOnUsbDeviceRemoved.- 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.