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.