El sistema envía el mensaje WM_DEVICECHANGE a la ventana cuando se encuentra o se elimina un dispositivo. Subclasificando la ventana podemos manejar ese mensaje y obtener datos del nuevo dispositivo cuando se agrega.
En un formulario con un Label de nombre lblInfo peguen el siguiente código:
Option Explicit
Private Sub Form_Load()
lblInfo = vbCrLf & "No hay dispositivos nuevos" & vbCrLf
'
' Establece nuestra función como el nuevo procedimiento de ventana.
'
lPrevWndProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf DevWindowProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
lPrevWndProc = SetWindowLong(hWnd, GWL_WNDPROC, lPrevWndProc)
End Sub
Ahora, ¿cómo funciona el mensaje WM_DEVICECHANGE?.
Hay que tener en cuenta dos comportamientos de este mensaje:
1. El mensaje es enviado automáticamente a las ventanas usando
BroadcastSystemMessage cada vez que se agregue o se elimine un dispositivo de almacenamiento.
2. Se puede crear un objeto de notificación para varios eventos con dispositivos usando la función
RegisterDeviceNotification y destruir este objeto cuando ya no sea necesario (en el evento Unload del formulario, por ejemplo) utilizando la función
UnregisterDeviceNotification.
Declare Function RegisterDeviceNotification Lib "user32" Alias "RegisterDeviceNotificationA" (ByVal hRecipient As Long, ByVal NotificationFilter As Long, ByVal Flags As Long) As Long
Declare Function UnregisterDeviceNotification Lib "user32" (ByVal hDevNotify As Long) As Long
Son bastante sencillas de usar, pero para este caso no nos van a servir. Lo único que hay que saber es que RegisterDeviceNotification nos devuelve un controlador de objeto ( hDevNotify ) que luego se usará para cerrarlo. También dejo las constantes que usan estas funciones:
'
' Tipo de notificaciones.
'
Public Const DEVICE_NOTIFY_WINDOW_HANDLE = &H0&
Public Const DEVICE_NOTIFY_SERVICE_HANDLE = &H1&
Public Const DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = &H4&
'
' Tipo de dispositivos para notificaciones.
'
Public Const DBT_DEVTYP_DEVICEINTERFACE = &H5&
Public Const DBT_DEVTYP_HANDLE = &H6&
Public Const DBT_DEVTYP_OEM = &H0&
Public Const DBT_DEVTYP_PORT = &H3&
Public Const DBT_DEVTYP_VOLUME = &H2&
El resto de la explicación es común para los dos métodos expuse anteriormente.
Ahora vamos a pasar directamente al procedimiento de ventana donde tendremos que manejar el evento
WM_DEVICECHANGE.
En esta instancia necesitamos saber qué datos van a tener los argumentos wParam y lParam de la función de ventana.
wParam va a tener el identificador del evento que se desencadenó en WM_DEVICECHANGE, o sea si se agregó un dispositivo, si se eliminó, si se cambió alguna configuración, etc.
En nuestro caso, como no usamos
RegisterDeviceNotification, los eventos que se van a desencadenar son sólo DBT_DEVICEARRIVAL y DBT_DEVICEREMOVECOMPLETE, a continuación pongo las constantes que identifican a todos los eventos:
'
' Eventos que pueden ocurrir (se pasan en wParam).
'
Public Const DBT_CONFIGCHANGECANCELED = &H18&
Public Const DBT_CONFIGCHANGED = &H19&
Public Const DBT_CUSTOMEVENT = &H8006&
Public Const DBT_DEVICEARRIVAL = &H8000&
Public Const DBT_DEVICEQUERYREMOVE = &H8001&
Public Const DBT_DEVICEQUERYREMOVEFAILED = &H8002&
Public Const DBT_DEVICEREMOVECOMPLETE = &H8004&
Public Const DBT_DEVICEREMOVEPENDING = &H8003&
Public Const DBT_DEVICETYPESPECIFIC = &H8005&
Public Const DBT_DEVNODES_CHANGED = &H7&
Public Const DBT_QUERYCHANGECONFIG = &H17&
Public Const DBT_USERDEFINED = &HFFFF&
lParam va a contener la dirección de una estructura que va a dar datos sobre el evento. La estructura se llama
DEV_BROADCAST_HDR y es la siguiente:
Type DEV_BROADCAST_HDR
dbch_size As Long
dbch_devicetype As Long
dbch_reserved As Long
End Type
Lo único que nos interesa de esta estructura es el registro
dbch_devicetype que va a indicarnos qué tipo de dispositivo desencadenó el evento. Los posibles valores para este registro son los mismos que se usan en la llamada a
RegisterDeviceNotification:
'
' Tipo de dispositivos para notificaciones.
'
Public Const DBT_DEVTYP_DEVICEINTERFACE = &H5&
Public Const DBT_DEVTYP_HANDLE = &H6&
Public Const DBT_DEVTYP_OEM = &H0&
Public Const DBT_DEVTYP_PORT = &H3&
Public Const DBT_DEVTYP_VOLUME = &H2&
En el caso que sea un dispositivo de almacenamiento USB el registro tendrá el valor de DBT_DEVTYP_VOLUME.
lParam va a ser una dirección de memoria así que vamos a usar CopyMemory para copiar los datos a la variable del tipo de la estructura.
Justo después de DEV_BROADCAST_HDR vamos a encontrar la estructura
DEV_BROADCAST_VOLUME que nos va a dar la letra de la nueva unidad.
Type DEV_BROADCAST_VOLUME
dbcv_size As Long
dbcv_devicetype As Long
dbcv_reserved As Long
dbcv_unitmask As Long
dbcv_flags As Integer
End Type
Lo que nos va a interesar de esta estructura es el registro
dbcv_unitmask. Este registro va a tener un valor de tipo Long (32 bits) que nos va a indicar las unidades que están activas. Si un bit está en 1 significa que esa unidad está usada.
La siguiente función devuelve cuál fue la última unidad que se creó:
Function GetDriveFromMask(ByVal unitmask As Long) As String
Dim i%, iDriveType%
unitmask = LoWord(unitmask)
For i = 3 To 25
If ((unitmask And (2 ^ i)) Or (Abs(unitmask) And (2 ^ i))) Then
iDriveType = GetDriveType(Chr$(65 + i) & ":")
If (iDriveType = 2) Then
Exit For
End If
End If
Next
GetDriveFromMask = Chr$(i + 65)
End Function
Function LoWord(Dword As Long) As Integer
If Dword And &H8000& Then
LoWord = &H8000 Or (Dword And &H7FFF&)
Else
LoWord = Dword And &HFFFF&
End If
End Function
Y por último la función de ventana con todo lo que expliqué anteriormente.
Option Explicit
Public Const GWL_WNDPROC = (-4)
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function GetDriveType Lib "kernel32" Alias "GetDriveTypeA" (ByVal nDrive As String) As Long
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Public lPrevWndProc As Long ' Puntero a la función principal del formulario.
Function DevWindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
'
' Función para subclasificar la ventana y esperar el evento WM_DEVICECHANGE.
'
Dim lpDevHdr As DEV_BROADCAST_HDR
Dim lpDevVolume As DEV_BROADCAST_VOLUME
If (uMsg = WM_DEVICECHANGE) Then
If (wParam = DBT_DEVICEARRIVAL) Then
'
' Se insertó un nuevo dispositivo USB.
'
' Lee la estructura de la memoria.
'
Call CopyMemory(lpDevHdr, ByVal lParam, Len(lpDevHdr))
If (lpDevHdr.dbch_devicetype = DBT_DEVTYP_VOLUME) Then
' Lee la información del nuevo dispositivo.
'
Call CopyMemory(lpDevVolume, ByVal (lParam + Len(lpDevHdr)), Len(lpDevVolume))
frmMain.lblInfo = "Se encontró un nuevo volumen: " & GetDriveFromMask(lpDevVolume.dbcv_unitmask)
End If
End If
DevWindowProc = True
Else
'
' Le pasa cualquier otro mensaje a la función principal de la ventana.
'
DevWindowProc = CallWindowProc(lPrevWndProc, frmMain.hWnd, uMsg, wParam, lParam)
End If
End Function
Bueh, espero que sirva.