YOGYUI

MFC::SetupAPI - 장치관리자(Device Manager) 정보 얻기 본문

Software/C, C++

MFC::SetupAPI - 장치관리자(Device Manager) 정보 얻기

요겨 2021. 6. 24. 13:42
반응형

윈도 OS가 설치된 PC에서 소프트웨어 구동 시 특정 장치의 드라이버가 설치되어있는지 여부를 확인해야 한다는 고객 요구사항이 있어 이리저리 구글링해본뒤 직접 구현해보았다

그 중 개발자들에게 도움이 될만한 기본적인 내용을 간단히 적어본다

 

MFC의 Win32 API 중 SetupAPI를 활용하면 제어판의 장치 관리자에서 접근 가능한 거의 모든 정보를 열람할 수 있다

https://docs.microsoft.com/en-us/windows/win32/api/setupapi/

 

Setupapi.h header - Win32 apps

01/11/2019 38 minutes to read In this article --> This header is used by Application Installation and Servicing. For more information, see: setupapi.h contains the following programming interfaces: Functions   InstallHinfSectionA InstallHinfSection is an

docs.microsoft.com

 

API 문서를 참고해서 다음과 같이 구현해봤다 (GitHub 링크)

※ 유니코드 문자집합 사용

 

외부종속성은 setupapi.lib 하나만 링크해주면 된다

#define _AFXDLL
#include <iostream>
#include <afxwin.h>
#include <SetupAPI.h>
#include <locale.h>

#pragma comment(lib, "setupapi.lib")

void test() {
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA stDevInfoData = SP_DEVINFO_DATA();

    hDevInfo = SetupDiGetClassDevs(
        0L, 
        0L, 
        0L, 
        DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_PROFILE
    );

    if (hDevInfo == INVALID_HANDLE_VALUE)
        return;

    stDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &stDevInfoData); i++) {
        TCHAR szInstanceId       [MAX_PATH] = { 0 };
        TCHAR szClassName        [MAX_PATH] = { 0 };
        TCHAR szFriendlyName     [MAX_PATH] = { 0 };
        TCHAR szClassDescription [MAX_PATH] = { 0 };
        TCHAR szDeviceDescription[MAX_PATH] = { 0 };

        // Get Device Instance ID
        BOOL bResult = SetupDiGetDeviceInstanceId(
            hDevInfo,
            &stDevInfoData,
            szInstanceId,
            _countof(szInstanceId),
            0
        );
        
        if (!bResult) {
            _tprintf(_T("Failed to get device instance ID\n"));
            continue;
        }

        (VOID)SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &stDevInfoData,
            SPDRP_CLASS,
            0,
            (PBYTE)szClassName,
            _countof(szClassName),
            0
        );

        (VOID)SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &stDevInfoData,
            SPDRP_DEVICEDESC,
            0,
            (PBYTE)szDeviceDescription,
            _countof(szDeviceDescription),
            0
        );

        (VOID)SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &stDevInfoData,
            SPDRP_FRIENDLYNAME,
            0,
            (PBYTE)szFriendlyName,
            _countof(szFriendlyName),
            0
        );

        (VOID)SetupDiGetClassDescription(
            &stDevInfoData.ClassGuid,
            szClassDescription,
            _countof(szClassDescription),
            0
        );

        _tprintf(_T("[%d]\n"), i);
        _tprintf(_T("-- Class: %s\n"), szClassName);
        _tprintf(_T("-- Friendly Name: %s\n"), szFriendlyName);
        _tprintf(_T("-- Instance ID: %s\n"), szInstanceId);
        _tprintf(_T("-- Class Description: %s\n"), szClassDescription);
        _tprintf(_T("-- Device Description: %s\n"), szDeviceDescription);
        _tprintf(_T("\n"));
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
}

int main() {
    setlocale(LC_ALL, "korean");
    _wsetlocale(LC_ALL, L"korean");

    test();
}

 

실행해보면 다음과 같이 콘솔창에 디바이스의 정보가 출력된다

[0]
-- Class: System
-- Friendly Name:
-- Instance ID: PCI\VEN_8086&DEV_6F90&SUBSYS_6F908086&REV_01\3&103A9D54&0&48
-- Class Description: 시스템 장치
-- Device Description: Intel(R) Xeon(R) E7 v4/Xeon(R) E5 v4/Xeon(R) E3 v4/Xeon(R) D QPI Link 1 - 6F90

[1]
-- Class: System
-- Friendly Name:
-- Instance ID: PCI\VEN_8086&DEV_6FD1&SUBSYS_6FD18086&REV_01\3&103A9D54&0&B9
-- Class Description: 시스템 장치
-- Device Description: Intel(R) Xeon(R) E7 v4/Xeon(R) E5 v4/Xeon(R) E3 v4/Xeon(R) D Memory Controller 1 - Channel 1 Thermal Control - 6FD1

[2]
-- Class: System
-- Friendly Name:
-- Instance ID: PCI\VEN_8086&DEV_6F68&SUBSYS_6F688086&REV_01\3&103A9D54&0&B0
-- Class Description: 시스템 장치
-- Device Description: Intel(R) Xeon(R) E7 v4/Xeon(R) E5 v4/Xeon(R) E3 v4/Xeon(R) D Target Address/Thermal/RAS - 6F68

[3]
-- Class: System
-- Friendly Name:
-- Instance ID: PCI\VEN_8086&DEV_6FBA&SUBSYS_00000000&REV_01\3&1C6B4348&0&BE
-- Class Description: 시스템 장치
-- Device Description: Intel(R) Xeon(R) E7 v4/Xeon(R) E5 v4/Xeon(R) E3 v4/Xeon(R) D DDRIO Channel 2/3 Interface - 6FBA

[4]
-- Class: VolumeSnapshot
-- Friendly Name:
-- Instance ID: STORAGE\VOLUMESNAPSHOT\HARDDISKVOLUMESNAPSHOT100
-- Class Description: 저장소 볼륨 섀도 복사본
-- Device Description: 일반 볼륨 섀도 복사본

(... 후략 ...)

 

디바이스 하나 골라서 제어판 장치관리자의 정보들과 비교해보자

[870]
-- Class: USB
-- Friendly Name:
-- Instance ID: USB\VID_0403&PID_601F&MI_00\6&1F7B2494&0&0000
-- Class Description: 범용 직렬 버스 컨트롤러
-- Device Description: FTDI FT601 USB 3.0 Bridge Device

 

SetupDiGetDeviceRegistryProperty 함수의 세번째 인자 (DWORD Property) 값을 바꿔가면서 다양한 정보를 얻을 수 있다

https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertyw

 

SetupDiGetDeviceRegistryPropertyW function (setupapi.h) - Win32 apps

The SetupDiGetDeviceRegistryProperty function retrieves a specified Plug and Play device property.

docs.microsoft.com

// SetupAPI.h
//
// Device registry property codes
// (Codes marked as read-only (R) may only be used for
// SetupDiGetDeviceRegistryProperty)
//
// These values should cover the same set of registry properties
// as defined by the CM_DRP codes in cfgmgr32.h.
//
// Note that SPDRP codes are zero based while CM_DRP codes are one based!
//
#define SPDRP_DEVICEDESC                  (0x00000000)  // DeviceDesc (R/W)
#define SPDRP_HARDWAREID                  (0x00000001)  // HardwareID (R/W)
#define SPDRP_COMPATIBLEIDS               (0x00000002)  // CompatibleIDs (R/W)
#define SPDRP_UNUSED0                     (0x00000003)  // unused
#define SPDRP_SERVICE                     (0x00000004)  // Service (R/W)
#define SPDRP_UNUSED1                     (0x00000005)  // unused
#define SPDRP_UNUSED2                     (0x00000006)  // unused
#define SPDRP_CLASS                       (0x00000007)  // Class (R--tied to ClassGUID)
#define SPDRP_CLASSGUID                   (0x00000008)  // ClassGUID (R/W)
#define SPDRP_DRIVER                      (0x00000009)  // Driver (R/W)
#define SPDRP_CONFIGFLAGS                 (0x0000000A)  // ConfigFlags (R/W)
#define SPDRP_MFG                         (0x0000000B)  // Mfg (R/W)
#define SPDRP_FRIENDLYNAME                (0x0000000C)  // FriendlyName (R/W)
#define SPDRP_LOCATION_INFORMATION        (0x0000000D)  // LocationInformation (R/W)
#define SPDRP_PHYSICAL_DEVICE_OBJECT_NAME (0x0000000E)  // PhysicalDeviceObjectName (R)
#define SPDRP_CAPABILITIES                (0x0000000F)  // Capabilities (R)
#define SPDRP_UI_NUMBER                   (0x00000010)  // UiNumber (R)
#define SPDRP_UPPERFILTERS                (0x00000011)  // UpperFilters (R/W)
#define SPDRP_LOWERFILTERS                (0x00000012)  // LowerFilters (R/W)
#define SPDRP_BUSTYPEGUID                 (0x00000013)  // BusTypeGUID (R)
#define SPDRP_LEGACYBUSTYPE               (0x00000014)  // LegacyBusType (R)
#define SPDRP_BUSNUMBER                   (0x00000015)  // BusNumber (R)
#define SPDRP_ENUMERATOR_NAME             (0x00000016)  // Enumerator Name (R)
#define SPDRP_SECURITY                    (0x00000017)  // Security (R/W, binary form)
#define SPDRP_SECURITY_SDS                (0x00000018)  // Security (W, SDS form)
#define SPDRP_DEVTYPE                     (0x00000019)  // Device Type (R/W)
#define SPDRP_EXCLUSIVE                   (0x0000001A)  // Device is exclusive-access (R/W)
#define SPDRP_CHARACTERISTICS             (0x0000001B)  // Device Characteristics (R/W)
#define SPDRP_ADDRESS                     (0x0000001C)  // Device Address (R)
#define SPDRP_UI_NUMBER_DESC_FORMAT       (0X0000001D)  // UiNumberDescFormat (R/W)
#define SPDRP_DEVICE_POWER_DATA           (0x0000001E)  // Device Power Data (R)
#define SPDRP_REMOVAL_POLICY              (0x0000001F)  // Removal Policy (R)
#define SPDRP_REMOVAL_POLICY_HW_DEFAULT   (0x00000020)  // Hardware Removal Policy (R)
#define SPDRP_REMOVAL_POLICY_OVERRIDE     (0x00000021)  // Removal Policy Override (RW)
#define SPDRP_INSTALL_STATE               (0x00000022)  // Device Install State (R)
#define SPDRP_LOCATION_PATHS              (0x00000023)  // Device Location Paths (R)
#define SPDRP_BASE_CONTAINERID            (0x00000024)  // Base ContainerID (R)

#define SPDRP_MAXIMUM_PROPERTY            (0x00000025)  // Upper bound on ordinals

장치 설명 (Device Description) 문자열을 기반으로 존재하는지 여부는 다음과 같이 구현하면 간편하다

#include <vector>

void test2(std::vector<CString>& avecDesc)
{
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA stDevInfoData = SP_DEVINFO_DATA();

    hDevInfo = SetupDiGetClassDevs(
        0L,
        0L,
        0L,
        DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_PROFILE
    );

    if (hDevInfo == INVALID_HANDLE_VALUE)
        return;

    stDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &stDevInfoData); i++) {
        TCHAR szDeviceDescription[MAX_PATH] = { 0 };

        (VOID)SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &stDevInfoData,
            SPDRP_DEVICEDESC,
            0,
            (PBYTE)szDeviceDescription,
            _countof(szDeviceDescription),
            0
        );

        // _tprintf(_T("%s\n"), szDeviceDescription);
        avecDesc.push_back(szDeviceDescription);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
}

int main()
{
    LARGE_INTEGER	liStart;
    LARGE_INTEGER	liEnd;
    LARGE_INTEGER	liFreq;

    QueryPerformanceFrequency(&liFreq);
    
    QueryPerformanceCounter(&liStart);
    std::vector<CString> vecDev;
    test2(vecDev);
    
    QueryPerformanceCounter(&liEnd);
    double fTimeElapsedMs = (double)(liEnd.QuadPart - liStart.QuadPart) / (double)liFreq.QuadPart * 1000;
	_tprintf(_T("Total Device Count: %lld, Elapsed: %f msec\n"), vecDev.size(), fTimeElapsedMs);

    auto idx = std::find(vecDev.begin(), vecDev.end(), _T("FTDI FT601 USB 3.0 Bridge Device"));
    if (idx == vecDev.end()) {
        _tprintf(_T("Cannot find device"));
    }
    else {
        _tprintf(_T("Found device (index: %lld)"), idx - vecDev.begin());
    }
}

실행결과

Total Device Count: 909, Elapsed: 2118.421500 msec
Found device (index: 870)

909개 디바이스들의 장치 설명(SPDRP_DEVICEDESC)을 가져오는데 2초 정도 소요된다

시간이 적지 않게 걸리므로 어플리케이션 초기화 시 호출하거나, 사용자에 의한 refresh 명령이 호출될때만 구동하는 것이 좋을 것 같다

 

벡터에 문자열들을 집어넣은 뒤, std::find 함수로 존재하는지 여부를 확인할 수 있다

 

참 쉽쥬?

여유 시간 짬짬이 장치관리자를 트리뷰같은걸 써서 직접 만들어보는 것도 재밌을 것 같다 

 

[참고]

https://www.codeproject.com/Articles/14469/Simple-Device-Manager

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=blue7red&logNo=100063168724

반응형