前回(Windows APIでPCI Express関係の情報を得る)の続きです。前回は、システムに存在するPCIデバイスをすべて列挙し、それぞれについて情報の取得を試みるプログラムでした。今回は、対象の1デバイスを指定していく方法で書いてみました。

Windowsで1デバイスを特定する文字列はデバイスインスタンスパス (Device instance path)です。デバイスインスタンスID (Device instance ID)とも言います。Windows Vistaから、デバイスマネージャーでは「デバイス インスタンス パス」と表記されるようになりました。

デバイスインスタンスパスは、このようにデバイスマネージャーから該当するデバイスのプロパティを開いて確認できます(右クリックのコンテキストメニューからコピーもできます)。また、WMIでPNPDeviceIDというプロパティから取得できるようになっていることもあります。

デバイスマネージャーからNVIDIA GeForce GT 610のプロパティを表示し、「詳細」タブのプロパティで「デバイス インスタンス パス」を選択した状態のウィンドウ


それでは、3とおりのソースコードを載せます。

まずは1つ目、CM_Locate_DevNode関数を使う方法です。CM系関数だけで処理が完結する(DEVINST値だけあれば事足りる)場合、一番簡潔だと思います。

#define UNICODE
#define INITGUID
 
#include <iostream>
#include <windows.h>
#include <cfgmgr32.h>
#include <devpkey.h>
#include <pciprop.h>
 
#pragma comment(lib, "cfgmgr32.lib")
 
bool GetUInt32Property(
  DEVINST devInst,
  const DEVPROPKEY& propKey,
  UINT32& result)
{
  DEVPROPTYPE propertyType;
  ULONG bufferSize = sizeof result;
  auto cr = CM_Get_DevNode_Property(
    devInst,
    &propKey,
    &propertyType,
    reinterpret_cast<BYTE*>(&result),
    &bufferSize,
    0);
  return cr == CR_SUCCESS && propertyType == DEVPROP_TYPE_UINT32;
}
 
int main()
{
  std::wcout.imbue(std::locale(""));
 
  DEVINST devInst;
  auto crLocate = CM_Locate_DevNode(
    &devInst,
    LR"(PCI\VEN_10DE&DEV_104A&SUBSYS_104A1569&REV_A1\4&2EB3824&0&0018)",
    CM_LOCATE_DEVNODE_NORMAL);
  if (crLocate != CR_SUCCESS)
  {
    std::cerr << "CM_Locate_DevNode failed." << std::endl;
    return 1;
  }
 
  DEVPROPTYPE propertyType;
  WCHAR buffer[1024]; // 決め打ち。本来は動的に決めるほうが良いだろう。
  ULONG bufferSize = sizeof buffer;
  auto cr = CM_Get_DevNode_Property(
    devInst,
    &DEVPKEY_NAME,
    &propertyType,
    reinterpret_cast<BYTE*>(buffer),
    &bufferSize,
    0);
  if (cr != CR_SUCCESS || propertyType != DEVPROP_TYPE_STRING)
  {
    std::cerr << "Name is unknown." << std::endl;
    return 1;
  }
 
  UINT32 currentLinkSpeed;
  UINT32 currentLinkWidth;
  if (!GetUInt32Property(
      devInst,
      DEVPKEY_PciDevice_CurrentLinkSpeed,
      currentLinkSpeed)
    || !GetUInt32Property(
      devInst,
      DEVPKEY_PciDevice_CurrentLinkWidth,
      currentLinkWidth))
  {
    return 0;
  }
  std::wcout << buffer << std::endl;
  switch (currentLinkSpeed)
  {
  case DevProp_PciExpressDevice_LinkSpeed_TwoAndHalf_Gbps:
    std::wcout << L"2.5 Gbps" << std::endl;
    break;
  case DevProp_PciExpressDevice_LinkSpeed_Five_Gbps:
    std::wcout << L"5 Gbps" << std::endl;
    break;
  default:
    std::wcout << L"Unknown speed" << std::endl;
    break;
  }
  std::wcout << L"Link width: x" << currentLinkWidth << std::endl;
}

2つ目は、SetupDiOpenDeviceInfo関数を使う方法です。前回同様、SP_DEVINFO_DATA値が得られるので、CM系関数だけでなくSetupDi系関数(SP_DEVINFO_DATAへのポインタを実引数に取る)も使用する場合には、こちらの方法になるでしょう。

#define UNICODE
#define INITGUID
 
#include <iostream>
#include <windows.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <devpkey.h>
#include <pciprop.h>
 
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "cfgmgr32.lib")
 
bool GetUInt32Property(
  DEVINST devInst,
  const DEVPROPKEY& propKey,
  UINT32& result)
{
  DEVPROPTYPE propertyType;
  ULONG bufferSize = sizeof result;
  auto cr = CM_Get_DevNode_Property(
    devInst,
    &propKey,
    &propertyType,
    reinterpret_cast<BYTE*>(&result),
    &bufferSize,
    0);
  return cr == CR_SUCCESS && propertyType == DEVPROP_TYPE_UINT32;
}
 
int main()
{
  std::wcout.imbue(std::locale(""));
 
  auto hdi = SetupDiCreateDeviceInfoList(nullptr, nullptr);
  SP_DEVINFO_DATA data{ sizeof data };
  if (SetupDiOpenDeviceInfo(
    hdi,
    LR"(PCI\VEN_10DE&DEV_104A&SUBSYS_104A1569&REV_A1\4&2EB3824&0&0018)",
    nullptr,
    0,
    &data))
  {
    DEVPROPTYPE propertyType;
    WCHAR buffer[1024]; // 決め打ち。本来は動的に決めるほうが良いだろう。
    ULONG bufferSize = sizeof buffer;
    auto cr = CM_Get_DevNode_Property(
      data.DevInst,
      &DEVPKEY_NAME,
      &propertyType,
      reinterpret_cast<BYTE*>(buffer),
      &bufferSize,
      0);
    if (cr != CR_SUCCESS || propertyType != DEVPROP_TYPE_STRING)
    {
      return 1;
    }
 
    UINT32 currentLinkSpeed;
    UINT32 currentLinkWidth;
    if (!GetUInt32Property(
        data.DevInst,
        DEVPKEY_PciDevice_CurrentLinkSpeed,
        currentLinkSpeed)
      || !GetUInt32Property(
        data.DevInst,
        DEVPKEY_PciDevice_CurrentLinkWidth,
        currentLinkWidth))
    {
      return 1;
    }
    std::wcout << "--------\n";
    std::wcout << buffer << std::endl;
    switch (currentLinkSpeed)
    {
    case DevProp_PciExpressDevice_LinkSpeed_TwoAndHalf_Gbps:
      std::wcout << L"2.5 Gbps" << std::endl;
      break;
    case DevProp_PciExpressDevice_LinkSpeed_Five_Gbps:
      std::wcout << L"5 Gbps" << std::endl;
      break;
    default:
      std::wcout << L"Unknown speed" << std::endl;
      break;
    }
    std::wcout << L"Link width: x" << currentLinkWidth << std::endl;
  }
  SetupDiDestroyDeviceInfoList(hdi);
}

3つ目は、前回と同じSetupDiGetClassDevs関数を使う方法です。こちらのプログラムはSetupDiGetClassDevs関数に与える実引数以外、前回と全く同じです。そのため、SetupDiGetClassDevs関数を呼び出す部分だけ載せます。

  auto hdi = SetupDiGetClassDevs(
    nullptr,
    LR"(PCI\VEN_10DE&DEV_104A&SUBSYS_104A1569&REV_A1\4&2EB3824&0&0018)",
    nullptr,
    DIGCF_DEVICEINTERFACE | DIGCF_ALLCLASSES);

SetupDiGetClassDevs関数にデバイスインスタンスパスを与える場合、4番目の実引数はDIGCF_DEVICEINTERFACEとDIGCF_ALLCLASSESのORにしないとうまくいかないようです。MSDNライブラリではそうは書いていないのでハマりました。参考: windows – Does SetupDiGetClassDevs work with device instance IDs as documented? – Stack Overflow


SetupDi系とCM系の関数は両方に同じ機能を提供するものがあったり、片方だけにしかないものがあったりするので、適宜組み合わせて使うことになります。今回のように、いくつもやり方がある場合もあります。前回・今回使いませんでしたが、CM_Get_DevNode_Property関数に相当するSetupDiGetDeviceProperty関数もありました。手段が複数あるのは悩むだけなので、やめてほしいです。

以上、デバイスインスタンスパスで特定のデバイスを指定し、そのデバイスのPCI Express情報を取得するプログラムを書いてみた話でした。


スポンサード リンク

この記事のカテゴリ