首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi 透過 SetupAPI.dll 列舉與停用硬體裝置完整實作

     編輯:Delphi

前言

本文介紹如何在 Delphi 中透過 Windows SetupAPI.dll 列舉系統所有硬體裝置,並展示裝置類別名稱與圖示,更進一步示範如何程式化啟用或停用特定裝置(如軟碟機、光碟機、USB 裝置)。原始碼參考 MSDN 範例並加以整理擴充,提供完整可編譯的單元與表單程式碼。

核心單元:SetupAPI.pas

此單元封裝了 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.

主表單實作:untMain.pas

表單包含 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 子句。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved