C# - PCI 장치 열거 (레지스트리, SetupAPI)
지난 글에서 WinDbg로 PCI 장치를 열거해 봤는데요,
WinDbg - PCI 장치 열거
; https://www.sysnet.pe.kr/2/0/13873
(커널이 아닌) 사용자 모드에서 코드로 열거하는 방법을 검색해 보면
레지스트리를 이용하면 된다고 합니다.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\PCI
해당 경로를 가보면 이런 식으로 Vendor ID, Device ID에 따른 하위 디렉터리가 있고,
그것의 속성으로 제법 많은 정보를 제공하고 있습니다. 이후의 코딩은 단순히
RegistryKey 클래스를 이용하는 것에 불과하므로 예제는 생략합니다. ^^
또 다른 방법으로는 SetupAPI를 이용하는 방법이 있는데요,
SetupAPI
; https://learn.microsoft.com/en-us/windows-hardware/drivers/install/setupapi
대충 웹상의 자료를 엮어서 C#으로 코딩해 보면 이런 식으로 됩니다. (
첨부 파일은 이 글의 예제 코드를 포함합니다.)
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1;
internal class Program
{
[DllImport("SetupAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiGetDeviceProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY Property, ref DevPropType PropertyRegDataType, byte[] PropertyBuffer, uint PropertyBufferSize, ref uint RequiredSize, uint Flags);
[DllImport("SetupAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("SetupAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, string Enumerator, IntPtr hwndParent, DIGCF Flags);
[DllImport("SetupAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
static void Main(string[] args)
{
Guid emptyGuid = Guid.Empty;
IntPtr hDevInfo = SetupDiGetClassDevs(ref emptyGuid, "PCI", IntPtr.Zero, DIGCF.ALLCLASSES | DIGCF.PRESENT);
if (hDevInfo == IntPtr.Zero)
{
Console.WriteLine("SetupDiGetClassDevs failed");
return;
}
uint deviceIndex = 0;
SP_DEVINFO_DATA devInfoData = new SP_DEVINFO_DATA();
devInfoData.cbSize = (uint)Marshal.SizeOf(devInfoData);
while (SetupDiEnumDeviceInfo(hDevInfo, deviceIndex, ref devInfoData))
{
deviceIndex++;
Console.WriteLine($"[Device #{deviceIndex}]");
Console.WriteLine($"\tClassGuid: {devInfoData.ClassGuid}");
uint busNumber = GetDeviceUint32Property(hDevInfo, ref devInfoData, ref DEVPROPKEY.BusNumber);
Console.WriteLine($"\tBusNumber: {(busNumber == uint.MaxValue ? "(invalid)" : busNumber)}");
string busDeviceDesc = GetDeviceStringProperty(hDevInfo, ref devInfoData, ref DEVPROPKEY.BusReportedDeviceDesc);
Console.WriteLine($"\tBusReportedDeviceDesc: {busDeviceDesc}");
string deviceDesc = GetDeviceStringProperty(hDevInfo, ref devInfoData, ref DEVPROPKEY.DeviceDesc);
Console.WriteLine($"\tDeviceDesc: {deviceDesc}");
}
Console.WriteLine();
Console.WriteLine("Total # of devices: " + deviceIndex);
SetupDiDestroyDeviceInfoList(hDevInfo);
}
static uint GetDeviceUint32Property(IntPtr hDevInfo, ref SP_DEVINFO_DATA devInfoData, ref DEVPROPKEY propertyKey)
{
DevPropType propertyDataType = 0;
byte[] buffer = new byte[4];
uint requiredSize = 0;
if (SetupDiGetDeviceProperty(hDevInfo, ref devInfoData, ref propertyKey, ref propertyDataType,
buffer, (uint)buffer.Length, ref requiredSize, 0) == false
|| propertyDataType != DevPropType.UINT32 || buffer.Length != requiredSize)
{
return UInt32.MaxValue;
}
return BitConverter.ToUInt32(buffer, 0);
}
static string GetDeviceStringProperty(IntPtr hDevInfo, ref SP_DEVINFO_DATA devInfoData, ref DEVPROPKEY propertyKey)
{
DevPropType propertyDataType = 0;
byte[] buffer = new byte[1024];
uint requiredSize = 0;
if (SetupDiGetDeviceProperty(hDevInfo, ref devInfoData, ref propertyKey, ref propertyDataType,
buffer, (uint)buffer.Length, ref requiredSize, 0) == false
|| propertyDataType != DevPropType.STRING || buffer.Length < requiredSize)
{
return "";
}
return Encoding.Unicode.GetString(buffer, 0, (int)requiredSize);
}
}
public struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
public struct DEVPROPKEY
{
public static DEVPROPKEY BusReportedDeviceDesc = new DEVPROPKEY { FmtId = new Guid("{540B947E-8B40-45BC-A8A2-6A0B894CBDA2}"), PropId = 0x04 };
public static DEVPROPKEY BusNumber = new DEVPROPKEY { FmtId = new Guid("{A45C254E-DF1C-4EFD-8020-67D146A850E0}"), PropId = 0x17 };
public static DEVPROPKEY DeviceDesc = new DEVPROPKEY { FmtId = new Guid("{A45C254E-DF1C-4EFD-8020-67D146A850E0}"), PropId = 2 };
public Guid FmtId;
public uint PropId;
}
public enum DevPropType : uint
{
EMPTY = 0x00000000, // nothing, no property data
NULL = 0x00000001, // null property data
SBYTE = 0x00000002, // 8-bit signed int (SBYTE)
BYTE = 0x00000003, // 8-bit unsigned int (BYTE)
INT16 = 0x00000004, // 16-bit signed int (SHORT)
UINT16 = 0x00000005, // 16-bit unsigned int (USHORT)
INT32 = 0x00000006, // 32-bit signed int (LONG)
UINT32 = 0x00000007, // 32-bit unsigned int (ULONG)
INT64 = 0x00000008, // 64-bit signed int (LONG64)
UINT64 = 0x00000009, // 64-bit unsigned int (ULONG64)
FLOAT = 0x0000000A, // 32-bit floating-point (FLOAT)
DOUBLE = 0x0000000B, // 64-bit floating-point (DOUBLE)
DECIMAL = 0x0000000C, // 128-bit data (DECIMAL)
GUID = 0x0000000D, // 128-bit unique identifier (GUID)
CURRENCY = 0x0000000E, // 64 bit signed int currency value (CURRENCY)
DATE = 0x0000000F, // date (DATE)
FILETIME = 0x00000010, // file time (FILETIME)
BOOLEAN = 0x00000011, // 8-bit boolean (DEVPROP_BOOLEAN)
STRING = 0x00000012, // null-terminated string
STRING_LIST = (STRING | MOD_LIST), // multi-sz string list
SECURITY_DESCRIPTOR = 0x00000013, // self-relative binary SECURITY_DESCRIPTOR
SECURITY_DESCRIPTOR_STRING = 0x00000014, // security descriptor string (SDDL format)
DEVPROPKEY = 0x00000015, // device property key (DEVPROPKEY)
DEVPROPTYPE = 0x00000016, // device property type (DEVPROPTYPE)
BINARY = (BYTE | MOD_ARRAY), // custom binary data
ERROR = 0x00000017, // 32-bit Win32 system error code
NTSTATUS = 0x00000018, // 32-bit NTSTATUS code
STRING_INDIRECT = 0x00000019, // string resource (@[path\]<dllname>,-<strId>)
MOD_ARRAY = 0x00001000,
MOD_LIST = 0x00002000,
}
[Flags]
public enum DIGCF
{
ALLCLASSES = 0x04,
PRESENT = 0x02,
}
위의 프로그램을 Hyper-V Generation 1 VM에서 실행하면 이런 식으로 출력이 나오고,
c:\temp> ConsoleApp1.exe
[Device #1]
ClassGuid: 4d36e96a-e325-11ce-bfc1-08002be10318
BusNumber: 0
BusReportedDeviceDesc: IDE Controller
DeviceDesc: Intel(R) 82371AB/EB PCI Bus Master IDE Controller
[Device #2]
ClassGuid: 4d36e97d-e325-11ce-bfc1-08002be10318
BusNumber: 0
BusReportedDeviceDesc: PCI to ISA Bridge
DeviceDesc: PCI to ISA Bridge
[Device #3]
ClassGuid: 4d36e97d-e325-11ce-bfc1-08002be10318
BusNumber: 0
BusReportedDeviceDesc: Video Controller (VGA Compatible)
DeviceDesc: Microsoft Hyper-V S3 Cap
[Device #4]
ClassGuid: 4d36e97d-e325-11ce-bfc1-08002be10318
BusNumber: 0
BusReportedDeviceDesc: PCI HOST Bridge
DeviceDesc: CPU to PCI Bridge
Total # of devices: 4
WinDbg의 !pci 명령어와 비교해 보면,
3: kd> !pcitree
Bus 0x0 (FDO Ext ffff9c0983857a60)
(d=0, f=0) 80867192 devext 0xffff9c09837c94b0 devstack 0xffff9c09837c9360 0600 Bridge/HOST to PCI
(d=7, f=0) 80867110 devext 0xffff9c09837cb4b0 devstack 0xffff9c09837cb360 0601 Bridge/PCI to ISA
(d=7, f=1) 80867111 devext 0xffff9c09837cd4b0 devstack 0xffff9c09837cd360 0101 Mass Storage Controller/IDE
(d=8, f=0) 14145353 devext 0xffff9c09837d14b0 devstack 0xffff9c09837d1360 0300 Display Controller/VGA
Total PCI Root busses processed = 1
Total PCI Segments processed = 1
그런대로 잘 맞는 것 같습니다. ^^
1) 레지스트리로부터 정보를 가져오는 것과 2) SetupAPI를 사용하는 것 중에서 딱히 뭐가 더 좋다고 할 수는 없습니다. 레지스트리는 그만큼 간편하지만 덜 정형화된 것 같고, SetupAPI는 그만큼 정형화는 됐겠지만 사용하기에 너무 번거롭습니다.
그냥 쓰고 싶은 것을 쓰면 되는 걸로! ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]