USB-s eszközök kezelése

 

Nemrég egy olyan feladatot kaptam hogy közvetlenül  USB-s nyomtatóra írjak ki egy fájlt.

Sok utánajárás és keresgélés után jöttem csak rá a megoldásra, ezért gondoltam leírom, hogy is lehet megcsinálni. A problémát WindowsXP alatt kellett megoldanom, ezért a példaprogram is az XP-s megoldást mutatja be. A megvalósítás pedig Borland Delphi rendszerben történt.

 

Minden USB-s eszköznek van egy egyedi azonosítója (GUID) és egy DeviceName–je. Ha meg akarjuk nyitni az eszközt, akkor ezt a DeviceName-et kell átadni a CreateFile() WinAPI függvénynek. A DeviceName-et a GUID-ból tudhatjuk meg, azt pedig a registry-ből olvashatjuk ki. Egy olyan megoldást fogok bemutatni, ami a számítógéphez csatlakoztatott USB-s eszközöket kilistázza a DeviceName-mel. Ehhez a SetupApi.dll-ben található függvényeket fogjuk használni.

 

WinXP alatt a következő helyen vannak az USB-s bejegyzések: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB

A Vid-del kezdődő bejegyzéseken belül található egy DeviceParameters, ami tartalmazza a SymbolicName kulcsot.

Aminek az értéke pl.

\??\USB#Vid_03f0&Pid_2304#TH1CQ1F095#{a5dcbf10-6530-11d2-901f-00c04fb951ed}

 

A kapcsok közötti rész a GUID {a5dcbf10-6530-11d2-901f-00c04fb951ed}

 

A SymbolicName-ből egy kis módosítással megkaphatjuk a DeviceName-t, ha kicseréljük az 1-4. karaktereket erre: \\?\.

 

Ez már egy érvényes DeviceName amit megadhatunk a CreateFile() függvénynek

\\?\USB#Vid_03f0&Pid_2304#TH1CQ1F095#{a5dcbf10-6530-11d2-901f-00c04fb951ed}

 

var nyomtatohw: string;

...

nyomtatohw := ‘\\?\USB#Vid_03f0&Pid_2304#TH1CQ1F095#{a5dcbf10-6530-11d2-901f-00c04fb951ed}’

h:=CreateFile(pchar(nyomtatohw), GENERIC_READ OR GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

...

 

De sajnos ilyet nem drótozhatunk bele konstans módon egy programba, ezért kénytelenek vagyunk kilistázni az összes eszközt egy leírással együtt és így a felhasználó ki tudja választani a megfelelőt (az én esetemben a nyomtatót)

 

Ezt, a function listUSB(var count: integer):ADeviceNames;  függvénnyel csináltam meg.

Ez viszont egy AdeviceNames típusú rekordtömbben adja vissza a neveket a leírással.

 

type TDeviceNames = record

 Real,

 Device : string[255];

end;

 

type ADeviceNames = array of TDeviceNames;

 

A következő függvénnyel ellenőrzöm, hogy az eszköz csatlakoztatva van-e a számítógéphez.

A PGUID egy GUID típusú változóra mutató pointer, aminek a feltöltésére van egy makró, de nekem ez sehogy sem működött, ezért ügyködtem fél oldalon keresztül a manuális feltöltéssel.

A CheckGuid() függvényt a listUSB() függvény használja miután kikereste a registry-ből a GUID-ot és meghatározta belőle a DeviceName-et, csak akkor tárolja el az AdeviceNames tömbben ha a CheckGuid() függvény true értékkel tér vissza, ami azt jelenti hogy az eszköz a számítógéphez van kötve és be is van kapcsolva. A SetupApi.dll-es rutinokhoz nem fűznék hosszabb megjegyzést, ezekről van részletes leírás a Microsoft weboldalán is.

 

function CheckGuid(guid, DeviceName: string):boolean;

var

 sub: Tstrings;

 d1 : cardinal;

 d2, d3, d4: word;

 d: byte;

 d51, d52, d53, d54, d55, d56: byte;

 dd: string;

 i, f, m: cardinal;

 needed:cardinal;

 hInfo: HDEVINFO;

 L_GUID: PGUID;

 ii: DWORD;

 Interface_Info: SP_INTERFACE_DEVICE_DATA;

 detail: PSP_INTERFACE_DEVICE_DETAIL_DATA;

 Devices: Tstrings;

 name: pchar;

Begin

 if length(guid)<>36 then begin CheckGuid:=false; exit; end; //ellenőrzések hogy valódi e a GUID

 sub:=TStringList.Create;

 sub:=explode('-', guid); 

 if sub.Count<>5  then begin CheckGuid:=false; exit; end;

 for i:=0 to sub.Count-2 do

  sub.Strings[i]:='$' +  sub.Strings[i];

 

 d1 := StrToInt(sub.Strings[0]);

 d2 := StrToInt(sub.Strings[1]);

 d3 := StrToInt(sub.Strings[2]);

 d4 := StrToInt(sub.Strings[3]);

 SetLength(dd, 3);

 dd[1]:='$';

 dd[2]:= sub.Strings[4][1];

 dd[3]:= sub.Strings[4][2];

 d51:=StrToInt(dd);

 dd[2]:= sub.Strings[4][3];

 dd[3]:= sub.Strings[4][4];

 d52:=StrToInt(dd);

 dd[2]:= sub.Strings[4][5];

 dd[3]:= sub.Strings[4][6];

 d53:=StrToInt(dd);

 dd[2]:= sub.Strings[4][7];

 dd[3]:= sub.Strings[4][8];

 d54:=StrToInt(dd);

 dd[2]:= sub.Strings[4][9];

 dd[3]:= sub.Strings[4][10];

 d55:=StrToInt(dd);

 dd[2]:= sub.Strings[4][11];

 dd[3]:= sub.Strings[4][12];

 d56:=StrToInt(dd);

 SetLength(dd, 0);

 

 getmem(L_GUID, 16); getmem(name, 255);

 L_GUID.D1:=d1;

 L_GUID.D2:=d2;

 L_GUID.D3:=d3;

 asm

  mov ax, d4

  mov d, ah

 end;

 L_GUID.D4[0]:=d;

 asm

  mov ax, d4

  mov d, al

 end;

 L_GUID.D4[1]:=d;

 L_GUID.D4[2]:=d51;

 L_GUID.D4[3]:=d52;

 L_GUID.D4[4]:=d53;

 L_GUID.D4[5]:=d54;

 L_GUID.D4[6]:=d55;

 L_GUID.D4[7]:=d56;

 

 hInfo := SetupDiGetClassDevs(L_GUID, nil, 0,  (DIGCF_PRESENT OR DIGCF_INTERFACEDEVICE));

 if  DWORD(hInfo)=INVALID_HANDLE_VALUE then

 begin

  freemem(L_GUID, 16);

  freemem(name, 255);

  checkGuid:=false;

  exit;

 end;

 Devices:= TstringList.Create;

 i:=0;

 while true do

 begin

   Interface_Info.cbSize := sizeof(SP_INTERFACE_DEVICE_DATA);

   // Enumerate device

   if not SetupDiEnumDeviceInterfaces(hInfo, nil, L_GUID, i, @Interface_Info) then

   begin

     SetupDiDestroyDeviceInfoList(hInfo);

     m := i;

     break;

   end;

    // get the required length

   SetupDiGetDeviceInterfaceDetail(hInfo, @Interface_Info, nil, 0, @needed, nil);

   getmem(detail, needed);//detail = (PSP_INTERFACE_DEVICE_DETAIL_DATA) malloc(needed);

   if (detail=nil) then

   begin

    SetupDiDestroyDeviceInfoList(hInfo);

    m := i;

    break;

   end;

// fill the  device details

   detail.cbSize := sizeof(TSP_INTERFACE_DEVICE_DETAIL_DATA);

   if (not SetupDiGetDeviceInterfaceDetail(hInfo, @Interface_Info, detail ,needed, @needed, nil)) then

   begin

     freemem(detail, cardinal(needed));

     SetupDiDestroyDeviceInfoList(hInfo);

     m := i;

     break;

   end;

   f:=0;

   StrCopy(name, detail.DevicePath);

   freemem(detail, cardinal(needed));

   Devices.Add(name); // keep a copy of each device name

   inc(i);

 end;

 freemem(L_GUID, 16);

 freemem(name, 255);

 checkGuid:=false;

 if Devices.Count = 0 then begin  checkGuid:=false; Devices.Free; exit; end;

 for i:=0 to Devices.Count-1 do

  if UpperCase(Devices.Strings[i]) = UpperCase(DeviceName) then

  begin

    checkGuid:=true;

    break;

  end;

 Devices.Free;

End;

 

Ez pedig a listUSB függvény ami visszaadja az USB-s listát (AdeviceNames) és az eszközök számát (count). Ide kellenek a SetupApi-s függvények.

 

function listUSB(var count: integer):ADeviceNames;

var

 sub, vid: TStrings;

 DeviceNamesArray: ADeviceNames;

 i, k, j, c, index, max: integer;

 dis1, dis2, s, guid: AnsiString;

 reg: Tregistry;

begin

 count := 0;

 listUSB:=nil;

 reg:=Tregistry.Create; reg.RootKey :=  HKEY_LOCAL_MACHINE;

 if not reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\USB') then

 begin

   ShowMessage('Can''t open in registry: \SYSTEM\CurrentControlSet\Enum\USB'); reg.Free;

   exit;

 end;

 sub := TstringList.Create;

 vid := TstringList.Create;

 SetLength(DeviceNamesArray, 0);

 reg.GetKeyNames(sub);

 if ( sub.Count=0 ) then begin

  ShowMessage('Can''t open in registry: \\SYSTEM\\CurrentControlSet\\Enum\\USB...');       

  reg.Free;

  exit;

 end;

 max := sub.Count;

 index:=0;

 for i:=0  to max-1 do

 begin                    //Vid-del kezdődő bejegyzéseket keresünk

  if ( (length(sub.Strings[i])>=3) and (UpperCase(sub.Strings[i][1])='V') and (UpperCase(sub.Strings[i][2])='I') and (UpperCase(sub.Strings[i][3])='D') ) then

  begin

    if not (reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\USB\'+sub.Strings[i]))   then 

    begin

        ShowMessage('Can''t open key in registry: \SYSTEM\CurrentControlSet\Enum\USB\'+sub.Strings[i]);    

        reg.Free();

       exit

    end;

    reg.GetKeyNames(vid); //VID_

    for c:=0 to vid.Count-1 do  //open SubKeys

    begin

     if not (reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\USB\'+sub.Strings[i])) then

     begin

       ShowMessage('Can''t open key in registry: \SYSTEM\CurrentControlSet\Enum\USB\'+sub.Strings[i]);   

       reg.Free();

       exit

     end;

     if not (reg.OpenKeyReadOnly(vid.Strings[c])) then continue;  //VID_ 0.

     dis1:= reg.ReadString('LocationInformation');   //Az eszköz leírása

     dis2:= reg.ReadString('DeviceDesc');               //Az eszköz leírása

     if length(dis1)= 0 then dis1:='notfound'; //Real_ 0dik

     if length(dis2)= 0 then dis2:='notfound'; //Real_ 0dik

     if not (reg.OpenKeyReadOnly('Device Parameters')) then continue;

     s := reg.ReadString('SymbolicName');         

     if (length(s) = 0) then continue;

     Delete(s, 1, 4); s:='\\?\'+s;       //a szimbolikus névből előállítjuk a GUID-ot és a DeviceName-t

     guid:=s; Delete(guid, 1, pos('{', guid)); delete(guid, length(guid), length(guid));

     if not checkGuid(guid, s) then continue; //ha az eszköz használatban van akkor eltároljuk

     SetLength(DeviceNamesArray, length(DeviceNamesArray)+1);

     DeviceNamesArray[index].Real:=dis1 + ' ' +dis2;

     DeviceNamesArray[index].Device:=s;

     inc(index);

    end;

  end;

 end;

 if (index=0) then begin ShowMessage('USB Devices not found'); reg.Free; exit; end;

 count := index;

 listUSB:=DeviceNamesArray;

end;

 

 

Innen már csak ki kell írnunk a listát és a kiválasztott elem DeviceName-jét felhasználhatjuk az eszköz megnyitására, majd írás/olvasására.

 

DeviceNames  : = listUSB(i);

 

USB_DeviceName := DeviceNames[x].Device;

 

nyomtatohandle := CreateFile(pchar(USB_DeviceName), GENERIC_READ OR GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

 

WriteFile(h, nyomtatohandle, count , b, nil);

 

CloseHandle(nyomtatohandle);

 

Olvasni ehhez hasonlóan, ReadFile() tudunk.

 

Magyar Attila - m.magyar3@chello.hu