Получить букву диски или ms-dos name
Я умею находить через SetupDi-функции данное устройство (по гуид отбираю класс USB и в нем по vidpid сам девайс) SymbolicName, но его не воспринимает DeviceIoControl , а мне надо.
Я знаю, что можно получить буквы всех дисков, их дос-имена и даже кой-какие параметры с пом. Volume Management Functions .
Могу я сделать CreateFile на SymbolicName и наверное что-то узнать - размер там.. писать туда и читать наверное смогу файлы. Но, я не знаю моё ли это устройство, у меня ведь есть только vidpid и имя !
Вот такая незадача, надо найти устройство и получить имя, подходящее для DeviceIoControl , но как сделать - непонятно!
Прошу помощи.
// Строим список подключённых устройств с заданными параметрами и получаем дескриптор на него
hDeviceList = SetupDiGetClassDevs(&tmpguid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDeviceList == INVALID_HANDLE_VALUE) return 1;
DWORD errCode = 0, tmpsize = 0, countDev = 0, countMyDev = 0;
SP_DEVINFO_DATA Devinfo; //инфа об устройстве
SP_DEVICE_INTERFACE_DATA Interface;
PSP_DEVICE_INTERFACE_DETAIL_DATA pInterfaceDetailed;
wchar_t wcVidPidStr[512] = {0}; //for hardwareid
for (countDev = 0; countDev < 10000;)
{
memset(&Devinfo, 0, sizeof(SP_DEVINFO_DATA));
Devinfo.cbSize = sizeof(SP_DEVINFO_DATA); // Нужно установить.
if (!SetupDiEnumDeviceInfo(hDeviceList, countDev, &Devinfo)) //Получаем информацию об очередном устройстве из списка.
{
if(GetLastError()== ERROR_NO_MORE_ITEMS) break;
else return 1;
}//!SetupDiEnumDeviceInfo
memset(wcVidPidStr, 0, sizeof(wcVidPidStr));
if ( !SetupDiGetDeviceRegistryProperty(hDeviceList, &Devinfo, SPDRP_HARDWAREID, NULL, (byte*)wcVidPidStr,
sizeof(wcVidPidStr), &tmpsize) ) //получаем строку с vidpid
{
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
continue; //пробуем следующее по списку
}
_wcslwr_s(wcVidPidStr, 512); //в нижний регистр
if(wcsstr(wcVidPidStr, MY_VID_PID) == NULL) //ищем наше устройство (по vidpid)
{
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++; //если не нашли, то идем за следующим
continue;
}
else
{
memset(&Interface, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
Interface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); // Фиксировано
if(!SetupDiEnumDeviceInterfaces(hDeviceList, NULL, &tmpguid, countMyDev, &Interface)) //Получаем информацию об интерфейсе (нужно для вызова функций далее)
{
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
continue; //пробуем следующее по списку
}
SetupDiGetDeviceInterfaceDetail(hDeviceList, &Interface, NULL, NULL, &tmpsize, NULL); //Получаем путь к устройству. Сначала получим размер необходимой памяти.
errCode = GetLastError();
if(errCode != ERROR_INSUFFICIENT_BUFFER)
{
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
continue; //пробуем следующее по списку
}
pInterfaceDetailed = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(tmpsize);
if (pInterfaceDetailed == NULL)
{
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
continue; //пробуем следующее по списку
}
pInterfaceDetailed->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
// Считываем подробную информацию об интерфейсе
if(!SetupDiGetDeviceInterfaceDetail(hDeviceList, &Interface, pInterfaceDetailed, tmpsize, NULL, NULL))
{
free(pInterfaceDetailed);
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
continue; //пробуем следующее по списку
}
countMyDev++; //это наш клиент
free(pInterfaceDetailed);
vstrSymLinks.push_back(pInterfaceDetailed->DevicePath);
if(!SetupDiDeleteDeviceInfo(hDeviceList, &Devinfo)) countDev++;
}//our vidpid
}//for
//И вот я получил SymbolicName
//Посылаю устройству команду читать нулевой сектор
struct scsi_st
{
SCSI_PASS_THROUGH_DIRECT t_spti;
DWORD tmp;
byte sensebuf[32];
} myspti;
vector<byte> vbuf;
hDevice = CreateFile(vstrSymLinks, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); //Устройство открылось, хэндл получен
if (hDevice == INVALID_HANDLE_VALUE) return 1;
memset(&myspti, 0, sizeof(scsi_st));
myspti.t_spti.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
myspti.t_spti.Lun = 0;
myspti.t_spti.TargetId = 0;
myspti.t_spti.PathId = 0;
myspti.t_spti.CdbLength = 10;
myspti.t_spti.DataIn = SCSI_IOCTL_DATA_IN;
myspti.t_spti.SenseInfoLength = 32;
myspti.t_spti.SenseInfoOffset = sizeof(SCSI_PASS_THROUGH_DIRECT) + sizeof(DWORD);
myspti.t_spti.TimeOutValue = 10; //2
vbuf.assign(512, 0);
myspti.t_spti.DataTransferLength = (DWORD)vbuf.size();
myspti.t_spti.DataBuffer = &vbuf[0];
myspti.t_spti.Cdb[0] = READ_10;
myspti.t_spti.Cdb[1] = 0;
myspti.t_spti.Cdb[2] = addr[3];
myspti.t_spti.Cdb[3] = addr[2];
myspti.t_spti.Cdb[4] = addr[1];
myspti.t_spti.Cdb[5] = addr[0];
myspti.t_spti.Cdb[6] = 0;
myspti.t_spti.Cdb[7] = len[1];
myspti.t_spti.Cdb[8] = len[0];
myspti.t_spti.Cdb[9] = 0;
DWORD returned = 0;
if(!DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &myspti, sizeof(scsi_st), &myspti, sizeof(scsi_st), &returned, NULL))
return 1;
Что здесь (в DeviceIoControl) происходит! Я получаю ERROR_NOT_SUPPORTED на SymbolicName , а если я CreateFile делаю на L"\\\\.\\j:" или на L"\\\\.PhysicalDrive1" , то все отлично - читает нулевой сектор и причем
содержимое там правильное, т.е. читается ок.
Соответственно мне надо по VendorId_ProductId получить букву диска или дос-имя. Пока я не понял как, кроме перебора всех букв с помощью Volume Management Functions и слать им ээ.. IOCTL_SCSI_GET_INQUIRY_DATA наверное?
при чем здесь SymbolicName и DeviceIOControl()?
Где конкретно происходит ошибка? Как выглядит SymbolicName?
Я и не утверждаю, что надо через него пытаться работать с устройством. Я показываю, что его-то (имя), найти я могу без особых проблем, а вот то что надо - дос-имя или букву диска я пока не могу (не знаю) найти по vendorId_productId ! А ведь явно это можно сделать, я вот читаю семейство функций Public PnP Configuration Manager Functions типа CM_.. и похоже там то, что надо. Только пока еще не разобрался. ((
Если вы ясно представляете последовательность действий для получения буквы диска/дос-имени по vendorId_productId , то прошу написать. Отправлять каждому устройству запросы, мне бы не хотелось, лучше все-таки всзять значения из системы.
Пока хочу посоветовать изучить IOCTL_MOUNTDEV_QUERY_UNIQUE_ID и связанные функции. По этому IOCTL по-идее устройство должно вернуть свою \\Volume\{..} по которой уже определяется буква. Это, если я ничего не путаю, и есть ниточка связывающая Device и Volume. Как проясните этот момент - напишите сюда.
В общем,случайно наткнулся на функцию QueryDOSDevice,прочитал описание и вспомнил про тему.Возможно,она подойдёт
Она по букве диска возвращает имя в формате \Device\<…>.Т.е.,перебирая значения,вы можете наткнуться на нужное.Ибо я понял,что вы имя получаете в формате \Device\<…>
Надеюсь,вам поможет
Я решил свою проблему, однако сразу вылезла еще одна - spti работает только под администратором. Один раз, почему-то программа работала под пользователем, но почему я не знаю, возможно все-таки там была ошибка.. или это был глюк. Сейчас, как я не бьюсь, под юзером CreateFile выдает access denied.
Ниже будет пост по поиску буквы диска, имея на руках vendor id and product id и отправке устройству scsi команд. Надеюсь кому-нибудь пригодится
Создано устройство, подключаемое по юсб и в системе определяемое как mass storage. Т.е. не надо писать своих драйверов, всё за нас написано микрософтом. Мы хотим в него отправлять команды через DeviceIoControl, т.к. просто работы с файлами нам не хватает - мы вводим в устройство еще свои некоторые команды и функционал.
Поехали.
Выбираем технологию spti. В ней есть недостаток - нужны права админа. Пока я могу это игнорировать, но в будущем мне придется найти другой путь.
DeviceIoControl требует хэндл устройства, а spti требует, чтобы устройство это было \\.\K: or \\.\PhysicalDrive1 - к примеру . Иначе DeviceIoControl вернет ошибку.
Как найти эти имена? Второй тип имени я так и не нашел, но есть предположения что он находится через посылку шине запроса типа inquiry/query ну и последующего перебора и отправке запросов inquiry/query уже устройствам.
Первый тип имени находится достаточно легко.
Я искал через USB устройства.
SetupDiGetClassDevs на GUID_DEVINTERFACE_USB_DEVICE выдает нам хэндл на список usb device interface, не забудьте DIGCF_PRESENT|DIGCF_DEVICEINTERFACE .
Затем в цикле выполняем вызовы SetupDiEnumDeviceInfo -> SetupDiGetDeviceRegistryProperty (с запросом SPDRP_HARDWAREID) . Сверяем HARDWAREID с нашими vendorId_productId и если наше усторйство идем далее, разве что можно вызвать SetupDiGetDeviceInstanceId для определения серийного номера (мы его внесли в дескрипторы устройства и поэтому для его определения нам не надо опрашивать девайс лишний раз - \USB\Vid_xxxxxPid_yyyy\1000000000000000 где 10000.. это наш серийник).
Поднимаемся выше по device tree с помощью функций CM_Get_Child -> CM_Get_Device_ID и ищем уровень \STORAGE\RemovableMedia, там видим имя вида \STORAGE\RemovableMedia\8&10849428&0&RM например, где 8&10849428&0&RM - некий рандомный идентификатор диска, который в течении сессии неизменен .
Далее открываем HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices и перебираем параметры ключа (с пом-ю RegEnumValue например). Нас интересуют параметры с именами \DosDevice\X: где Х - буквы дисков. Среди них мы ищем в значениях параметров (а это массивы юникодных символов, по крайней мере для стандартных mass storage без извращений) наш 8&10849428&0&RM . Значит это наш диск. Выгрызаем из имени X: и.. собственно все, можно общаться с устройством.
CreateFile("\\.\x:", ..) -> DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, ...)
Описанная мною последовательность не единственная, можно и через другие функции/параметры/ключи реестра. Я выбрал этот путь, мне он показался не слишком длинный и достаточно надежным.
Увы, если вы разрабатываете массовое коммерческое приложение, то требование админских прав делают spti практически нереальным вариантом работы с устройством. Буду биться над данной проблемой в ближайшем будущем, наверное придется браться за ASPI, т.к. писать свой драйвер я вряд ли осилю :(
Ниже исходник примера, вам требуется вставить свои vendorID_productId. Единственная проблема в примере - почему-то после общения с устройство возникает неприятный глюк - после перезагрузки компа загрузка не идет дальше биоса. Либо нужно еще что-то послать в устройство, какую-то команду закрытия или.. хз чего, либо наш девайс глючит (
Как найти эти имена? Второй тип имени я так и не нашел,
Можно через IOCTL_STORAGE_GET_DEVICE_NUMBER.
В целом принцип понятен, но выскажу свои соображения из того что успел изучить.
Действительно, мы когда-то делали нечто подобное для определения шины устройства, но шины эти были IDE и SCSI. Там оказывается несколько проще. Здесь же проблема в том, что за VID/PID отвечает драйвер usbhub (могу попутать имя) а за Ven/Disk/STORAGE - usbstor. И прозрачной схемы связи между объектами создаваемыми этими двумя драйверами я не нашел :(. Дело в том что когда юсб-класс=8 подкласс=6 это сторейдж и тогда подгружается usbstor и начинает свою магию. Далее по протоколу юсб я не разбирался, но знаю, что явно связаны они через этот магический номер типа 000018451960C42C&0. Проблема как раз в том что мне не понятен источник этого номера, т.к. для "нормальных" юсб девайсов он выглядит более-менее, но для юсб-салазок которые я использую он 0 для всех сторейдж устройств которые воткнуты в систему. Следовательно, предположил я, если мы возьмем еще одни подобные (а возможно и такие же) салазки и подключим и их, получим связь типа Х где тот самый 0 будет в центре и появится неопределенность какому vid/pid принадлежит данный сторейдж. Если бы стал понятен принцип формирования этого номера, можно было бы построить законченную схему.
...
PROFIT!