Microsoft MVP성태의 닷넷 이야기
닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI) [링크 복사], [링크+제목 복사],
조회: 3431
글쓴 사람
정성태 (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)
11300정성태9/10/201721184.NET Framework: 681. dotnet.exe - run, exec, build, restore, publish 차이점 [3]
11299정성태9/9/201719855개발 환경 구성: 330. Hyper-V VM의 Internal Network를 Private 유형으로 만드는 방법
11298정성태9/8/201723159VC++: 119. EnumProcesses / EnumProcessModules API 사용 시 주의점 [1]
11297정성태9/8/201719833디버깅 기술: 96. windbg - 풀 덤프에 포함된 모든 닷넷 모듈을 파일로 저장하는 방법
11296정성태9/8/201722938웹: 36. Edge - "이 웹 사이트는 이전 기술에서 실행되며 Internet Explorer에서만 작동합니다." 끄는 방법
11295정성태9/7/201720431디버깅 기술: 95. Windbg - .foreach 사용법
11294정성태9/4/201720093개발 환경 구성: 329. 마이크로소프트의 CoreCLR 프로파일러 예제 빌드 방법 [1]
11293정성태9/4/201720703개발 환경 구성: 328. Visual Studio(devenv.exe)를 배치 파일(.bat)을 통해 실행하는 방법
11292정성태9/4/201718888오류 유형: 419. Cannot connect to WMI provider - Invalid class [0x80041010]
11291정성태9/3/201720712개발 환경 구성: 327. 아파치 서버 2.4를 위한 mod_aspdotnet 마이그레이션
11290정성태9/3/201723973개발 환경 구성: 326. 아파치 서버에서 ASP.NET을 실행하는 mod_aspdotnet 모듈 [2]
11289정성태9/3/201721630개발 환경 구성: 325. GAC에 어셈블리 등록을 위해 gacutil.exe을 사용하는 경우 주의 사항
11288정성태9/3/201718437개발 환경 구성: 324. 윈도우용 XAMPP의 아파치 서버 구성 방법
11287정성태9/1/201727596.NET Framework: 680. C# - 작업자(Worker) 스레드와 UI 스레드 [11]
11286정성태8/28/201715016기타: 67. App Privacy Policy
11285정성태8/28/201723517.NET Framework: 679. C# - 개인 키 보안의 SFTP를 이용한 파일 업로드파일 다운로드1
11284정성태8/27/201721540.NET Framework: 678. 데스크톱 윈도우 응용 프로그램에서 UWP 라이브러리를 이용한 비디오 장치 열람하는 방법 [1]파일 다운로드1
11283정성태8/27/201717313오류 유형: 418. CSS3117: @font-face failed cross-origin request. Resource access is restricted.
11282정성태8/26/201719759Math: 22. 행렬로 바라보는 피보나치 수열
11281정성태8/26/201721596.NET Framework: 677. Visual Studio 2017 - NuGet 패키지를 직접 참조하는 PackageReference 지원 [2]
11280정성태8/24/201718591디버깅 기술: 94. windbg - 풀 덤프에 포함된 모든 모듈을 파일로 저장하는 방법
11279정성태8/23/201730186.NET Framework: 676. C# Thread가 Running 상태인지 아는 방법
11278정성태8/23/201718435오류 유형: 417. TFS - Warning - Unable to refresh ... because you have a pending edit. [1]
11277정성태8/23/201719674오류 유형: 416. msbuild - error MSB4062: The "TransformXml" task could not be loaded from the assembly
11276정성태8/23/201723921.NET Framework: 675. C# - (파일) 확장자와 연결된 실행 파일 경로 찾기 [2]파일 다운로드1
11275정성태8/23/201732951개발 환경 구성: 323. Visual Studio 설치 없이 빌드 환경 구성 - Visual Studio 2017용 Build Tools [1]
... 91  92  93  94  95  96  97  98  99  100  101  102  103  104  [105]  ...