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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  [26]  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13287정성태3/16/202311600Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법파일 다운로드1
13286정성태3/15/202312023Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
13285정성태3/14/202311407Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법 [2]파일 다운로드1
13284정성태3/13/202311279Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법파일 다운로드1
13283정성태3/12/202310105오류 유형: 852. 파이썬 - TypeError: coercing to Unicode: need string or buffer, NoneType found
13282정성태3/12/202310496Linux: 58. WSL - nohup 옵션이 필요한 경우
13281정성태3/12/202311142Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/202312627개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/202312087오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/202311889개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/202313169개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/202312761.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어 [1]
13275정성태3/3/202312674.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/202311948.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/202310968.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/202311839오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/202311659오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
13270정성태2/24/202310934.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/202312318스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/202312754개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/202313091.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/202312395오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/202312805.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/202314777스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/202312083개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/202313474디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
... 16  17  18  19  20  21  22  23  24  25  [26]  27  28  29  30  ...