RegisterDeviceNotification() returns NULL
I have an app that creates FileSystemWatchers for root directories. In order for externally attached storage (USB sticks) to be removed easily, I have to release the appropriate FileSystemWatcher when Windows requests to remove a drive. If the FileSystemWatcher were left in place, Windows would display a message that the device is still in use and cannot be removed. In order for me to receive the request to remove the device in my app, I have to first register my window for the request with 'RegisterDeviceNotification()' and later release the FileSystemWatcher when I receive the WM_DEVICECHANGE message with DBT_DEVICEQUERYREMOVE.
C++/CLI:
NativeHelpers::RegisterDeviceNotificationResult^ NativeHelpers::ShellHelper::RegisterDeviceNotificationForWindow(IntPtr hWnd, String ^ drive)
{
if (String::IsNullOrWhiteSpace(drive))
return nullptr;
pin_ptr<const wchar_t> str = PtrToStringChars(drive);
HANDLE hDrive = CreateFile(str, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hDrive == INVALID_HANDLE_VALUE)
return nullptr;
DEV_BROADCAST_HANDLE NotificationFilter;
GUID my_GUID_IO_MEDIA_EJECT_REQUEST;
HRESULT hRes = CLSIDFromString(L"{d07433d1-a98e-11d2-917a-00a0c9068ff3}", &my_GUID_IO_MEDIA_EJECT_REQUEST);
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
NotificationFilter.dbch_handle = hDrive;
if (hRes == S_OK)
NotificationFilter.dbch_eventguid = my_GUID_IO_MEDIA_EJECT_REQUEST;
HDEVNOTIFY hNotify = ::RegisterDeviceNotificationW((HANDLE)hWnd.ToInt64(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
if (hNotify == NULL)
{
DWORD err = GetLastError();
::CloseHandle(hDrive);
return nullptr;
}
RegisterDeviceNotificationResult ^ result = gcnew RegisterDeviceNotificationResult();
result->DeviceHandle = (IntPtr)hDrive;
result->DevNotifyHandle = (IntPtr)hNotify;
return result;
}
C#:
private IntPtr OnWindowMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == NI.WM_DEVICECHANGE) // WM_DEVICECHANGE
{
if (wParam.ToInt64() == 0x0000000000008001L) // DBT_DEVICEQUERYREMOVE
{
handled = true;
NI.DEV_BROADCAST_HANDLE dbHandle = (NI.DEV_BROADCAST_HANDLE)System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, typeof(NI.DEV_BROADCAST_HANDLE));
// Unregister device notification and remove FileSystemWatcher.
ClearDeviceNotification(dbHandle.DevNotifyHandle);
return new IntPtr(1); // Ejection of USB memory is allowed.
}
}
return IntPtr.Zero;
}
So far this works very well with normal storage devices like USB sticks. 'OSF Mount' is an app that allows you to open vmdk files created by VMWare Player (or other container formats) and add them as a normal drive. However, when using the 'OSF Mount' app, 'RegisterDeviceNotification()' returns NULL and 'GetLastError()' returns 1066 (ERROR_SERVICE_SPECIFIC_ERROR; 'The service has returned a service-specific error code.'). So 'hNotify' is NULL. Nevertheless, when requested to eject the drive, I get a WM_DEVICECHANGE message with DBT_DEVICEQUERYREMOVE to my window. This is accompanied by a 'DevNotifyHandle', but my app does not know this, as 'RegisterDeviceNotification()' returned NULL. Therefore, my app cannot associate and release a FileSystemWatcher with this 'DevNotifyHandle'. This prevents the drive from being dismounted without errors.
Does anyone have an idea how I can solve the problem? Can I fix the LastError 1066 or is it possible to determine the associated drive to the 'DevNotifyHandle' supplied by WM_DEVICECHANGE?