|
Introduction
To perform some tasks, we still need to trap unmanaged windows messages, even though we are developing managed code. This is a little tutorial that tries to clarify how to trap those messages using the .NET Framework.
The code is written in C#. I believe it will be quite easy for those who are reading this document, and you can use VB.NET™ instead of C#, to port the concepts.
Learn by example
Now, to clarify the concepts lets use something useful. Maybe some of the readers have the need to detect if a CD, or for what matters any removable volume mounted on a device which supports a software ejection method (DVD, Zip, etc…), has been inserted or removed from a device. This can be accomplished by detecting the WM_CHANGEDEVICE message.
Trapping messages in .NET
There are, to my knowledge, two ways of trapping windows messages in the Microsoft .NET Framework.:
The IMessageFilter interface
The most obvious, and I suspect the least useful, is to use the IMessageFilter interface.
The BCL defines an IMessageFilter interface, which describes a single method named PreFilterMessage, which can be used to trap Windows messages. After defining a class which implements the IMessageFilter interface, we just need to tell the Application class, that it should add it to the queue of IMessageFilter interfaces it can handle using the Application.AddMessageFilter method. We can also remove a filter using the Application.RemoveMessageFilter method from the queue. Now, this approach is only useful to trap dispatched messages, it does not handle all messages. This process is out of scope in this document, anyway, as an example example, we would have to use something like: using System;
using System.Windows.Forms;
public class MyFilter: IMessageFilter
{
public bool PreFilterMessage(ref Message aMessage)
{
if (aMessage.Msg==WM_AMESSAGE)
{
}
return false;
}
}
At some other point of your code you would have to register an instance of MyFilter, like:
MyFilter fFilter = new MyFilter();
(…)
Application.AddMessageFilter(fFilter);
(…)
Application.RemoveMessageFilter(fFilter);
Overriding a WndProc method
The solution, in our example, is overriding a WndProc method of a Control or to do the same with an implementation of the NativeWindow class. The first solution is useful if we need to trap messages in a class that inherits the Control class. If we want to build a class that is not dependent on a specific Control, we will have to use the NativeWindow class. This class simply wraps a window handle, and so, it enables us to override the WndProc method which is implemented.
In both cases, we will override the WndProc method like: protected override void WndProc(ref Message aMessage)
{
if (aMessage.Msg==WM_AMESSAGE)
{
}
}
Detecting a volume insertion or removal
Now, let’s look at our example. We already know that we will need to trap the WM_DEVICECHANGED message, and to filter two specific events. We will build the example based on two classes.
The first one, _DeviceVolumeMonitor, will be private to the project, and will only serve to inherit the NativeWindow class, so that our main class won’t expose the public NativeWindow methods. This class will also serve the purpose of encapsulating all API constants and structures that we will need to make things work.
DeviceVolumeMonitor, will be our main class. It implements most of the logic needed to the user. Nevertheless, the main subject of this tutorial is in the other class.
The WM_CHANGEDEVICE message
This message is broadcasted whenever something relevant occurs in a device connected to Microsoft Windows. For this matter, there are two events which we will need to trap, the DBT_DEVICEARRIVAL and the DBT_DEVICEREMOVECOMPLETE.
Both events are handled the same way, the only difference being that DBT_DEVICEARRIVAL detects a volume being inserted, and the DBT_DEVICEREMOVECOMPLETE detects that a volume was removed. Now, we need to look at some of the Win32 API declarations to see how this works.
The WM_CHANGEDEVICE constant definition can be found in WinUser.h, all other information we will need, it can be found in DBT.h header file. Both files are part of the Microsoft Platform SDK.
When a WM_CHANGEDEVICE is broadcasted, the message structure will transport the event value in the WParam, and some additional data in a location pointed by LParam.
As I already described, we are only interested in trapping messages that have a WParam with the DBT_DEVICEARRIVAL or DBT_DEVICEREMOVALCOMPLETE values. The data pointed by LParam will have the same structure in both cases.
Once one of the events is detected, we must cast the pointer stored in LParam, to a _DEV_BROADCAST_HDR structure, and evaluate the dbch_devicetype field. We are only interested in proceeding if the field value is DBT_DEVTYP_VOLUME.
If this is the case, then we will need to cast LParam again, but this time to point at a _DEV_BROADCAST_VOLUME structure. This structure has two fields which we will have to evaluate. The first one is dbcv_flags. This field will tell us the nature of the volume that was mounted (or dismounted). It can be either a media volume like a CD (the flag will have a DBTF_MEDIA flag) or a network share (DBTF_NET). In this case we will only be interested in filtering the DBTF_MEDIA value. The other field is dbcv_unitmask, which is a bit vector that tells us which drive letters were mounted.
At this point, there are two facts which need to be clarified. As stated in the Platform SDK documentation, this message can tell us if more than one volume was mounted and these can be of multiple types. We will have to make an assumption that is whenever the dbcv_flags includes the DBTF_MEDIA value, we will assume that all the drives described by the dbcv_unitmask bit vector are of this type. I don’t think that it is likely that this notification will ever have a dbcv_flags that includes both types. Anyway, this would be solved if we would determine the specific type for all the drives described by dbcv_unitmask. On the other hand, as it was already implied, dbcv_unitmask can describe more than one drive at a time. I guess this is only likely if we have a jukebox device, but anyway this is a problem we will solve in the code.
The _DeviceVolumeMonitor class
This is a helper class which is internal to the project. Now as I told you before, this class is only useful to inherit the NativeWindow class and to encapsulate the API constants and structures we will use. The class constructor takes only a parameter which will be the instance of the main class that owns the _DeviceVolumeMonitor instance.
The API constants and structures were modified to become more readable and useful in the .NET context.
The WndProc is overridden according to the cascading conditions described for the WM_DEVICECHANGE message. If all conditions are fulfilled then the object uses as internal method of the main class to inform it that a valid event has occurred. This method is called TriggerEvents.
The DeviceVolumeMonitor
Our main class, contains some logic, which has no relation to windows messages itself.
Now, we know that this class creates an instance of the former class, and that whenever an event is detected, it is reported back to the main class through the TriggerEvents method. This is not a clean OOP approach, but there was no point in making things more complicated.
This class has two properties defined: Enabled and AsynchronousEvents. The Enabled property is self explanatory, is handles enabling or disabling the message trapping. This is done with the helper class. When we want messages to be trapped we assign the handle to the NativeWindow descendant, and the WndProc method will be invoked whenever there is a message either broadcasted or dispatched, the handle will be released when the property is unset. This approach is better than controlling the property value in the WndProc method (the property is still evaluated on the method as a sanity check anyway), because the class won’t overload the system with unnecessary checks.
AsynchronousEvents controls the way events are invoked. If it is set, then the event will be called asynchronously, and WndProc won’t stall waiting for the application to process whatever it has to. Now, the way this is done on the TriggerEvents method is not peaceful. Many of you will criticize the BeginInvoke without following the design pattern. It is an unsafe option, but still it works if it only evolves calling thread safe code. Either way, I assume that you know what you are doing if you set this property.
There is a platform invoke to an API function called QueryDosDevice. This is a function that enables the translation between the drive letter, or DOS device name, to the full device path. This is used by one of two methods which translate the bit vector that was mentioned before. MaskToLogicalPaths translates the bit vector to a comma delimited string with all the drive letters described by the bit vector, MaskToDevicePaths on the other hand returns a string which is a list of the full device paths.
Two events are defined, OnVolumeInserted and OnVolumeRemoved. Both of them take the issued bit vector as a parameter.
Finally, I have implemented the IDisposable interface, I followed the design pattern, so there is not much to say here.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 47 (Total in Forum: 47) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I am writing an app that waits for a CD to be inserted. Once the CD is inserted windows wants to open it (pops up the 'What would you like to do?' dialog box). Can I have my app consume the event, so windows won't do anything else?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Just wondering if you have solved this problem? I too have noticed that I get the messages for USB flash drives, CD etc but when I insert an SD card into my Dell Inspiron 6400 no message is received.
Which is what I need....
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I too am having a hard time finding out how to do this. Anyone get this solved yet? (really shouldn't be this difficult)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I just downloaded the source code and read the article. I'm working on a project for a friend of mine. I've read the messages about detecting a USB device, and that's what I needed.
But, using the UNmodified source code, when I insert a CD into my CD-ROM drive, it just says "Volume inserted in ", but when I remove the CD it says "Volume removed from E:", and E: is, of course, my CD-ROM drive. Why is this? I'm on Win2K Pro SP4.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just pointing out that you write WM_CHANGEDEVICE instead of WM_DEVICECHANGE at several points in your article. I guess I shouldn't have copied and pasted it
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
You mention that we can do this by overriding WndProc in a control or do it with NativeWindow like you did. Since my custom control is obviously not a top level window, it doesn't receive WM_DEVICECHANGE messages. How do I go about handling them then?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
yeah, i want to know that too. i have several controls in my app and one of them needs to test if usb devices get pluggin in. how can i reveice the messages from a control? do i have to call RegisterDeviceNotification() or is there a more elegant way?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
how can I detect a USB-Device within a windows-service in c#? I cannot override the WndProc - Method ( can I ? ) thanks a lot
|
| Sign In·View Thread·PermaLink | 3.00/5 (2 votes) |
|
|
|
 |
|
|
:  Sorry for maybe non relevant question. I have an unmanaged cpp dll and I need to create and set mine event in this dll but handle for this event should be in managed vb.net project. How do I catch created by myself unmanaged event in managed vb.net project ? thanks in advance
Geyur
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It depends on the way you have implemented your code. There are several solutions to this problem. The best way to expose a managed class to unmanaged code can be achieved using a CCW. This will, is short, enable your managed code to be accessed through a COM interface.You should look up this subject on MSDN.
Rui Reis
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Im trying to do exactly what was done here in managed code for a Windows CE project using C#. This example code doesnt build under a smart device application so I had to modify it slightly. I modified the Windows Form to derive from MessageWindow instead of NativeWindow hoping to still get notified when volumes are inserted/removed but I never get any notifications of any sort. Do you have any experience with this or any suggestions?
Rob
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Since I am a new programmer, I know almost nothing about Win32 API. Thanks for the artical "Trapping windows messages", it's pretty useful to me. In fact, I am writing something about detecting storage device attaching/detaching, and media insert/remove from the storage device. I've got two questions. 1) How can I trap all the WM_DEVICECHANGE messages, not just the default two (DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE). 2) How can I detect media insert/remove from the storage device, such as: insert/remove CF card from a USB card reader? * I've tried to read the MSDN online to get some ideas, but I still don't understand. If I can, may I ask for a complete sample? Thanks, Thanks, Thanks a lot!!!
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi!shintaerp,
I have got the same problem in my work as you did.I don't know whether you have worked out this problem.If you did,would you like to share your successful experience with me? Thanks a lot!
|
| Sign In·View Thread·PermaLink | 3.00/5 (1 vote) |
|
|
|
 |
|
|
well try somthing like this:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) if m.Msg = &H219 ' WM_DEVICECHANGE ' HERE YOU CAN LOOK AT THE M.WPARAM and filter all the events you liek End if MyBase.WndProc(m) End Sub
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm having issues with this exact thing. MY CF card reader (compacflash) mounts a drive letter, but I cant use FileSystemWatcher on it (anymore than I could on a cddrive that was empty). I've yet been unable to catch the system message for media insert....is there a place that enumerates the WParams so I can at least get an idea of what I'm looking for? Also..is there part of the Message that returns the drive letter affected by the WM_DEVICECHANGE ?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I dont know if you guys have come up with this but I wrote a deal to trap for messages..and this is what I came up with. And I'm sorry for sounding like SUCH an idiot but this is my first venture into ANY programming like this.
First off, and I've said this in other posts but I'll give you the whole rundown.
My card reader mounts a drive when it is connected even if there is no media in it. I've tried to get it to throw WM_DEVICECHANGE when I insert the media with no luck.
I finally wrote a little ditty that captures all Messages and returns the integer value.
The winner is: 49389;
When my app first starts, this Message is recieved, but with a WParam of 19. When I insert a card, I get the message again, but with WParam of 0.
What this message is? NO CLUE!
Its not any of this class:
public enum WindowsMessages {
WM_ACTIVATE = 0x6, WM_ACTIVATEAPP = 0x1C, WM_AFXFIRST = 0x360, WM_AFXLAST = 0x37F, WM_APP = 0x8000, WM_ASKCBFORMATNAME = 0x30C, WM_CANCELJOURNAL = 0x4B, WM_CANCELMODE = 0x1F, WM_CAPTURECHANGED = 0x215, WM_CHANGECBCHAIN = 0x30D, WM_CHAR = 0x102, WM_CHARTOITEM = 0x2F, WM_CHILDACTIVATE = 0x22, WM_CLEAR = 0x303, WM_CLOSE = 0x10, WM_COMMAND = 0x111, WM_COMPACTING = 0x41, WM_COMPAREITEM = 0x39, WM_CONTEXTMENU = 0x7B, WM_COPY = 0x301, WM_COPYDATA = 0x4A, WM_CREATE = 0x1, WM_CTLCOLORBTN = 0x135, WM_CTLCOLORDLG = 0x136, WM_CTLCOLOREDIT = 0x133, WM_CTLCOLORLISTBOX = 0x134, WM_CTLCOLORMSGBOX = 0x132, WM_CTLCOLORSCROLLBAR = 0x137, WM_CTLCOLORSTATIC = 0x138, WM_CUT = 0x300, WM_DEADCHAR = 0x103, WM_DELETEITEM = 0x2D, WM_DESTROY = 0x2, WM_DESTROYCLIPBOARD = 0x307, WM_DEVICECHANGE = 0x219, WM_DEVMODECHANGE = 0x1B, WM_DISPLAYCHANGE = 0x7E, WM_DRAWCLIPBOARD = 0x308, WM_DRAWITEM = 0x2B, WM_DROPFILES = 0x233, WM_ENABLE = 0xA, WM_ENDSESSION = 0x16, WM_ENTERIDLE = 0x121, WM_ENTERMENULOOP = 0x211, WM_ENTERSIZEMOVE = 0x231, WM_ERASEBKGND = 0x14, WM_EXITMENULOOP = 0x212, WM_EXITSIZEMOVE = 0x232, WM_FONTCHANGE = 0x1D, WM_GETDLGCODE = 0x87, WM_GETFONT = 0x31, WM_GETHOTKEY = 0x33, WM_GETICON = 0x7F, WM_GETMINMAXINFO = 0x24, WM_GETOBJECT = 0x3D, WM_GETTEXT = 0xD, WM_GETTEXTLENGTH = 0xE, WM_HANDHELDFIRST = 0x358, WM_HANDHELDLAST = 0x35F, WM_HELP = 0x53, WM_HOTKEY = 0x312, WM_HSCROLL = 0x114, WM_HSCROLLCLIPBOARD = 0x30E, WM_ICONERASEBKGND = 0x27, WM_IME_CHAR = 0x286, WM_IME_COMPOSITION = 0x10F, WM_IME_COMPOSITIONFULL = 0x284, WM_IME_CONTROL = 0x283, WM_IME_ENDCOMPOSITION = 0x10E, WM_IME_KEYDOWN = 0x290, WM_IME_KEYLAST = 0x10F, WM_IME_KEYUP = 0x291, WM_IME_NOTIFY = 0x282, WM_IME_REQUEST = 0x288, WM_IME_SELECT = 0x285, WM_IME_SETCONTEXT = 0x281, WM_IME_STARTCOMPOSITION = 0x10D, WM_INITDIALOG = 0x110, WM_INITMENU = 0x116, WM_INITMENUPOPUP = 0x117, WM_INPUTLANGCHANGE = 0x51, WM_INPUTLANGCHANGEREQUEST = 0x50, WM_KEYDOWN = 0x100, WM_KEYFIRST = 0x100, WM_KEYLAST = 0x108, WM_KEYUP = 0x101, WM_KILLFOCUS = 0x8, WM_LBUTTONDBLCLK = 0x203, WM_LBUTTONDOWN = 0x201, WM_LBUTTONUP = 0x202, WM_MBUTTONDBLCLK = 0x209, WM_MBUTTONDOWN = 0x207, WM_MBUTTONUP = 0x208, WM_MDIACTIVATE = 0x222, WM_MDICASCADE = 0x227, WM_MDICREATE = 0x220, WM_MDIDESTROY = 0x221, WM_MDIGETACTIVE = 0x229, WM_MDIICONARRANGE = 0x228, WM_MDIMAXIMIZE = 0x225, WM_MDINEXT = 0x224, WM_MDIREFRESHMENU = 0x234, WM_MDIRESTORE = 0x223, WM_MDISETMENU = 0x230, WM_MDITILE = 0x226, WM_MEASUREITEM = 0x2C, WM_MENUCHAR = 0x120, WM_MENUCOMMAND = 0x126, WM_MENUDRAG = 0x123, WM_MENUGETOBJECT = 0x124, WM_MENURBUTTONUP = 0x122, WM_MENUSELECT = 0x11F, WM_MOUSEACTIVATE = 0x21, WM_MOUSEFIRST = 0x200, WM_MOUSEHOVER = 0x2A1, WM_MOUSELAST = 0x20A, WM_MOUSELEAVE = 0x2A3, WM_MOUSEMOVE = 0x200, WM_MOUSEWHEEL = 0x20A, WM_MOVE = 0x3, WM_MOVING = 0x216, WM_NCACTIVATE = 0x86, WM_NCCALCSIZE = 0x83, WM_NCCREATE = 0x81, WM_NCDESTROY = 0x82, WM_NCHITTEST = 0x84, WM_NCLBUTTONDBLCLK = 0xA3, WM_NCLBUTTONDOWN = 0xA1, WM_NCLBUTTONUP = 0xA2, WM_NCMBUTTONDBLCLK = 0xA9, WM_NCMBUTTONDOWN = 0xA7, WM_NCMBUTTONUP = 0xA8, WM_NCMOUSEHOVER = 0x2A0, WM_NCMOUSEMOVE = 0xA0, WM_NCPAINT = 0x85, WM_NCRBUTTONDBLCLK = 0xA6, WM_NCRBUTTONDOWN = 0xA4, WM_NCRBUTTONUP = 0xA5, WM_NEXTDLGCTL = 0x28, WM_NEXTMENU = 0x213, WM_NOTIFY = 0x4E, WM_NOTIFYFORMAT = 0x55, WM_NULL = 0x0, WM_PAINT = 0xF, WM_PAINTCLIPBOARD = 0x309, WM_PAINTICON = 0x26, WM_PALETTECHANGED = 0x311, WM_PALETTEISCHANGING = 0x310, WM_PARENTNOTIFY = 0x210, WM_PASTE = 0x302, WM_PENWINFIRST = 0x380, WM_PENWINLAST = 0x38F, WM_POWER = 0x48, WM_PRINT = 0x317, WM_PRINTCLIENT = 0x318, WM_QUERYDRAGICON = 0x37, WM_QUERYENDSESSION = 0x11, WM_QUERYNEWPALETTE = 0x30F, WM_QUERYOPEN = 0x13, WM_QUEUESYNC = 0x23, WM_QUIT = 0x12, WM_RBUTTONDBLCLK = 0x206, WM_RBUTTONDOWN = 0x204, WM_RBUTTONUP = 0x205, WM_RENDERALLFORMATS = 0x306, WM_RENDERFORMAT = 0x305, WM_SETCURSOR = 0x20, WM_SETFOCUS = 0x7, WM_SETFONT = 0x30, WM_SETHOTKEY = 0x32, WM_SETICON = 0x80, WM_SETREDRAW = 0xB, WM_SETTEXT = 0xC, WM_SETTINGCHANGE = 0x1A, WM_SHOWWINDOW = 0x18, WM_SIZE = 0x5, WM_SIZECLIPBOARD = 0x30B, WM_SIZING = 0x214, WM_SPOOLERSTATUS = 0x2A, WM_STYLECHANGED = 0x7D, WM_STYLECHANGING = 0x7C, WM_SYNCPAINT = 0x88, WM_SYSCHAR = 0x106, WM_SYSCOLORCHANGE = 0x15, WM_SYSCOMMAND = 0x112, WM_SYSDEADCHAR = 0x107, WM_SYSKEYDOWN = 0x104, WM_SYSKEYUP = 0x105, WM_TCARD = 0x52, WM_TIMECHANGE = 0x1E, WM_TIMER = 0x113, WM_UNDO = 0x304, WM_UNINITMENUPOPUP = 0x125, WM_USER = 0x400, WM_USERCHANGED = 0x54, WM_VKEYTOITEM = 0x2E, WM_VSCROLL = 0x115, WM_VSCROLLCLIPBOARD = 0x30A, WM_WINDOWPOSCHANGED = 0x47, WM_WINDOWPOSCHANGING = 0x46, WM_WININICHANGE = 0x1A
}
I'd love to know the constant that belongs to that integer, any of you guru's know how to get to it?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Take a look into te dbt.h header file, i bet its an DBT_* message! good resource: http://msdn2.microsoft.com/en-us/library/aa363480.aspx
modified on Friday, December 28, 2007 4:42:20 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,
I want to write a little code fragment where i get noticed when a new window or a new tab pane is opened - independent of the application.
Can anybody please explain me how i can implement this with the IMessageFilter??
thanks in advance.
regards
pat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't get it to work with USB Mass Storage Devices (my digital camera), CD-Rom works fine. I'm using Windows 2000. Does anyone know why that is? Shouldn't you receive an event for USB devices also?
Thx.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I just stumbled across this article and have been implementing something similar myself. My solution was targetted at USB mass storage devices, so I think I can help you here.
The reason why Riul's solution doesn't work for anything except CD/DVD drives is the line
if(lBroadcastHeader.Type==DeviceType.Volume) in _DeviceVolumeMonitor.WndProc(), file DeviceVolumeMonitor.cs line 108.
Remove/comment the if and always trigger the events to also get notifications for USB or Network devices.
One more point: when inserting a new media in my CD I get an additional bit set in dbcv_unitmask, additional to the bit indicating the drive letter. But only during insert and not when removing the CD. Anyone knows why?
|
| Sign In·View Thread·PermaLink | 2.67/5 (3 votes) |
|
|
|
 |
|
|
Hello,
The code works for me on W2K and not on XP Home.
It notifys me of CD Insert/Remove and USB connect/disconnect.
How can I detect media insert/remove from the USB card reader?
I hope somebody can help.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,
The code works for me on W2K and not on XP Home.
It notifys me of CD Insert/Remove and USB connect/disconnect.
How can I detect media insert/remove from the USB card reader?
I hope somebody can help.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|