本文介紹如何在 Delphi 中透過 Windows SetupAPI.dll 列舉系統所有硬體裝置,並展示裝置類別名稱與圖示,更進一步示範如何程式化啟用或停用特定裝置(如軟碟機、光碟機、USB 裝置)。原始碼參考 MSDN 範例並加以整理擴充,提供完整可編譯的單元與表單程式碼。
此單元封裝了 SetupAPI.dll 的關鍵函數、常數與結構定義,包含裝置資訊集合操作、裝置屬性查詢、類別圖示列表取得、裝置狀態變更(啟用/停用)等核心功能。
unit SetupAPI;
interface
uses Windows;
const
DIGCF_PRESENT = $00000002;
DIGCF_ALLCLASSES = $00000004;
SPDRP_DEVICEDESC = 0;
DIF_PROPERTYCHANGE = 18;
DICS_ENABLE = 1;
DICS_FLAG_GLOBAL = 1;
DICS_DISABLE = 2;
type
HDEVINFO = type Cardinal;
DI_FUNCTION = type Cardinal;
PSP_DEVINFO_DATA = ^SP_DEVINFO_DATA;
SP_DEVINFO_DATA = record
cbSize : DWORD;
ClassGuid : TGUID;
DevInst : DWORD;
Reserved : Longint;
end;
PSP_CLASSINSTALL_HEADER = ^SP_CLASSINSTALL_HEADER;
SP_CLASSINSTALL_HEADER = record
cbSize : DWORD;
InstallFunction : DI_FUNCTION;
end;
PSP_PROPCHANGE_PARAMS = ^SP_PROPCHANGE_PARAMS;
SP_PROPCHANGE_PARAMS = record
ClassInstallHeader : SP_CLASSINSTALL_HEADER;
StateChange : DWORD;
Scope : DWORD;
HwProfile : DWORD;
end;
PSP_CLASSIMAGELIST_DATA = ^SP_CLASSIMAGELIST_DATA;
SP_CLASSIMAGELIST_DATA = record
cbSize : DWORD;
ImageList : HWND;
Reserved : DWORD;
end;
// 從 Setupapi.dll 匯入的函數
function SetupDiGetClassDevs(const ClassGuid: PGUID; Enumerator: PChar;
hwndParent: HWND; Flags: DWORD): HDEVINFO; stdcall;
external 'Setupapi.dll' name 'SetupDiGetClassDevsA';
function SetupDiEnumDeviceInfo(DeviceInfoSet: HDEVINFO; MemberIndex: DWORD;
DeviceInfoData: PSP_DEVINFO_DATA): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiEnumDeviceInfo';
function SetupDiGetDeviceRegistryProperty(DeviceInfoSet: HDEVINFO;
DeviceInfoData: PSP_DEVINFO_DATA; Property_: DWORD; PropertyRegDataType: PWORD;
PropertyBuffer: PByte; PropertyBufferSize: DWORD; RequiredSize: PWORD): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiGetDeviceRegistryPropertyA';
function SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiDestroyDeviceInfoList';
function SetupDiClassNameFromGuid(ClassGuid: PGUID; ClassName: PChar;
ClassNameSize: DWORD; RequiredSize: PDWORD): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiClassNameFromGuidA';
function SetupDiSetClassInstallParams(DeviceInfoSet: HDEVINFO;
DeviceInfoData: PSP_DEVINFO_DATA; ClassInstallParams: PSP_CLASSINSTALL_HEADER;
ClassInstallParamsSize: DWORD): BOOL stdcall;
external 'Setupapi.dll' name 'SetupDiSetClassInstallParamsA';
function SetupDiCallClassInstaller(InstallFunction: DI_FUNCTION;
DeviceInfoSet: HDEVINFO; DeviceInfoData: PSP_DEVINFO_DATA): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiCallClassInstaller';
function SetupDiGetClassImageList(ClassImageListData: PSP_CLASSIMAGELIST_DATA): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiGetClassImageList';
function SetupDiDestroyClassImageList(ClassImageListData: PSP_CLASSIMAGELIST_DATA): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiDestroyClassImageList';
function SetupDiGetClassImageIndex(ClassImageListData: PSP_CLASSIMAGELIST_DATA;
ClassGuid: PGUID; ImageIndex: PINT): BOOL; stdcall;
external 'Setupapi.dll' name 'SetupDiGetClassImageIndex';
implementation
end.
表單包含 TListView (lv1)、TButton (btn1)、TImageList (il1)。點擊按鈕列舉所有裝置,顯示類別名稱、GUID、裝置描述與啟用狀態,並自動載入類別圖示。
unit untMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, SetupAPI, StdCtrls, ComCtrls, ImgList;
type
TForm1 = class(TForm)
lv1: TListView;
btn1: TButton;
il1: TImageList;
procedure btn1Click(Sender: TObject);
private
function GetDeviceClassName(aGUID: TGUID): string;
public
end;
var
Form1: TForm1;
ClassImageListData: SP_CLASSIMAGELIST_DATA; // 全域變數,程式結束時釋放
implementation
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
var
hDevInfo: Cardinal;
DeviceInfoData: SP_DEVINFO_DATA;
i: Integer;
DataT, BufferSize: DWORD;
Buffer: PAnsiChar;
ListItem: TListItem;
ImageIndex: Integer;
begin
lv1.Items.BeginUpdate;
try
// 取得裝置資訊集合控制代碼
hDevInfo := SetupDiGetClassDevs(nil, nil, 0,
DIGCF_PRESENT or DIGCF_ALLCLASSES);
if hDevInfo = INVALID_HANDLE_VALUE then Exit;
// 取得類別圖示清單
ClassImageListData.cbSize := SizeOf(SP_CLASSIMAGELIST_DATA);
if SetupDiGetClassImageList(@ClassImageListData) then
begin
il1.Handle := ClassImageListData.ImageList;
lv1.SmallImages := il1;
end;
i := 0;
BufferSize := 256;
DeviceInfoData.cbSize := SizeOf(SP_DEVINFO_DATA);
GetMem(Buffer, BufferSize);
// 列舉裝置
while SetupDiEnumDeviceInfo(hDevInfo, i, @DeviceInfoData) do
begin
SetupDiGetDeviceRegistryProperty(hDevInfo, @DeviceInfoData,
SPDRP_DEVICEDESC, @DataT, PByte(Buffer), BufferSize, nil);
Inc(i);
ListItem := lv1.Items.Add;
// 取得類別圖示索引
if SetupDiGetClassImageIndex(@ClassImageListData,
@DeviceInfoData.ClassGuid, @ImageIndex) then
ListItem.ImageIndex := ImageIndex;
// 顯示類別名稱、GUID、裝置名稱
ListItem.Caption := GetDeviceClassName(DeviceInfoData.ClassGuid);
ListItem.SubItems.Add(GUIDToString(DeviceInfoData.ClassGuid));
ListItem.SubItems.Add(Buffer);
// 簡易判斷是否可停用 (此處需自行實作 IsDisableable)
ListItem.SubItems.Add('False');
end;
if Buffer <> nil then FreeMem(Buffer);
if (GetLastError <> NO_ERROR) and (GetLastError <> ERROR_NO_MORE_ITEMS) then Exit;
finally
SetupDiDestroyDeviceInfoList(hDevInfo);
lv1.Items.EndUpdate;
end;
end;
// 透過 GUID 取得裝置類別名稱
function TForm1.GetDeviceClassName(aGUID: TGUID): string;
var
ClassName: PChar;
ClassNameSize: DWORD;
begin
ClassNameSize := 0;
GetMem(ClassName, ClassNameSize);
while not SetupDiClassNameFromGuid(@aGUID, ClassName, ClassNameSize, @ClassNameSize) do
begin
if GetLastError = ERROR_INSUFFICIENT_BUFFER then
begin
if ClassName <> nil then FreeMem(ClassName);
GetMem(ClassName, ClassNameSize);
end else Break;
end;
Result := ClassName;
if ClassName <> nil then FreeMem(ClassName);
end;
// 程式結束時釋放圖示清單
procedure TForm1.FormDestroy(Sender: TObject);
begin
SetupDiDestroyClassImageList(@ClassImageListData);
end;
end.
以下函數示範如何針對指定裝置類別 (GUID) 啟用或停用裝置,支援軟碟機、光碟機、USB 裝置三大類。
function ChangeDeviceState(hDevInfo: HDEVINFO; NewStatus: DWORD;
SelectedItem: DWORD): Boolean;
var
PropChangeParams: SP_PROPCHANGE_PARAMS;
DeviceInfoData: SP_DEVINFO_DATA;
begin
Result := False;
DeviceInfoData.cbSize := SizeOf(SP_DEVINFO_DATA);
if not SetupDiEnumDeviceInfo(hDevInfo, SelectedItem, @DeviceInfoData) then Exit;
PropChangeParams.ClassInstallHeader.cbSize := SizeOf(SP_CLASSINSTALL_HEADER);
PropChangeParams.ClassInstallHeader.InstallFunction := DIF_PROPERTYCHANGE;
PropChangeParams.Scope := DICS_FLAG_GLOBAL;
PropChangeParams.StateChange := NewStatus;
if not SetupDiSetClassInstallParams(hDevInfo, @DeviceInfoData,
PSP_CLASSINSTALL_HEADER(@PropChangeParams), SizeOf(PropChangeParams)) then Exit;
if not SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, @DeviceInfoData) then Exit;
Result := True;
end;
function ControlDisk(nStatus, nIndex: Integer): Boolean;
var
Guid: TGUID;
GUIDString: string;
hDevInfo: Cardinal;
i: DWORD;
DeviceInfoData: SP_DEVINFO_DATA;
begin
Result := False;
if nStatus = -1 then Exit;
ZeroMemory(@Guid, SizeOf(TGUID));
case nIndex of
0: GUIDString := '{4D36E980-E325-11CE-BFC1-08002BE10318}'; // 軟碟機
1: GUIDString := '{4D36E965-E325-11CE-BFC1-08002BE10318}'; // 光碟機
2: GUIDString := '{36FC9E60-C465-11CF-8056-444553540000}'; // USB
end;
Guid := StringToGUID(GUIDString);
hDevInfo := SetupDiGetClassDevs(@Guid, nil, HWND(nil), DIGCF_PRESENT);
if hDevInfo = INVALID_HANDLE_VALUE then Exit;
ZeroMemory(@DeviceInfoData, SizeOf(SP_DEVINFO_DATA));
DeviceInfoData.cbSize := SizeOf(SP_DEVINFO_DATA);
i := 0;
while SetupDiEnumDeviceInfo(hDevInfo, i, @DeviceInfoData) do
begin
if nStatus = 1 then
Result := ChangeDeviceState(hDevInfo, DICS_ENABLE, i)
else if nStatus = 2 then
Result := ChangeDeviceState(hDevInfo, DICS_DISABLE, i);
Inc(i);
end;
SetupDiDestroyDeviceInfoList(hDevInfo);
end;
// 停用光碟機
if ControlDisk(DICS_DISABLE, 1) then ShowMessage('成功停用!');
// 啟用光碟機
if ControlDisk(DICS_ENABLE, 1) then ShowMessage('啟用成功!');
// 注意:USB 裝置需呼叫兩次 ControlDisk(DICS_ENABLE, 2) 才能完成啟用
SetupAPI.pas 至 uses 子句。