2013-01-08 32 views
7

Để liên lạc với bộ điều khiển vi mô, tôi sử dụng cổng nối tiếp. Tôi sử dụng TCommPortDriver 2.1 hoạt động tốt. Tuy nhiên, nó thiếu khả năng phát hiện thêm hoặc loại bỏ các comports mới. Điều này xảy ra thường xuyên trong một phiên.Làm cách nào để phát hiện thêm cổng nối tiếp mới?

Có sự kiện nào cho biết khi nào một bổ sung đã được thêm hoặc xóa không?

Cập nhật 1

tôi đã cố gắng đề nghị đầu tiên của RRUZ và biến nó thành một chương trình độc lập. Nó phản ứng trên WM_DEVICECHANGE khi cáp được cắm vào hoặc ra, nhưng WParam không hiển thị việc đến hoặc tháo thiết bị. Kết quả là:

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

Thông điệp đầu tiên được gửi khi cáp USB được cắm ra và tiếp theo hai khi nó được cắm vào Phần thông điệp cho thấy WM_DEVICECHANGE nhắn (537) nhưng WParam là 7, đó là. không phải WM_DEVICECHANGE hoặc DBT_DEVICEARRIVAL. Tôi sửa đổi mã phần nào theo thứ tự tin nhắn được xử lý nhưng như LParam là số không này là không sử dụng. Kết quả giống hệt với VCL và FMX. Như một kiểm tra xem mã dưới đây.

Cập nhật 2

bây giờ tôi đã nhận mã WMI chạy. Nó chỉ cháy khi một cổng COM được thêm vào, không có phản ứng khi một bị loại bỏ. Kết quả:

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 

Điều này có thể giải thích thực tế là mã khác này không được xem là bổ sung cổng COM không? Nó xuất hiện để xem kết nối mới như một cổng USB (những gì nó thực sự là). Trình điều khiển Arduino dịch thành cổng COM nhưng không được WMI nhận diện. Windows nhắn tin 'thấy' một cổng COM thay đổi nhưng không thể phát hiện cho dù nó được thêm vào hoặc gỡ bỏ.

Dù sao đi nữa: thay đổi thiết bị hoạt động. Tôi chỉ cần liệt kê các cổng COM để xem cổng nào thực sự hiện diện và đó là điều tôi đã làm theo cách thủ công. Bây giờ tôi có thể làm điều đó tự động với WM_DEVICECHANGE. Tôi chỉ cần thêm một sự kiện vào thành phần CPDrv.

Cảm ơn RRUZ về mã của bạn và trợ giúp!

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

Bạn có thể có một số may mắn bằng WMI sự kiện, chỉ cần sử dụng sự kiện DEVICE_CHANGE và tìm kiếm cổng nối tiếp mới được bổ sung –

+1

Mã WMI được đăng không bao gồm phát hiện thiết bị loại bỏ, vì vậy bạn phải thay đổi câu WQL thành 'Select * From __InstanceDeletionEvent Trong vòng 1 Where TargetInstance ISA" Win32_PnPEntity "AND TargetInstance.ClassGuid =" {4d36e978-e325-11ce-bfc1- 08002be10318} "' – RRUZ

+1

hoặc để phát hiện sự xuất hiện hoặc loại bỏ thiết bị bằng một câu WQL đơn, bạn có thể sử dụng sự kiện WMI '__InstanceOperationEvent'. – RRUZ

Trả lời

10

Bạn có thể sử dụng chức năng RegisterDeviceNotification WinAPI đi qua các cấu trúc DEV_BROADCAST_DEVICEINTERFACE với lớp giao diện GUID_DEVINTERFACE_COMPORT thiết bị.

Thử mẫu này.

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

Rất tiếc, cảm ơn rất nhiều! Tôi đã thêm biểu mẫu vào ứng dụng hiện tại của tôi với mã bạn đã cung cấp. Khi thay đổi cổng com WndProc được gọi nhưng không đến. Việc kiểm tra chặt chẽ 'Msg' cho thấy rằng' Msg' của nó thực sự là 'WM_DEVICECHANGE. WParam' là 7, không phải 'DBT_DEVICEARRIVAL' (tất cả khác là 0). Thay đổi điều kiện sao cho nó được thực thi khi 'WParam' là 7,' Dbi: = PDevBroadcastDeviceInterface (LParam); 'trả về' nil', tôi nên biết vì 'LParam' = 0.' WndProc' chỉ được gọi một lần khi thay đổi cổng com. Ý tưởng nào? – Arnold

+0

@Arnold, bạn đang thêm loại cổng nối tiếp nào? vì tôi đã thử sử dụng [bộ điều hợp cổng nối tiếp USB] (http://www.amazon.com/Belkin-SERIAL-PORT-ADAPTER-F5U409-CU/dp/B00009L7MI) và thêm [cổng nối tiếp ảo] (http: // www.mks.zp.ua/products/vspdxp/) và trong cả hai trường hợp, mã hoạt động tốt (Windows 7 x64). – RRUZ

+0

Tôi thêm cáp USB (cũng) được xem là cổng nối tiếp của ComPortDriver. – Arnold

6

lựa chọn khác là sử dụng các sự kiện WMI, về trường hợp này bằng cách sử dụng __InstanceCreationEvent tổ chức sự kiện và lớp Win32_PnPEntity WMI bạn có thể lọc các cổng nối tiếp thêm vào bằng cách sử dụng GUID {4d36e978-e325-11ce-bfc1-08002be10318} lớp, writting một câu WQL như vậy

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

Hãy thử mẫu này

{$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

Tôi thích cách bạn chơi với WMI, RRUZ! +1 – Pateman

Các vấn đề liên quan