성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리</h1> <p> 다음과 같은 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MFC 에서 WM_DEVICECHANGE 메시지의 Wparam이 항상 7로 들어옵니다. ; <a target='tab' href='https://social.msdn.microsoft.com/Forums/ko-KR/bd667c77-428b-439f-9cf0-9229de544b65/mfc-wmdevicechange-wparam-7-?forum=visualcplusko'>https://social.msdn.microsoft.com/Forums/ko-KR/bd667c77-428b-439f-9cf0-9229de544b65/mfc-wmdevicechange-wparam-7-?forum=visualcplusko</a> </pre> <br /> 편하게 C# WinForm으로 구현해 보겠습니다. 우선, 기본 상태에서 WM_DEVICECHANGE의 상태 알림을 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } const int WM_DEVICECHANGE = 0x0219; protected override void WndProc(ref Message m) { if (m.Msg == WM_DEVICECHANGE) { Debug.WriteLine(DateTime.Now + ": " + m.Msg + ", " + m.WParam.ToInt64() + ", " + m.LParam.ToInt64()); } base.WndProc(ref m); } } } </pre> <br /> USB 장치를 연결 및 해제 시 다음과 같은 결과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [연결 시] 2018-12-05 오전 8:52:35: 537, <span style='color: blue; font-weight: bold'>7</span>, 0 2018-12-05 오전 8:52:36: 537, <span style='color: blue; font-weight: bold'>7</span>, 0 2018-12-05 오전 8:52:36: 537, <span style='color: blue; font-weight: bold'>7</span>, 0 [해제 시] 2018-12-05 오전 8:52:43: 537, <span style='color: blue; font-weight: bold'>7</span>, 0 2018-12-05 오전 8:52:43: 537, <span style='color: blue; font-weight: bold'>7</span>, 0 </pre> <br /> 보는 바와 같이, 연결과 해제에 따른 WPARAM의 값이 7로 동일하기 때문에 구분을 할 수 있는 기준이 없습니다. 이제 문서에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RegisterDeviceNotification function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerdevicenotificationa'>https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerdevicenotificationa</a> </pre> <br /> RegisterDeviceNotification을 처리해 줄 UsbAlarm이라는 타입을 다음과 같이 만들고,<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; namespace WindowsFormsApp1 { public class UsbAlarm : IDisposable { [DllImport("user32.dll", SetLastError = true)] static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, uint Flags); [DllImport("user32.dll", SetLastError = true)] static extern bool UnregisterDeviceNotification(IntPtr Handle); List<IntPtr> _notifications = new List<IntPtr>(); public const int WM_DEVICECHANGE = 0x0219; public const int DBT_DEVTYP_DEVICEINTERFACE = 0x05; public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000; // 연결된 장치 알아내기 // <a target='tab' href='http://bboogugu.egloos.com/v/591770'>http://bboogugu.egloos.com/v/591770</a> Guid[] GUID_DEVINTERFACE_LIST = { // GUID_DEVINTERFACE_USB_DEVICE new Guid(0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED), // GUID_DEVINTERFACE_COMPORT new Guid(0x86e0d1e0, 0x8089, 0x11d0, 0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73), // GUID_DEVINTERFACE_MODEM new Guid(0x2c7089aa, 0x2e0e, 0x11d1, 0xb1, 0x14, 0x00, 0xc0, 0x4f, 0xc2, 0xaa, 0xe4), // GUID_DEVINTERFACE_DISK new Guid(0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b), // GUID_DEVINTERFACE_HID, new Guid(0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30), // GUID_NDIS_LAN_CLASS new Guid(0xad498944, 0x762f, 0x11d0, 0x8d, 0xcb, 0x00, 0xc0, 0x4f, 0xc3, 0x35, 0x8c), // GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR new Guid(0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18), // GUID_DEVINTERFACE_PARALLEL new Guid(0x97F76EF0, 0xF883, 0x11D0, 0xAF, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x84, 0x5C), // GUID_DEVINTERFACE_PARCLASS new Guid(0x811FC6A5, 0xF728, 0x11D0, 0xA5, 0x37, 0x00, 0x00, 0xF8, 0x75, 0x3E, 0xD1) }; unsafe public UsbAlarm(IntPtr windowHandle) { DEV_BROADCAST_DEVICEINTERFACE filter = new DEV_BROADCAST_DEVICEINTERFACE(); filter.dbcc_size = DEV_BROADCAST_DEVICEINTERFACE.Size; filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; foreach (Guid guid in GUID_DEVINTERFACE_LIST) { filter.dbcc_classguid = guid; DEV_BROADCAST_DEVICEINTERFACE* ptr = &filter; IntPtr ptrStruct = new IntPtr(ptr); IntPtr hDevNotify = RegisterDeviceNotification(windowHandle, ptrStruct, DEVICE_NOTIFY_WINDOW_HANDLE); if (hDevNotify == IntPtr.Zero) { Debug.WriteLine("Failed to register: " + guid); } else { _notifications.Add(hDevNotify); } } } public void Dispose() { foreach (IntPtr handle in _notifications) { UnregisterDeviceNotification(handle); } } } [StructLayout(LayoutKind.Sequential)] struct DEV_BROADCAST_DEVICEINTERFACE { public int dbcc_size; public int dbcc_devicetype; public int dbcc_reserved; public Guid dbcc_classguid; public char dbcc_name; public static readonly int Size = Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE)); } } </pre> <br /> 이렇게 Form에서 사용해 봅니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.ComponentModel; using System.Diagnostics; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { <span style='color: blue; font-weight: bold'>UsbAlarm _usbAlarm;</span> public Form1() { InitializeComponent(); <span style='color: blue; font-weight: bold'>_usbAlarm = new UsbAlarm(this.Handle);</span> } protected override void OnClosing(CancelEventArgs e) { if (_usbAlarm != null) { _usbAlarm.Dispose(); } base.OnClosing(e); } protected override void WndProc(ref Message m) { if (m.Msg == UsbAlarm.WM_DEVICECHANGE) { Debug.WriteLine("Form1: " + DateTime.Now + ": " + m.Msg + ", " + m.WParam.ToInt64() + ", " + m.LParam.ToInt64()); } base.WndProc(ref m); } } } </pre> <br /> 다시 USB 장치를 연결 및 해제하면 이제 그 상태를 WPARAM 값을 통해 구분할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [연결 시] Form1: 2018-12-05 오전 9:08:37: 537, 32768, 5892240 Form1: 2018-12-05 오전 9:08:37: 537, 32768, 5892240 DBT_DEVICEARRIVAL (0x8000 == 32768) [해제 시] Form1: 2018-12-05 오전 9:08:44: 537, 32772, 5892240 Form1: 2018-12-05 오전 9:08:44: 537, 32772, 5892240 DBT_DEVICEREMOVECOMPLETE (0x8004 == 32772) </pre> <br /> 잘 동작하는군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 다시 질문으로 돌아가서, 검색 결과 다이얼로그의 최상위에서만 정상 결과가 들어온다는 이야기를 하고 있습니다. 이게 좀 이상합니다. RegisterDeviceNotification의 첫 번째 인자가 윈도우 핸들 값인데, 윈도우 시스템이 굳이 저 윈도우 핸들의 부모 핸들을 탐색한 후 최상위 윈도우에만 알림을 전달할 것 같지는 않습니다.<br /> <br /> 실제로 테스트를 해볼까요? ^^<br /> <br /> MainForm에 버튼을 추가하고 그에 대해 MyForm 하위 대화창을 띄워두도록 한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.ComponentModel; using System.Diagnostics; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } protected override void WndProc(ref Message m) { if (m.Msg == UsbAlarm.WM_DEVICECHANGE) { Debug.WriteLine("Form1: " + DateTime.Now + ": " + m.Msg + ", " + m.WParam.ToInt64() + ", " + m.LParam.ToInt64()); } base.WndProc(ref m); } private void button1_Click(object sender, EventArgs e) { MyForm form = new MyForm(); form.ShowDialog(); } } } </pre> <br /> MyForm 코드에 UsbAlarm 코드를 구현하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.ComponentModel; using System.Diagnostics; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class MyForm : Form { UsbAlarm _usbAlarm; public MyForm() { InitializeComponent(); _usbAlarm = new UsbAlarm(this.Handle); } protected override void WndProc(ref Message m) { if (m.Msg == UsbAlarm.WM_DEVICECHANGE) { Debug.WriteLine("MyForm: " + DateTime.Now + ": " + m.Msg + ", " + m.WParam.ToInt64() + ", " + m.LParam.ToInt64()); } base.WndProc(ref m); } protected override void OnClosing(CancelEventArgs e) { if (_usbAlarm != null) { _usbAlarm.Dispose(); } base.OnClosing(e); } } } </pre> <br /> USB 연결/해제 시 다음과 같은 알림을 받을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [연결 시] MyForm: 2018-12-05 오전 9:20:58: 537, 7, 0 Form1: 2018-12-05 오전 9:20:58: 537, 7, 0 MyForm: 2018-12-05 오전 9:20:58: 537, 32768, 7331776 MyForm: 2018-12-05 오전 9:20:58: 537, 32768, 7331776 [해제 시] MyForm: 2018-12-05 오전 9:21:05: 537, 32772, 7331776 MyForm: 2018-12-05 오전 9:21:05: 537, 32772, 7331776 MyForm: 2018-12-05 오전 9:21:16: 537, 7, 0 Form1: 2018-12-05 오전 9:21:16: 537, 7, 0 </pre> <br /> 정상적으로 Top-level 윈도우가 아니라 RegisterDeviceNotification에 전달한 윈도우로 이벤트가 잘 전달이 되는 것을 확인할 수 있습니다. 그런데, 왜 뜬금없이 Top-level 윈도우에서만 받을 수 있다는 이야기가 나왔을까요? 이쯤에서 다시 문서를 보면,<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Applications send event notifications using the BroadcastSystemMessage function. Any application with a top-level window can receive <span style='color: blue; font-weight: bold'>basic notifications</span> by processing the WM_DEVICECHANGE message. Applications can use the RegisterDeviceNotification function to register to receive device notifications. </div><br /> <br /> 의문이 풀립니다. 즉, Top-level 윈도우는 RegisterDeviceNotification으로 알림 등록을 하지 않아도 WM_DEVICECHANGE 이벤트를 기본적인 수준에서 받을 수 있다는 내용인데, 질문자가 검색한 글의 작성자는 이것을 잘못 해석한 내용을 써놓았던 것입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1408&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1067
(왼쪽의 숫자를 입력해야 합니다.)