如何检测添加新的串口?
Posted
技术标签:
【中文标题】如何检测添加新的串口?【英文标题】:How to detect addition of new serial port? 【发布时间】:2012-12-22 10:53:37 【问题描述】:为了与微控制器通信,我使用串行端口。我使用工作正常的 TCommPortDriver 2.1。但是,它缺乏检测新组件的添加或删除的能力。这在会话期间经常发生。
是否有一个事件表明组件何时被添加或删除?
更新 1
我尝试了RRUZ的第一个建议并将其变成了一个独立的程序。当电缆插入或拔出时,它会对WM_DEVICECHANGE
作出反应,但WParam
不会显示设备的到达或移除。结果是:
msg = 537, wparam = 7, lparam = 0
msg = 537, wparam = 7, lparam = 0
msg = 537, wparam = 7, lparam = 0
第一条消息在 USB 数据线拔出时发送,接下来的两条消息在插入时发送。
消息部分显示WM_DEVICECHANGE
消息(537),但WParam
是7,而不是WM_DEVICECHANGE
或DBT_DEVICEARRIVAL
。我稍微修改了代码以便处理消息,但由于LParam
为零,这没有用。结果与 VCL 和 FMX 相同。作为检查,请参见下面的代码。
更新 2
我现在运行了 WMI 代码。它仅在添加 COM 端口时触发,删除时没有反应。结果:
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
这是否可以解释在其他代码中这不被视为添加 COM 端口的事实?它似乎将新连接视为 USB 端口(实际上是什么)。 Arduino 驱动程序将其转换为 COM 端口,但 WMI 无法识别。 Windows 消息“看到”COM 端口更改,但无法检测到它是添加还是删除。
无论如何:设备更改有效。我只需要枚举 COM 端口即可查看实际存在的 COM 端口,这是我已经手动完成的。现在我可以使用WM_DEVICECHANGE
自动执行此操作。我只是在 CPDrv 组件中添加一个事件。
感谢 RRUZ 提供代码和帮助!
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.
【问题讨论】:
使用 WMI 事件可能会有一些运气,只需使用 DEVICE_CHANGE 事件并寻找新添加的串行端口 贴出的WMI代码不包含移除设备检测,需要将WQL语句改为Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA "Win32_PnPEntity" AND TargetInstance.ClassGuid="4d36e978-e325-11ce-bfc1-08002be10318"
或使用单个 WQL 语句检测设备的到达或移除,您可以使用 __InstanceOperationEvent
WMI 事件。
+1 为您提供详尽的解释。我测试了 __InstanceOperationEvent ,但它需要一些后处理,因为无法从响应中区分删除和添加。我准备好了我的组件,很高兴看到它对删除和添加做出反应。感谢您的代码和帮助!
【参考方案1】:
您可以将DEV_BROADCAST_DEVICEINTERFACE
结构与GUID_DEVINTERFACE_COMPORT
设备接口类一起使用RegisterDeviceNotification
WinAPI 函数。
试试这个示例。
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.
【讨论】:
哇,非常感谢!我使用您提供的代码向我当前的应用程序添加了一个表单。更改 com 端口时,调用 WndProc 但未到达。仔细检查Msg
表明它的Msg
确实是WM_DEVICECHANGE. WParam
是7,而不是DBT_DEVICEARRIVAL
(所有其他都是0)。更改条件使其在WParam
为7 时执行,Dbi := PDevBroadcastDeviceInterface (LParam);
返回nil
,我应该知道因为LParam
= 0。WndProc
在更改com 端口时只调用一次。有什么想法吗?
@Arnold,你要添加什么样的串口?因为我尝试使用 USB serial port adapter 并添加 virtual serial ports 并且在这两种情况下代码都可以正常工作(Windows 7 x64)。
我添加了一根 USB 电缆,它(也)被 ComPortDriver 视为串行端口。
可能是您的应用程序中的某些东西干扰了设备通知,您可以尝试使用普通 vcl 表单应用程序中的代码吗?
这不会改变结果。在问题更新中查看我的报告。【参考方案2】:
另一个选项是使用 WMI 事件,在这种情况下使用 __InstanceCreationEvent
事件和 Win32_PnPEntity
WMI 类,您可以过滤使用 4d36e978-e325-11ce-bfc1-08002be10318
类 GUID 添加的串行端口,像这样编写 WQL 语句
Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_PnPEntity" AND TargetInstance.ClassGuid="4d36e978-e325-11ce-bfc1-08002be10318"
试试这个示例
$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.
【讨论】:
我喜欢你玩 WMI 的方式,RRUZ! +1以上是关于如何检测添加新的串口?的主要内容,如果未能解决你的问题,请参考以下文章