2013-01-08 25 views
7

Do komunikacji z mikrokontrolerami używam portu szeregowego. Używam TCommPortDriver 2.1, który działa dobrze. Jednak nie ma możliwości wykrycia dodania lub usunięcia nowych komportów. Dzieje się to regularnie podczas sesji.Jak wykryć dodanie nowego portu szeregowego?

Czy jest jakieś wydarzenie informujące o dodaniu lub usunięciu konta?

Update 1

Próbowałem pierwszą sugestię RRUZ i przekształcił go w samodzielny program. Reaguje on na WM_DEVICECHANGE, gdy kabel jest podłączony lub odłączony, ale WParam nie pokazuje przybycia lub usunięcia urządzenia. Wyniki są następujące:

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

Pierwsza wiadomość zostanie wysłana, gdy kabel USB jest podłączony out i dwa następne, gdy jest on podłączony Część wiadomość pojawia się komunikat WM_DEVICECHANGE (537), ale WParam wynosi 7, co jest. nie WM_DEVICECHANGE lub . Zmieniłem trochę kod, aby wiadomość została przetworzona, ale jako LParam jest zero, to nie ma sensu. Wyniki są identyczne z VCL i FMX. Weryfikacja patrz kod poniżej.

Aktualizacja 2

ja teraz mam kod WMI działa. Występuje tylko przy dodaniu portu COM, brak reakcji po usunięciu. Wyniki:

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 

Czy to może wyjaśniać fakt, że w drugim kodzie nie jest to postrzegane jako dodanie portu COM? Wygląda na to, że nowe połączenie wygląda jak port USB (czym on jest). Sterownik Arduino tłumaczy to na port COM, ale nie jest to rozpoznawane przez WMI. Komunikat systemu Windows "widzi" zmianę portu COM, ale nie może wykryć, czy jest ona dodawana czy usuwana.

W każdym razie: zmiana urządzenia działa. Muszę tylko wyliczyć porty COM, aby zobaczyć, który z nich jest rzeczywiście obecny i to było coś, co już zrobiłem ręcznie. Teraz mogę to zrobić automatycznie za pomocą WM_DEVICECHANGE. Dodaję tylko zdarzenie do komponentu CPDrv.

Dzięki RRUZ za kod i pomoc!

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. 
+1

można mieć trochę szczęścia przy użyciu zdarzeń WMI, wystarczy użyć zdarzenia DEVICE_CHANGE i spojrzeć na nowo dodawanych portów szeregowych –

+1

Opublikowany kod WMI nie zawiera wykrywania urządzenia do usuwania, ponieważ musisz zmienić zdanie WQL na 'Wybierz * z __InstanceDeletionEvent w ciągu 1 Gdzie TargetInstance ISA" Win32_PnPEntity "AND TargetInstance.ClassGuid =" {4d36e978-e325-11ce-bfc1- 08002be10318} "' – RRUZ

+1

lub do wykrywania przybycia lub usunięcia urządzenia za pomocą pojedynczego zdania WQL można użyć zdarzenia WMI "__InstanceOperationEvent". – RRUZ

Odpowiedz

10

Można użyć funkcji RegisterDeviceNotification WinAPI przechodząc strukturę DEV_BROADCAST_DEVICEINTERFACE z klasą interfejsu GUID_DEVINTERFACE_COMPORT urządzenia.

Wypróbuj tę próbkę.

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. 
+0

Wew, wielkie dzięki! Dodałem formularz do mojej obecnej aplikacji z podanym kodem. Podczas zmiany portu WndProc jest wywoływany, ale nie przychodzi. Dokładna inspekcja 'Msg' pokazuje, że jej' Msg' jest rzeczywiście 'WM_DEVICECHANGE. WParam' ma 7, a nie "DBT_DEVICEARRIVAL" (wszystkie inne to 0). Zmiana warunku takiego, że zostanie wykonana, gdy 'WParam' ma wartość 7,' Dbi: = PDevBroadcastDeviceInterface (LParam); 'zwraca' nil', powinienem wiedzieć, ponieważ 'LParam' = 0.' WndProc' jest wywoływany tylko raz podczas zmiany port COM. Jakieś pomysły? – Arnold

+0

@Arnold, jaki rodzaj portu szeregowego dodajesz? ponieważ próbowałem użyć [adapter portu szeregowego USB] (http://www.amazon.com/Belkin-SERIAL-PORT-ADAPTER-F5U409-CU/dp/B00009L7MI) i dodawać [wirtualne porty szeregowe] (http: // www.mks.zp.ua/products/vspdxp/) iw obu przypadkach kod działa poprawnie (Windows 7 x64). – RRUZ

+0

Dodaję kabel USB, który jest (również) postrzegany jako port szeregowy przez ComPortDriver. – Arnold

6

Inną opcją jest korzystanie z WMI zdarzenia, w tym przypadku z wykorzystaniem __InstanceCreationEvent zdarzenie i klasę Win32_PnPEntity WMI można filtrować porty szeregowe dodawane przy użyciu identyfikatora GUID {4d36e978-e325-11ce-bfc1-08002be10318} klasy, pisanie zdanie WQL jak tak

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

Spróbuj próbkę

{$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. 
+0

Uwielbiam sposób, w jaki grasz z WMI, RRUZ! +1 – Pateman