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

(시리즈 글이 4개 있습니다.)
디버깅 기술: 217. WinDbg - PCI 장치 열거
; https://www.sysnet.pe.kr/2/0/13873

닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)
; https://www.sysnet.pe.kr/2/0/13877

닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람
; https://www.sysnet.pe.kr/2/0/13881

닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람
; https://www.sysnet.pe.kr/2/0/13883




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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  [54]  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12591정성태4/9/202120948.NET Framework: 1034. C# - byte 배열을 Hex(16진수) 문자열로 고속 변환하는 방법 [2]파일 다운로드1
12590정성태4/9/202117117.NET Framework: 1033. C# - .NET 4.0 이하에서 Console.IsInputRedirected 구현 [1]
12589정성태4/8/202118278.NET Framework: 1032. C# - Environment.OSVersion의 문제점 및 윈도우 운영체제의 버전을 구하는 다양한 방법 [1]
12588정성태4/7/202120042개발 환경 구성: 565. PowerShell - New-SelfSignedCertificate를 사용해 CA 인증서 생성 및 인증서 서명 방법
12587정성태4/6/202121314개발 환경 구성: 564. Windows 10 - ClickOnce 배포처럼 사용할 수 있는 MSIX 설치 파일 [1]
12586정성태4/5/202118248오류 유형: 710. Windows - Restart-Computer / shutdown 명령어 수행 시 Access is denied(E_ACCESSDENIED)
12585정성태4/5/202117183개발 환경 구성: 563. 기본 생성된 kubeconfig 파일의 내용을 새롭게 생성한 인증서로 구성하는 방법
12584정성태4/1/202118349개발 환경 구성: 562. kubeconfig 파일 없이 kubectl 옵션만으로 실행하는 방법
12583정성태3/29/202119205개발 환경 구성: 561. kubectl 수행 시 다른 k8s 클러스터로 접속하는 방법
12582정성태3/29/202118684오류 유형: 709. Visual C++ - 컴파일 에러 error C2059: syntax error: '__stdcall'
12581정성태3/28/202118630.NET Framework: 1031. WinForm/WPF에서 Console 창을 띄워 출력하는 방법 (2) - Output 디버깅 출력을 AllocConsole로 우회 [2]
12580정성태3/28/202116531오류 유형: 708. SQL Server Management Studio - Execution Timeout Expired.
12579정성태3/28/202117135오류 유형: 707. 중첩 가상화(Nested Virtualization) - The virtual machine could not be started because this platform does not support nested virtualization.
12578정성태3/27/202117529개발 환경 구성: 560. Docker Desktop for Windows 기반의 Kubernetes 구성 (2) - WSL 2 인스턴스에 kind가 구성한 k8s 서비스 위치
12577정성태3/26/202119107개발 환경 구성: 559. Docker Desktop for Windows 기반의 Kubernetes 구성 - WSL 2 인스턴스에 kind 도구로 k8s 클러스터 구성
12576정성태3/25/202117209개발 환경 구성: 558. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 (2) - k8s 서비스 위치
12575정성태3/24/202115814개발 환경 구성: 557. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 [1]
12574정성태3/23/202121344.NET Framework: 1030. C# Socket의 Close/Shutdown 동작 (동기 모드)
12573정성태3/22/202118742개발 환경 구성: 556. WSL 인스턴스 초기 설정 명령어 [1]
12572정성태3/22/202117970.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법파일 다운로드1
12571정성태3/21/202115992오류 유형: 706. WSL 2 기반으로 "Enable Kubernetes" 활성화 시 초기화 실패 [1]
12570정성태3/19/202121392개발 환경 구성: 555. openssl - CA로부터 인증받은 새로운 인증서를 생성하는 방법
12569정성태3/18/202121684개발 환경 구성: 554. WSL 인스턴스 export/import 방법 및 단축 아이콘 설정 방법
12568정성태3/18/202114995오류 유형: 705. C# 빌드 - Couldn't process file ... due to its being in the Internet or Restricted zone or having the mark of the web on the file.
12567정성태3/17/202117161개발 환경 구성: 553. Docker Desktop for Windows를 위한 k8s 대시보드 활성화 [1]
12566정성태3/17/202117006개발 환경 구성: 552. Kubernetes - kube-apiserver와 REST API 통신하는 방법 (Docker Desktop for Windows 환경)
... 46  47  48  49  50  51  52  53  [54]  55  56  57  58  59  60  ...