Microsoft MVP성태의 닷넷 이야기
닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI) [링크 복사], [링크+제목 복사],
조회: 1081
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

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에 따른 하위 디렉터리가 있고,

pci_enum_1.png

그것의 속성으로 제법 많은 정보를 제공하고 있습니다. 이후의 코딩은 단순히 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는 그만큼 정형화는 됐겠지만 사용하기에 너무 번거롭습니다.

그냥 쓰고 싶은 것을 쓰면 되는 걸로! ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/25/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13883정성태2/12/2025189닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13882정성태2/10/2025579스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/2025889닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/2025868오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20251107오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/2025987오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20251081닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20251274닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20251264스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20251211스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20251106디버깅 기술: 217. WinDbg - PCI 장치 열거
13872정성태1/23/20251089오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20251201Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20251274개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20251329개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20251232Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20251396오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20251359개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20251476오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20251427Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
13863정성태1/14/20251320Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/20251282Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/20251792Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/20251740디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/20251850디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...