How to determine the addition of a new serial port?

For communication with microcontrollers I use a serial port. I am using TCommPortDriver 2.1, which works great. However, it does not have the ability to detect the addition or removal of new components. This happens regularly during the session.

Is there an event that reports when a component has been added or removed?

Update 1

I tried the first RRUZ sentence and turned it into a standalone program. It responds to WM_DEVICECHANGE when the cable is connected or turned off, but WParam does not show the arrival or removal of the device. Results:

 msg = 537, wparam = 7, lparam = 0 msg = 537, wparam = 7, lparam = 0 msg = 537, wparam = 7, lparam = 0 

The first message is sent when the USB cable is connected, and the next two when it is connected. The message WM_DEVICECHANGE (537) is displayed in the message WM_DEVICECHANGE , but WParam is 7, which is not equal to WM_DEVICECHANGE or DBT_DEVICEARRIVAL . I changed the code to process the message a LParam , but since LParam is zero, this is useless. The results are identical to VCL and FMX. For verification, see Code below.

Update 2

Now I got the WMI code. It only starts when a COM port is added; when it is removed, there is no reaction. Results:

 TargetInstance.ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} TargetInstance.Description : Arduino Mega ADK R3 TargetInstance.Name : Arduino Mega ADK R3 (COM4) TargetInstance.PNPDeviceID : USB\VID_2341&PID_0044\64935343733351E0E1D1 TargetInstance.Status : OK 

Perhaps this explains the fact that in another code this is not considered as adding a COM port? It looks like the new connection is seen as a USB port (which is actually). The Arduino driver translates this to a COM port, but WMI is not recognized. Windows messaging "sees" a change in the COM port, but cannot determine whether it has been added or removed.

Anyway: changing the device works. I only need to list the COM ports to see which one is really present, and that was what I already did manually. Now I can do it automatically using WM_DEVICECHANGE . I just add an event to the CPDrv component.

Thanks to RRUZ for your code and help!

  unit dev_change; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TProc = procedure (text: string) of object; BroadcastHdr = ^DEV_BROADCAST_HDR; DEV_BROADCAST_HDR = packed record dbch_size: DWORD; dbch_devicetype: DWORD; dbch_reserved: DWORD; end; TDevBroadcastHdr = DEV_BROADCAST_HDR; type PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE; DEV_BROADCAST_DEVICEINTERFACE = record dbcc_size: DWORD; dbcc_devicetype: DWORD; dbcc_reserved: DWORD; dbcc_classguid: TGUID; dbcc_name: Char; end; TDevBroadcastDeviceInterface = DEV_BROADCAST_DEVICEINTERFACE; const DBT_DEVICESOMETHING = $0007; DBT_DEVICEARRIVAL = $8000; DBT_DEVICEREMOVECOMPLETE = $8004; DBT_DEVTYP_DEVICEINTERFACE = $00000005; type TDeviceNotifyProc = procedure(Sender: TObject; const DeviceName: String) of Object; TDeviceNotifier = class private hRecipient: HWND; FNotificationHandle: Pointer; FDeviceArrival: TDeviceNotifyProc; FDeviceRemoval: TDeviceNotifyProc; FOnWin: TProc; procedure WndProc(var Msg: TMessage); public constructor Create(GUID_DEVINTERFACE : TGUID); property OnDeviceArrival: TDeviceNotifyProc read FDeviceArrival write FDeviceArrival; property OnDeviceRemoval: TDeviceNotifyProc read FDeviceRemoval write FDeviceRemoval; destructor Destroy; override; property OnWin: TProc read FOnWin write FOnWin; end; TForm1 = class(TForm) Memo: TMemo; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } DeviceNotifier : TDeviceNotifier; public { Public declarations } procedure arrival(Sender: TObject; const DeviceName: String); procedure report (text: string); end; var Form1: TForm1; implementation {$R *.dfm} constructor TDeviceNotifier.Create(GUID_DEVINTERFACE : TGUID); var NotificationFilter: TDevBroadcastDeviceInterface; begin inherited Create; hRecipient := AllocateHWnd(WndProc); ZeroMemory (@NotificationFilter, SizeOf(NotificationFilter)); NotificationFilter.dbcc_size := SizeOf(NotificationFilter); NotificationFilter.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE; NotificationFilter.dbcc_classguid := GUID_DEVINTERFACE; //register the device class to monitor FNotificationHandle := RegisterDeviceNotification(hRecipient, @NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); end; procedure TDeviceNotifier.WndProc(var Msg: TMessage); var Dbi: PDevBroadcastDeviceInterface; begin OnWin (Format ('msg = %d, wparam = %d, lparam = %d', [msg.Msg, msg.WParam, msg.LParam])); with Msg do if (Msg = WM_DEVICECHANGE) and ((WParam = DBT_DEVICEARRIVAL) or (WParam = DBT_DEVICEREMOVECOMPLETE) or (WParam = DBT_DEVICESOMETHING)) then try Dbi := PDevBroadcastDeviceInterface (LParam); if Dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE then begin if WParam = DBT_DEVICEARRIVAL then begin if Assigned(FDeviceArrival) then FDeviceArrival(Self, PChar(@Dbi.dbcc_name)); end else if WParam = DBT_DEVICEREMOVECOMPLETE then begin if Assigned(FDeviceRemoval) then FDeviceRemoval(Self, PChar(@Dbi.dbcc_name)); end; end; except Result := DefWindowProc(hRecipient, Msg, WParam, LParam); end else Result := DefWindowProc(hRecipient, Msg, WParam, LParam); end; destructor TDeviceNotifier.Destroy; begin UnregisterDeviceNotification(FNotificationHandle); DeallocateHWnd(hRecipient); inherited; end; procedure TForm1.arrival(Sender: TObject; const DeviceName: String); begin report (DeviceName); ShowMessage(DeviceName); end; procedure TForm1.FormCreate(Sender: TObject); const GUID_DEVINTERFACE_COMPORT : TGUID = '{86E0D1E0-8089-11D0-9CE4-08003E301F73}'; begin DeviceNotifier:=TDeviceNotifier.Create(GUID_DEVINTERFACE_COMPORT); DeviceNotifier.FDeviceArrival:=arrival; DeviceNotifier.OnWin := report; end; procedure TForm1.FormDestroy(Sender: TObject); begin DeviceNotifier.Free; end; procedure TForm1.report (text: string); begin Memo.Lines.Add (text); end; end. 
+7
source share
2 answers

You can use the RegisterDeviceNotification WinAPI function that passes DEV_BROADCAST_DEVICEINTERFACE with the GUID_DEVINTERFACE_COMPORT interface GUID_DEVINTERFACE_COMPORT .

Try this sample.

 type PDevBroadcastHdr = ^DEV_BROADCAST_HDR; DEV_BROADCAST_HDR = packed record dbch_size: DWORD; dbch_devicetype: DWORD; dbch_reserved: DWORD; end; TDevBroadcastHdr = DEV_BROADCAST_HDR; type PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE; DEV_BROADCAST_DEVICEINTERFACE = record dbcc_size: DWORD; dbcc_devicetype: DWORD; dbcc_reserved: DWORD; dbcc_classguid: TGUID; dbcc_name: Char; end; TDevBroadcastDeviceInterface = DEV_BROADCAST_DEVICEINTERFACE; const DBT_DEVICEARRIVAL = $8000; DBT_DEVICEREMOVECOMPLETE = $8004; DBT_DEVTYP_DEVICEINTERFACE = $00000005; type TDeviceNotifyProc = procedure(Sender: TObject; const DeviceName: String) of Object; TDeviceNotifier = class private hRecipient: HWND; FNotificationHandle: Pointer; FDeviceArrival: TDeviceNotifyProc; FDeviceRemoval: TDeviceNotifyProc; procedure WndProc(var Msg: TMessage); public constructor Create(GUID_DEVINTERFACE : TGUID); property OnDeviceArrival: TDeviceNotifyProc read FDeviceArrival write FDeviceArrival; property OnDeviceRemoval: TDeviceNotifyProc read FDeviceRemoval write FDeviceRemoval; destructor Destroy; override; end; type TForm17 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } DeviceNotifier : TDeviceNotifier; public { Public declarations } procedure arrival(Sender: TObject; const DeviceName: String); end; var Form17: TForm17; implementation {$R *.dfm} constructor TDeviceNotifier.Create(GUID_DEVINTERFACE : TGUID); var NotificationFilter: TDevBroadcastDeviceInterface; begin inherited Create; hRecipient := AllocateHWnd(WndProc); ZeroMemory(@NotificationFilter, SizeOf(NotificationFilter)); NotificationFilter.dbcc_size := SizeOf(NotificationFilter); NotificationFilter.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE; NotificationFilter.dbcc_classguid := GUID_DEVINTERFACE; //register the device class to monitor FNotificationHandle := RegisterDeviceNotification(hRecipient, @NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); end; procedure TDeviceNotifier.WndProc(var Msg: TMessage); var Dbi: PDevBroadcastDeviceInterface; begin with Msg do if (Msg = WM_DEVICECHANGE) and ((WParam = DBT_DEVICEARRIVAL) or (WParam = DBT_DEVICEREMOVECOMPLETE)) then try Dbi := PDevBroadcastDeviceInterface(LParam); if Dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE then begin if WParam = DBT_DEVICEARRIVAL then begin if Assigned(FDeviceArrival) then FDeviceArrival(Self, PChar(@Dbi.dbcc_name)); end else if WParam = DBT_DEVICEREMOVECOMPLETE then begin if Assigned(FDeviceRemoval) then FDeviceRemoval(Self, PChar(@Dbi.dbcc_name)); end; end; except Result := DefWindowProc(hRecipient, Msg, WParam, LParam); end else Result := DefWindowProc(hRecipient, Msg, WParam, LParam); end; destructor TDeviceNotifier.Destroy; begin UnregisterDeviceNotification(FNotificationHandle); DeallocateHWnd(hRecipient); inherited; end; procedure TForm17.arrival(Sender: TObject; const DeviceName: String); begin ShowMessage(DeviceName); end; procedure TForm17.FormCreate(Sender: TObject); const GUID_DEVINTERFACE_COMPORT : TGUID = '{86E0D1E0-8089-11D0-9CE4-08003E301F73}'; begin DeviceNotifier:=TDeviceNotifier.Create(GUID_DEVINTERFACE_COMPORT); DeviceNotifier.FDeviceArrival:=arrival; end; procedure TForm17.FormDestroy(Sender: TObject); begin DeviceNotifier.Free; end; end. 
+10
source

Another option is to use WMI events in this case, using the __InstanceCreationEvent Event and the Win32_PnPEntity WMI class, you can filter the serial ports added using the {4d36e978-e325-11ce-bfc1-08002be10318} class of the GUID, writing the WQL sentence in this way

 Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_PnPEntity" AND TargetInstance.ClassGuid="{4d36e978-e325-11ce-bfc1-08002be10318}" 

Try this sample

 {$APPTYPE CONSOLE} {$R *.res} uses Windows, {$IF CompilerVersion > 18.5} Forms, {$IFEND} SysUtils, ActiveX, ComObj, WbemScripting_TLB; type TWmiAsyncEvent = class private FWQL : string; FSink : TSWbemSink; FLocator : ISWbemLocator; FServices : ISWbemServices; procedure EventReceived(ASender: TObject; const objWbemObject: ISWbemObject; const objWbemAsyncContext: ISWbemNamedValueSet); public procedure Start; constructor Create; Destructor Destroy;override; end; //Detect when a key was pressed in the console window function KeyPressed:Boolean; var lpNumberOfEvents : DWORD; lpBuffer : TInputRecord; lpNumberOfEventsRead : DWORD; nStdHandle : THandle; begin Result:=false; nStdHandle := GetStdHandle(STD_INPUT_HANDLE); lpNumberOfEvents:=0; GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents); if lpNumberOfEvents<> 0 then begin PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead); if lpNumberOfEventsRead <> 0 then begin if lpBuffer.EventType = KEY_EVENT then begin if lpBuffer.Event.KeyEvent.bKeyDown then Result:=true else FlushConsoleInputBuffer(nStdHandle); end else FlushConsoleInputBuffer(nStdHandle); end; end; end; { TWmiAsyncEvent } constructor TWmiAsyncEvent.Create; const strServer ='.'; strNamespace ='root\CIMV2'; strUser =''; strPassword =''; begin inherited Create; CoInitializeEx(nil, COINIT_MULTITHREADED); FLocator := CoSWbemLocator.Create; FServices := FLocator.ConnectServer(strServer, strNamespace, strUser, strPassword, '', '', wbemConnectFlagUseMaxWait, nil); FSink := TSWbemSink.Create(nil); FSink.OnObjectReady := EventReceived; FWQL:='Select * From __InstanceCreationEvent Within 1 '+ 'Where TargetInstance ISA "Win32_PnPEntity" AND TargetInstance.ClassGuid="{4d36e978-e325-11ce-bfc1-08002be10318}" '; end; destructor TWmiAsyncEvent.Destroy; begin if FSink<>nil then FSink.Cancel; FLocator :=nil; FServices :=nil; FSink :=nil; CoUninitialize; inherited; end; procedure TWmiAsyncEvent.EventReceived(ASender: TObject; const objWbemObject: ISWbemObject; const objWbemAsyncContext: ISWbemNamedValueSet); var PropVal: OLEVariant; begin PropVal := objWbemObject; Writeln(Format('TargetInstance.ClassGuid : %s ',[String(PropVal.TargetInstance.ClassGuid)])); Writeln(Format('TargetInstance.Description : %s ',[String(PropVal.TargetInstance.Description)])); Writeln(Format('TargetInstance.Name : %s ',[String(PropVal.TargetInstance.Name)])); Writeln(Format('TargetInstance.PNPDeviceID : %s ',[String(PropVal.TargetInstance.PNPDeviceID)])); Writeln(Format('TargetInstance.Status : %s ',[String(PropVal.TargetInstance.Status)])); end; procedure TWmiAsyncEvent.Start; begin Writeln('Listening events...Press Any key to exit'); FServices.ExecNotificationQueryAsync(FSink.DefaultInterface,FWQL,'WQL', 0, nil, nil); end; var AsyncEvent : TWmiAsyncEvent; begin try AsyncEvent:=TWmiAsyncEvent.Create; try AsyncEvent.Start; //The next loop is only necessary in this sample console sample app //In VCL forms Apps you don't need use a loop while not KeyPressed do begin {$IF CompilerVersion > 18.5} Sleep(100); Application.ProcessMessages; {$IFEND} end; finally AsyncEvent.Free; end; except on E:EOleException do Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode])); on E:Exception do Writeln(E.Classname, ':', E.Message); end; end. 
+6
source

All Articles