Jump to content

USB: Determine the right drive or handle


Joline
 Share

Recommended Posts

Hi everybody,

having solved my problem with getting informed when a usb device is attempted to remove (RegisterDeviceNotification) I fall into the next problem:

How can I determine the right drive or handle using the lParam in case of wParam = DBT_DEVICEQUERYREMOVE?

Here are parts of my code:

Func MY_WM_DEVICECHANGE(Const $hWnd, Const $msg, Const $wParam, Const $lParam)
;...    
    
    Local Const $DeviceType = GetDeviceType($lParam)

    Switch $wParam
        Case $DBT_DEVICEARRIVAL
;...
            If $DeviceType = $DBT_DEVTYP_VOLUME Then    ; <= VOLUME!!!
                Local Const $UnitMask = GetUnitMask($lParam)
                Local Const $driveLetter = GetDriveLetterFromUnitMask($unitMask) & ":\"
                ;this works, I get the right drive
;...
        Case $DBT_DEVICEQUERYREMOVE
;...
            If $DeviceType = $DBT_DEVTYP_HANDLE Then    ; <= HANDLE!!!
                Local Const $UnitMask = GetUnitMask($lParam)
                Local Const $driveLetter = GetDriveLetterFromUnitMask($unitMask) & ":\"
                ;this works NOT, I get the WRONG drive
                ;maybe I have to find the filehandle?
;...
 
        Case $DBT_DEVICEREMOVECOMPLETE
;...
            If $DeviceType = $DBT_DEVTYP_VOLUME Then    ; <= VOLUME!!!
                Local Const $UnitMask = GetUnitMask($lParam)
                Local Const $driveLetter = GetDriveLetterFromUnitMask($unitMask) & ":\"
                ;this works, I get the right drive
;...
 
    EndSwitch
EndFunc ;==>MY_WM_DEVICECHANGE


Func GetUnitMask($lParam)
    ; Create a struct from $lParam which contains a pointer to a Windows-created struct.
    Local Const $tagDEV_BROADCAST_VOLUME = "dword dbcv_size; dword dbcv_devicetype; dword dbcv_reserved; dword dbcv_unitmask; word dbcv_flags"

    Local Const $DEV_BROADCAST_VOLUME = DllStructCreate($tagDEV_BROADCAST_VOLUME, $lParam)

    Local Const $UnitMask = DllStructGetData($DEV_BROADCAST_VOLUME, "dbcv_unitmask")

    Return $UnitMask
EndFunc ;==>GetUnitMask
 
Func GetDeviceType($lParam)
    ; Create a struct from $lParam which contains a pointer to a Windows-created struct.
    Local Const $tagDEV_BROADCAST_VOLUME = "dword dbcv_size; dword dbcv_devicetype; dword dbcv_reserved; dword dbcv_unitmask; word dbcv_flags"

    Local Const $DEV_BROADCAST_VOLUME = DllStructCreate($tagDEV_BROADCAST_VOLUME, $lParam)

    Local Const $DeviceType = DllStructGetData($DEV_BROADCAST_VOLUME, "dbcv_devicetype")

    Return $DeviceType
EndFunc ;==>GetDeviceType

This works if I use it while wParam = DBT_DEVICEARRIVAL but NOT while wParam = DBT_DEVICEQUERYREMOVE. Here I get the wrong drive. Maybe I have to determine the file handle (from the opened file) because of DeviceType = Handle (and not volume) but how? Does anybody have an idea?

Edited by Joline
Link to comment
Share on other sites

My first guess would be that when you call GetUnitmask() from within the $DBT_DEVICEQUERYREMOVE case then you're opening a different structure than when the case is say $DBT_DEVICEARRIVAL.

To make my self clearer, $lParam stores a pointer to a Windows-created struct. Whenever Windows announces a $DBT_DEVICEARRIVAL event then $lParam will store a pointer to a DEV_BROADCAST_VOLUME struct. Whenever Windows announces a $DBT_DEVICEQUERYREMOVE event then $lParam will store a pointer to a DEV_BROADCAST_HANDLE struct. And so when you call GetUnitMask on a $DBT_DEVICEQUERYREMOVE event then you're trying to open a DEV_BROADCAST_VOLUME struct instead of a DEV_BROADCAST_HANDLE struct.

No, none of this will work.

Take these functions. Modify them to suit your needs if you need to. Come back and tell us what happened.

OK, this code won't solve the problem but I think I'm still right about the cause of the problem though.

Func DEV_BROADCAST_HANDLE($Handle, $CheckError = False)
    Local Const $tagDEV_BROADCAST_HANDLE = "dword dbch_size; dword dbch_devicetype; dword dbch_reserved; handle dbch_handle; handle dbch_hdevnotify; char dbch_eventguid[38]; long dbch_nameoffset; byte dbch_data[1]"

    Local Const $DEV_BROADCAST_HANDLE = DllStructCreate($tagDEV_BROADCAST_HANDLE)
    $LastError = _WinAPI_GetLastErrorMessage()

    If IsDllStruct($DEV_BROADCAST_HANDLE) Then
        Local Const $Size = DllStructGetSize($DEV_BROADCAST_HANDLE)

        Local Const $EventGUID = _WinAPI_GUIDFromString("{25dbce51-6c8f-4a72-8a6d-b54c2b4fc835}")

        Local Const $DBT_DEVTYP_HANDLE = 0x00000006

        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_size"     , $Size)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_devicetype", $DBT_DEVTYP_HANDLE)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_reserved" , 0)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_handle"   , $Handle)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_hdevnotify", 0)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_eventguid" , $EventGUID)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_nameoffset", 0)
        DllStructSetData($DEV_BROADCAST_HANDLE, "dbch_data"     , 0)

        Return $DEV_BROADCAST_HANDLE
    Else
        If $CheckError Then ShowError("DEV_BROADCAST_HANDLE")
        Return SetError(1, 0, 1)
    EndIf
EndFunc ;==>DEV_BROADCAST_HANDLE

; -------------------------------------------------------------------------------------------

Func DEV_BROADCAST_HDR($lParam, $CheckError = False)
    Local Const $tagDEV_BROADCAST_HDR = "dword dbch_size; dword dbch_devicetype; dword dbch_reserved"

    Local Const $DEV_BROADCAST_HDR = DllStructCreate($tagDEV_BROADCAST_HDR, $lParam)

    If IsDllStruct($DEV_BROADCAST_HDR) Then
        Return $DEV_BROADCAST_HDR
    Else
        If $CheckError Then Write("DEV_BROADCAST_HDR: Fail")
        Return SetError(1, 0, 1)
    EndIf
EndFunc ;==>DEV_BROADCAST_HDR

; -------------------------------------------------------------------------------------------

Func DEV_BROADCAST_VOLUME($lParam, $CheckError = False)
    ; Create a struct from $lParam which contains a pointer to a Windows-created struct.
    Local Const $tagDEV_BROADCAST_VOLUME = "dword dbcv_size; dword dbcv_devicetype; dword dbcv_reserved; dword dbcv_unitmask; word dbcv_flags"

    Local Const $DEV_BROADCAST_VOLUME = DllStructCreate($tagDEV_BROADCAST_VOLUME, $lParam)

    If IsDllStruct($DEV_BROADCAST_VOLUME) Then
        Return $DEV_BROADCAST_VOLUME
    Else
        If $CheckError Then Write("DEV_BROADCAST_VOLUME: Fail")
        Return SetError(1, 0, 1)
    EndIf
EndFunc ;==>DEV_BROADCAST_VOLUME

; -------------------------------------------------------------------------------------------

Func GetUnitMask($lParam, $CheckError = False)
    Local Const $DEV_BROADCAST_HDR = DEV_BROADCAST_HDR($lParam)

    Local Const $DeviceType = DllStructGetData($DEV_BROADCAST_HDR, "dbch_devicetype")

    Local Const $DBT_DEVTYP_VOLUME = 0x00000002
    Local Const $DBT_DEVTYP_HANDLE = 0x00000006

    Switch $DeviceType
        Case $DBT_DEVTYP_VOLUME
            Local Const $DEV_BROADCAST_VOLUME = DEV_BROADCAST_VOLUME($lParam)

            If IsDllStruct($DEV_BROADCAST_VOLUME) Then
                Return DllStructGetData($DEV_BROADCAST_VOLUME, "dbcv_unitmask")
            Else
                If $CheckError Then Write("GetUnitMask: Fail")
                Return SetError(1, 0, 1)
            EndIf

        Case $DBT_DEVTYP_HANDLE
            Local Const $DEV_BROADCAST_HANDLE = DEV_BROADCAST_HANDLE($lParam)

            If IsDllStruct($DEV_BROADCAST_HANDLE) Then
                Return DllStructGetData($DEV_BROADCAST_HANDLE, "dbcv_unitmask")
            Else
                If $CheckError Then Write("GetUnitMask: Fail")
                Return SetError(2, 0, 1)
            EndIf

    EndSwitch
EndFunc ;==>GetUnitMask
Edited by jaberwocky6669
Link to comment
Share on other sites

To make my self clearer, $lParam stores a pointer to a Windows-created struct. Whenever Windows announces a $DBT_DEVICEARRIVAL event then $lParam will store a pointer to a DEV_BROADCAST_VOLUME struct. Whenever Windows announces a $DBT_DEVICEQUERYREMOVE event then $lParam will store a pointer to a DEV_BROADCAST_HANDLE struct. And so when you call GetUnitMask on a $DBT_DEVICEQUERYREMOVE event then you're trying to open a DEV_BROADCAST_VOLUME struct instead of a DEV_BROADCAST_HANDLE struct.

This is also my thought. But there must be a relationship between both. How is otherwise possible that an application can release the right file(handle) if DEVICEQUERYREMOVE occurs?

So maybe in case of DEVICEARRIVAL there is also a pointer to a handle which can be stored and compared with the pointer to the DEV_BROADCAST_HANDLE struct when DEVICEQUERYREMOVE occurs.

Link to comment
Share on other sites

Well why not declare $DriveLetter as a global and then get the driveletter upon DBT_DEVICEARRIVAL?

Global $DriveLetter

Func MY_WM_DEVICECHANGE(Const $hWnd, Const $msg, Const $wParam, Const $lParam)
    ;...

    Local Const $DeviceType = GetDeviceType($lParam)

    Switch $wParam
        Case $DBT_DEVICEARRIVAL
            ;...
            If $DeviceType = $DBT_DEVTYP_VOLUME Then    ; <= VOLUME!!!
                Local Const $UnitMask = GetUnitMask($lParam)
                $DriveLetter = GetDriveLetterFromUnitMask($UnitMask) & ":\"
                ;this works, I get the right drive
            EndIf
            ;...
        Case $DBT_DEVICEQUERYREMOVE
            ;...
            If $DeviceType = $DBT_DEVTYP_HANDLE Then    ; <= HANDLE!!!
            EndIf
            ;...

        Case $DBT_DEVICEREMOVECOMPLETE
            ;...
            If $DeviceType = $DBT_DEVTYP_VOLUME Then    ; <= VOLUME!!!
            EndIf
            ;...
    EndSwitch
EndFunc ;==>MY_WM_DEVICECHANGE

Func GetUnitMask($lParam)
    ; Create a struct from $lParam which contains a pointer to a Windows-created struct.
    Local Const $tagDEV_BROADCAST_VOLUME = "dword dbcv_size; dword dbcv_devicetype; dword dbcv_reserved; dword dbcv_unitmask; word dbcv_flags"

    Local Const $DEV_BROADCAST_VOLUME = DllStructCreate($tagDEV_BROADCAST_VOLUME, $lParam)

    Local Const $UnitMask = DllStructGetData($DEV_BROADCAST_VOLUME, "dbcv_unitmask")

    Return $UnitMask
EndFunc ;==>GetUnitMask

Func GetDeviceType($lParam)
    ; Create a struct from $lParam which contains a pointer to a Windows-created struct.
    Local Const $tagDEV_BROADCAST_VOLUME = "dword dbcv_size; dword dbcv_devicetype; dword dbcv_reserved; dword dbcv_unitmask; word dbcv_flags"

    Local Const $DEV_BROADCAST_VOLUME = DllStructCreate($tagDEV_BROADCAST_VOLUME, $lParam)

    Local Const $DeviceType = DllStructGetData($DEV_BROADCAST_VOLUME, "dbcv_devicetype")

    Return $DeviceType
EndFunc ;==>GetDeviceType
Edited by jaberwocky6669
Link to comment
Share on other sites

Well why not declare $DriveLetter as a global and then get the driveletter upon DBT_DEVICEARRIVAL?

I have this already defined as global. ;) But the problem is how to detect WHICH drive is meant on DEVICEQUERYREMOVE...

E.g.: There are 2 devices connected, f: and g:. Now one of them should be removed. How can I determine in DEVICEQUERYREMOVE which of both is it? Do you know if this is passed in any way/form?

Link to comment
Share on other sites

My honest opinion is that this script has a long way to go because I think there is a fundamental error. I think that in order to register a device you have to first call RegisterDeviceNotification with a DEV_BROADCAST_DEVICEINTERFACE struct and then call it again with the DEV_BROADCAST_HANDLE struct but this time pass the hdevnotify parameter the result of calling RegisterDeviceNotification the first time (I need time to catch my breath)! This is complicated stuff for a newbie like me. Would any Windows guru out there in AutoIt land like to chime in on this? It would be much appreciated.

Edited by jaberwocky6669
Link to comment
Share on other sites

Try my latest version located in my signature. You'll also need RDN Constants.au3. It isn't a full working solution, proof of concept more like it. Edited by jaberwocky6669
Link to comment
Share on other sites

OK, Joline, my latest revision (located in my signature) works well for me. You'll need to grab the RDN Constants.au3 also located in my signature.

But this works for me. In fact, I think this script is ready to go PrimeTime! Ok, well, maybe that's a stretch, but, it's very useable.

Edited by jaberwocky6669
Link to comment
Share on other sites

Hi, jaberwocky,

did you test this also with more than one usb device?

P.S. Even I see that you use "switch-case" constructs extensively. Better you should use "if-then-else" if there are a single or 2 choices because of performance reasons. ;)

Edited by Joline
Link to comment
Share on other sites

I did test it with two devices and it worked fine. Did you run it yet? If so, did it run correctly?

Meh, switch statements might be a wee bit slower but they are easier for me to read and understand. Besides, this isn't performance critical code. If you need to focus on performance then change them to 'if-then-else' statements if you wish. Furthurmore, if you need to focus on performance then fire up Visual C++.

But I'm excited to know if it worked for you with more than one device. Also, if it didn't work what were your error messages? I need error messages for debugging.

Edited by jaberwocky6669
Link to comment
Share on other sites

Meh, switch statements might be a wee bit slower but they are easier for me to read and understand. Besides, this isn't performance critical code. If you need to focus on performance then change them to 'if-then-else' statements if you wish. Furthurmore, if you need to focus on performance then fire up Visual C++.

That is not a criticism but just an advice. I'm in this business since a long time... To realize that in C++ / C# / what ever is not the problem. The initial idea was to realize this _quickly_ in a scripting language because I didn't want to start the compiler for such a trivial thing. :) I did not suspect such problems. And I had no time to solve it. You did a good job. So see this not as a bad thing but a well meant advice. ;)

But I'm excited to know if it worked for you with more than one device. Also, if it didn't work what were your error messages? I need error messages for debugging.

There is a strange behaviour if you plug in 2 devices after each other and then try to remove. Sometimes it works, sometimes not. The good news is that the filehandle from DEVICEQUERYREMOVE is one of those from DEVICEARRIVAL. I've just inserted a write("$FileHandle: " & $FileHandle) to check this. So at the end the filehandle should be saved in an array and tested at DEVICEQUERYREMOVE to find out the right one.

I've such array already in my code but the wrong GetFileHandle function. ;) So at the weekend I'll insert your code snippet and hopefully this will work. :shocked: I'll report.

Link to comment
Share on other sites

  • Moderators

Joline,

I see that you use "switch-case" constructs extensively. Better you should use "if-then-else" if there are a single or 2 choices because of performance reasons

You might be interested to know that on many occasions Switch...Case structures have been shown to be faster in AutoIt than multiple If...Then...Else structures. ;)

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

That is not a criticism but just an advice. So see this not as a bad thing but a well meant advice. ;)

So at the weekend I'll insert your code snippet and hopefully this will work. :) I'll report.

Ah, ok, sorry and thank you.

I can't wait to see what happens.

It's unusual that it works for me more than 90% of the time. I will go test it out on another computer right now.

Edit: Ok wow, I just tried it on another Windows XP x86 machine and it worked intermittenly at best. It works fine on my Windows S7ven x64 and Windows XP x86 virtual machine. Well, I knew this wasn't exactly production code so back to the drawing board.

No actually, it no longer works on my virtual machine. So, somehting to do with windows XP. It might be the way that I open a handle to the devices.

Oh crap, you know what screw this. I've come to the end of me again. I've had about all I can stand of this code. Good luck. It's been fun baby. SIGH, it's just that it works so g**D**M beautifully on Windows S7ven. It's beyond me what could be screwing it up on my Windows XP machines!

Switch...Case structures have been shown to be faster in AutoIt than multiple If...Then...Else structures. ;)

This is interesting, could you tell me more? I did a quick google search that turned up naught concerning speed comparisons.

Edited by jaberwocky6669
Link to comment
Share on other sites

You might be interested to know that on many occasions Switch...Case structures have been shown to be faster in AutoIt than multiple If...Then...Else structures. ;)

Oh, interesting news. I've no idea what happen internally in AutoIt but I know what every (I think so) compiler will do with switch-case constructs. This will be translated into (assembly-)code with many jump labels. Jumping inside code... Uhhh! If...Then...Else constructs have only one jump label. Seems to be better, isn't it.

Sorry. Maybe my (old-school-)knowledge is based on availability of limited resources. But in times with inexhaustible resources nobody cares with memory, cpu etc. ... :)

Stop the religious war here! It was just an advise not a must. ;)

Link to comment
Share on other sites

It's unusual that it works for me more than 90% of the time.

I've extended your lines:

Local $FileHandle = OpenFile($VolumeAccessPath, True)

and

$FileHandle = GetFileHandle($lParam, True)

into:

Local $FileHandle = OpenFile($VolumeAccessPath, True)
Write("$FileHandle: " & $FileHandle)

and

$FileHandle = GetFileHandle($lParam, True)
Write("$FileHandle: " & $FileHandle)

Plug in 2 devices and try to remove them in different sequence. Just compare the results of $FileHandle...

I think it hasn't worked all the times but maybe I couldn't see straight yesterday in the evening. To much wine... ;)

Link to comment
Share on other sites

Plug in 2 devices and try to remove them in different sequence. Just compare the results of $FileHandle...

I think it hasn't worked all the times but maybe I couldn't see straight yesterday in the evening. To much wine... ;)

Works fine for me. here is my output embellished with some extra comments...

DEVICEARRIVAL: G
$FileHandle: 0x0000017C
RegisterDeviceInterfaceToHwnd: SUCCESS!

DEVICEARRIVAL: F
$FileHandle: 0x00000198
RegisterDeviceInterfaceToHwnd: SUCCESS!

DEVICEQUERYREMOVE
$FileHandle: 0x00000198
DEVICEREMOVECOMPLETE
UnregisterDeviceNotification: The operation completed successfully.

DEVICEQUERYREMOVE
$FileHandle: 0x0000017C
DEVICEREMOVECOMPLETE
UnregisterDeviceNotification: The operation completed successfully.

Here are the results of running it on my virtual machine:

DEVICEARRIVAL: E
$FileHandle: 0x0000071C
RegisterDeviceInterfaceToHwnd: SUCCESS!

DEVICEARRIVAL: F
$FileHandle: 0x0000000C
RegisterDeviceInterfaceToHwnd: SUCCESS!

DEVICEQUERYREMOVE ; this is with the check box checked; it still detached anyway!

DEVICEQUERYREMOVE ; this is with the check box checked; it still detached anyway!

So it has something to do with the way I open file handles on WindowsXP? Or it might be the way I handle the removal events in the WindowsProc function...

Anyways, I'm sticking to my guns. You got this far. I've learned as much as my poor brain can handle. If you get this working then come back and we'll celebrate

Ok, I went back to using WinAPI_CreateFileEx and I changed some of the openfile parameters. It seems to work right on my WindowsXP VM? Please try this when you get a chance.

Edited by jaberwocky6669
Link to comment
Share on other sites

Oh wow, I forgot to update my code! OK, wow. Ok, should be good to go now.

I've inserted and removed the devices in every order I could conceive of.

Edit** I just now tried this on my ancient Dell Inspiron 8100 laptop running x86 Windows XP and it worked superb. And I removed first the 2nd inserted device, worked fine.

Edited by jaberwocky6669
Link to comment
Share on other sites

Sweet! Yup, it was the file handles that were the problem. Well, that was a learning experience for me. May I ask what version of Windows you're running?

Edited by jaberwocky6669
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...