问题
I would like to know how I can get all the driver files for a particular device just like the Device Manager does?
I have the following code:
procedure TdlgMain.Test(const DeviceIndex: Integer);
var
PnPHandle: HDEVINFO;
DevData: TSPDevInfoData;
DeviceInterfaceData: TSPDeviceInterfaceData;
FunctionClassDeviceData: PSPDeviceInterfaceDetailData;
Success: LongBool;
Devn: Integer;
BytesReturned: DWORD;
SerialGUID: TGUID;
begin
ZeroMemory(@DevData, SizeOf(SP_DEVINFO_DATA));
DevData.cbSize := SizeOf(SP_DEVINFO_DATA);
ZeroMemory(@DeviceInterfaceData, SizeOf(TSPDeviceInterfaceData));
DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
if not SetupDiEnumDeviceInfo(hAllDevices,
DeviceIndex, DevData) then Exit;
SerialGUID := DevData.ClassGuid;
PnPHandle := SetupDiGetClassDevs(@SerialGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
if PnPHandle = Pointer(INVALID_HANDLE_VALUE) then
Exit;
Devn := 0;
repeat
DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
Success := SetupDiEnumDeviceInterfaces(PnPHandle, nil, SerialGUID, Devn, DeviceInterfaceData);
if Success then
begin
DevData.cbSize := SizeOf(DevData);
BytesReturned := 0;
// get size required for call
SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData, nil, 0, BytesReturned, @DevData);
if (BytesReturned <> 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
begin
// allocate buffer and initialize it for call
FunctionClassDeviceData := AllocMem(BytesReturned);
FunctionClassDeviceData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
//FunctionClassDeviceData.cbSize := BytesReturned;
if SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData,
FunctionClassDeviceData, BytesReturned, BytesReturned, @DevData) then
begin
ShowMessage(FunctionClassDeviceData.DevicePath);
end else
RaiseLastOSError();
FreeMem(FunctionClassDeviceData);
end;
end;
Inc(Devn);
until not Success;
SetupDiDestroyDeviceInfoList(PnPHandle);
But the ShowMessage()
is either not called at all or returns \
. How do I get the files properly?
I had a look at devcon
from the WinDDK, but it does not return the files either.
Thank you.
回答1:
I figured it out. There's no API to do it for you, you need to parse the INF files to achieve the result. Here's a quick-n-dirty solution for all of you, who are interested.
procedure TdlgMain.Test(const DeviceIndex: Integer);
var
Paths: TStringList;
I: Integer;
function GetWinDir: string; inline;
var
dir: array [0 .. MAX_PATH] of Char;
begin
GetWindowsDirectory(dir, MAX_PATH);
Result := IncludeTrailingBackslash(StrPas(dir));
end;
function GetSpecialFolderPath(const folder: Integer): string; inline;
const
SHGFP_TYPE_CURRENT = 0;
var
path: array [0 .. MAX_PATH] of Char;
begin
if SUCCEEDED(SHGetFolderPath(0, folder, 0, SHGFP_TYPE_CURRENT, @path[0]))
then
Result := IncludeTrailingBackslash(path)
else
Result := '';
end;
function LocateInfFile(const F: String): String; inline;
var
T: String;
begin
Result := '';
if (Pos(SysUtils.PathDelim, F) > 0) then
begin
Result := F;
Exit;
end;
T := GetWinDir();
if (FileExists(T + 'inf\' + F)) then
Result := T + 'inf\' + F
else if (FileExists(T + 'system32\' + F)) then
Result := T + 'system32\' + F;
end;
procedure ReadSectionNoKeys(const AFile, ASection: String;
const SL: TStringList);
var
TheFile: TStringList;
Line: String;
TrimEnd: Boolean;
Idx, Tmp: Integer;
begin
TrimEnd := False;
TheFile := TStringList.Create();
try
TheFile.LoadFromFile(AFile);
Idx := TheFile.IndexOf('[' + ASection + ']');
if (Idx <> -1) then
begin
Idx := Idx + 1;
while True do
begin
Line := Trim(TheFile[Idx]);
Inc(Idx);
if (Pos(';', Line) = 1) then
continue;
if (Pos('[', Line) > 0) then
Break;
Tmp := Pos(',', Line);
if (Tmp > 0) then
TrimEnd := True
else
begin
Tmp := PosEx(';', Line, 3);
if (Tmp > 0) then
TrimEnd := True;
end;
if (Line <> '') then
begin
if (TrimEnd) then
begin
Line := Trim(Copy(Line, 1, Tmp - 1));
TrimEnd := False;
end;
SL.Add(Line);
end;
if (Idx = (TheFile.Count - 1)) then
Break;
end;
end;
finally
TheFile.Free();
end;
end;
function IniReadStr(const Ini: TIniFile; const S, L, D: String): String;
var
T: Integer;
begin
Result := Ini.ReadString(S, L, D);
T := Pos(';', Result);
if (T > 0) then
Result := Trim(Copy(Result, 1, T - 1));
end;
procedure ParseInfFile(const InfFile, SectionName: String);
var
I: TIniFile;
SL, FilesList: TStringList;
X, Y, Tmp: Integer;
Pth, S, S1: String;
begin
I := TIniFile.Create(InfFile);
try
if (SectionName <> '') and (I.SectionExists(SectionName)) then
begin
// Check if the section has a value called "CopyFiles".
if (I.ValueExists(SectionName, 'CopyFiles')) then
begin
// It has. Read it to a string and separate by commas.
SL := TStringList.Create();
try
SL.CommaText := IniReadStr(I, SectionName, 'CopyFiles', '');
// Now, every line of the string list is a section name. Check
// the destination directory of each.
if (I.SectionExists('DestinationDirs')) then
for X := 0 to SL.Count - 1 do
begin
S := IniReadStr(I, 'DestinationDirs', SL[X], '');
if (S = '') then
S := IniReadStr(I, 'DestinationDirs', 'DefaultDestDir', '');
if (S <> '') then
begin
// Split the path by comma, if any.
Tmp := Pos(',', S);
S1 := '';
if (Tmp > 0) then
begin
S1 := Trim(Copy(S, Tmp + 1, Length(S)));
S := Trim(Copy(S, 1, Tmp - 1));
end;
// Convert the numeric value of S to a proper directory.
Pth := '';
if (S = '10') then
Pth := GetWinDir();
if (S = '11') then
Pth := GetWinDir() + 'system32\';
if (S = '12') then
Pth := GetWinDir() + 'system32\drivers\';
if (S = '50') then
Pth := GetWinDir() + 'system\';
if (S = '30') then
Pth := ExtractFileDrive(GetWinDir());
if (StrToInt(S) >= 16384) then
Pth := GetSpecialFolderPath(StrToInt(S));
if (S1 <> '') then
Pth := IncludeTrailingBackslash(Pth + S1);
// If we got the path, read the files.
if (Pth <> '') then
begin
FilesList := TStringList.Create();
try
ReadSectionNoKeys(InfFile, SL[X], FilesList);
for Y := 0 to FilesList.Count - 1 do
if (Paths.IndexOf(Pth + FilesList[Y]) = -1) then
Paths.Add(Pth + FilesList[Y]);
finally
FilesList.Free();
end;
end;
end;
end;
finally
SL.Free();
end;
end;
// Check if there're "Include" and "Needs" values.
if ((I.ValueExists(SectionName, 'Include')) and
(I.ValueExists(SectionName, 'Needs'))) then
begin
// Split both by comma.
SL := TStringList.Create();
FilesList := TStringList.Create();
try
SL.CommaText := IniReadStr(I, SectionName, 'Include', '');
FilesList.CommaText := IniReadStr(I, SectionName, 'Needs', '');
if (SL.Text <> '') and (FilesList.Text <> '') then
for X := 0 to SL.Count - 1 do
for Y := 0 to FilesList.Count - 1 do
ParseInfFile(LocateInfFile(SL[X]), FilesList[Y]);
finally
FilesList.Free();
SL.Free();
end;
end;
end;
finally
I.Free();
end;
end;
begin
Paths := TStringList.Create();
try
ParseInfFile(LocateInfFile(DeviceHelper.InfName), DeviceHelper.InfSection);
Paths.Sort();
ListView_InsertGroup(lvAdvancedInfo.Handle, 'Driver Files', 2);
for I := 0 to Paths.Count - 1 do
ListView_AddItemsInGroup(lvAdvancedInfo, '', Paths[I], 2);
finally
Paths.Free();
end;
end;
来源:https://stackoverflow.com/questions/10481467/getting-driver-files-for-a-particular-device