Microsoft MVP성태의 닷넷 이야기
닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI) [링크 복사], [링크+제목 복사],
조회: 3370
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  98  99  [100]  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11433정성태1/15/201820764개발 환경 구성: 349. dotnet ef 명령어 사용을 위한 준비
11432정성태1/11/201826513.NET Framework: 726. WPF + Direct2D + SharpDX 출력 C# 예제파일 다운로드2
11431정성태1/11/201824405.NET Framework: 725. C# - 동기 방식이면서 비동기 메서드(awaitable)처럼 구현한 사례 [9]
11430정성태1/10/201827870.NET Framework: 724. WPF + Direct2D 출력 C# 예제 [2]파일 다운로드1
11429정성태1/9/201818618개발 환경 구성: 348. ASP.NET Core 2.1 Preview 버전 적용 방법
11428정성태1/6/201821364개발 환경 구성: 347. WinForm 프로젝트를 WPF 프로젝트 유형으로 변경하는 방법파일 다운로드1
11427정성태1/5/201819376오류 유형: 445. vcpkg 빌드 오류 - Starting the CLR failed with HRESULT 80040153
11426정성태1/5/201829027오류 유형: 444. curl로 호출할 때 발생하는 오류 정리
11425정성태1/4/201819708개발 환경 구성: 346. ASP.NET Core Web Application을 IIS에서 호스팅하는 방법 (2)
11424정성태1/4/201819278개발 환경 구성: 345. ASP.NET Core 프로젝트를 명령행에서 빌드하는 방법
11423정성태1/3/201837480VC++: 123. 내가 만든 코드보다 OpenCV의 속도가 월등히 빠른 이유 [8]파일 다운로드2
11422정성태1/2/201827981.NET Framework: 723. C# - OpenCvSharp 사용 시 C/C++을 이용한 속도 향상 (for 루프 연산) [4]파일 다운로드1
11421정성태1/2/201819857오류 유형: 443. Visual Studio - nuget configuration is invalid
11420정성태12/30/201724077.NET Framework: 722. C# - Windows 10 운영체제의 데스크톱 앱에서 음성인식(SpeechRecognizer) 사용하는 방법 [3]파일 다운로드1
11419정성태12/23/201726188.NET Framework: 721. WebClient 타입의 ...Async 메서드 호출은 왜 await + 동기 호출 시 hang 현상이 발생할까요? [2]파일 다운로드1
11418정성태12/23/201735948.NET Framework: 720. 비동기 메서드 내에서 await 시 ConfigureAwait 호출 의미 [2]파일 다운로드1
11417정성태12/22/201721807.NET Framework: 719. Task를 포함하는 async 메서드의 동작 방식 [2]
11416정성태12/21/201719415.NET Framework: 718. AsyncTaskMethodBuilder.Create() 메서드 동작 방식 [2]
11415정성태12/21/201721105.NET Framework: 717. Task를 포함하지 않는 async 메서드의 동작 방식 [6]
11414정성태12/21/201728272.NET Framework: 716. async 메서드의 void 반환 타입 사용에 대하여파일 다운로드2
11413정성태12/20/201722529개발 환경 구성: 344. 윈도우 10 - TTS 및 음성 인식을 위한 환경 설정
11412정성태12/20/201725214.NET Framework: 715. C# - Windows 10 운영체제의 데스크톱 앱에서 TTS(SpeechSynthesizer) 사용하는 방법 [1]파일 다운로드1
11411정성태12/20/201723451사물인터넷: 15. 라즈베리 파이용 C++ 프로젝트에 SSL Socket 적용
11410정성태12/20/201735762.NET Framework: 714. SSL Socket 예제 - C/C++ 서버, C# 클라이언트 [1]파일 다운로드1
11409정성태12/18/201741827VC++: 122. 오픈 소스 라이브러리를 쉽게 빌드해 주는 "C++ Package Manager for Windows: vcpkg" [7]
11408정성태12/18/201721419.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)파일 다운로드1
... 91  92  93  94  95  96  97  98  99  [100]  101  102  103  104  105  ...