WindowsでIOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATIONをC#から正しく呼び出す方法【kernel32.dllとSuperSpeedPlus対応USB 3.2解析】

WindowsでUSB 3.1/3.2(SuperSpeedPlus)のリンク速度やレーン数をコードから正しく取得したい場合、どのDLLをP/Invokeすべきか、どのIOCTLをどのハンドルに投げるべきかでつまずきがちです。本記事では、IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATIONをC#から正しく叩くための実装手順と、kernel32.dllとkernelbase.dllの選び方を、実際に動く形を意識しながら詳しく解説します。

目次

IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION の概要

IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION は、USB ハブの特定ポートについて「SuperSpeedPlus(USB 3.1/3.2)」に関するレーン情報(速度・レーン数)を取得するためのユーザーモード IOCTL です。ターゲットは USB ハブ デバイス(GUID_DEVINTERFACE_USB_HUB)であり、入出力バッファとして USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION 構造体を使います。

この構造体は、ざっくり言うと「どのポート(ConnectionIndex)について」「Rx/Tx それぞれ何レーン」「そのレーンはどの SuperSpeedPlus 速度で動いているか」を返すためのものです。USB 3.2 Gen2x2 のような 2 レーン構成もここから判断できます。

メンバー意味ポイント
ConnectionIndex1始まりのポート番号ハブの N 番ポートを指定(0は無効)
Length構造体のサイズユーザー側で sizeof を入れるのが安全
RxSuperSpeedPlus.Speed受信レーンの SuperSpeedPlus 速度属性USB 3.1/3.2 仕様で定義された SSP スピード値
RxLaneCount受信レーン数(0ベース)実際のレーン数 = RxLaneCount + 1
TxSuperSpeedPlus.Speed送信レーンの SuperSpeedPlus 速度属性Rx 側と同様に SSP スピード値
TxLaneCount送信レーン数(0ベース)実際のレーン数 = TxLaneCount + 1

この IOCTL 自体は WDK/WD 10.0.26100 系(Windows 11 24H2 対応)で追加されたもので、「USB superspeed information のサポート」として USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION とセットで紹介されています。

kernel32.dll と kernelbase.dll の違いと P/Invoke の正解

歴史的な背景 – なぜ2つあるのか

元々、Win32 API の多くは kernel32.dll に実装されていました。その後の Windows で内部構造が整理され、多くの汎用 API 実装は KernelBase.dll に移され、「kernel32.dll のエクスポートは内部的に KernelBase.dll へフォワードする」形になりました。

ただし「公式にアプリがリンク・P/Invoke すべき DLL」としては今でも kernel32.dll が契約の窓口 です。実装 DLL である KernelBase.dll は OS の内部構造に近く、将来のバージョンで構成やエクスポートの有無が変わる可能性があり、直接指定するメリットはほぼありません。

DLL役割備考
kernel32.dllアプリからの公式 API エクスポート窓口多くの関数は内部で KernelBase にフォワード
KernelBase.dll実装本体・OS 内部用存在や構造は OS バージョンに依存、直接指定は非推奨

結論:P/Invoke の DLL 名は kernel32.dll を使う

  • C/C++ でリンクする import library も Kernel32.lib が基本。
  • C# などの P/Invoke でも、互換性・可読性の観点から kernel32.dll を指定するのが定石 です。
  • 実際、Microsoft Q&A で紹介されている検証コードでも DeviceIoControl や CreateFile は [DllImport("Kernel32.dll")] で宣言されています。

したがって、本記事のサンプルでも すべて kernel32.dll で宣言 します。

C# における DeviceIoControl の宣言例

using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern SafeFileHandle CreateFile(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        IntPtr lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped);
}

SafeFileHandle を使うことでハンドルリークを防ぎつつ、ガベージコレクタに自動解放させられるのでおすすめです。

SuperSpeedPlus 情報取得の全体フロー

IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION を有効に使うには、「ハブ単位でポートを総なめ」するのが基本パターンです。

  1. GUID_DEVINTERFACE_USB_HUB でハブ デバイスパスを列挙
  2. 各ハブデバイスパスを CreateFile で開く
  3. IOCTL_USB_GET_NODE_INFORMATION でポート数 (bNumberOfPorts) を取得
  4. ポート1〜N に対して IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION を発行
  5. 結果を解釈(レーン数と速度)して自前のモデルに格納

どのステップも「ドライバのサンプルコードや WDK がやっていること」と同じ流れで、既存ツール(UsbView など)とも親和性が高い方法です。

手順1:USB ハブ デバイスの列挙

USB ハブはデバイスインターフェイス クラス GUID_DEVINTERFACE_USB_HUB で公開されます。GUID は次の値です。

private static readonly Guid GUID_DEVINTERFACE_USB_HUB =
    new Guid("F18A0E88-C30C-11D0-8815-00A0C906BED8");

SetupAPI での列挙はおなじみの3段階です。

  1. SetupDiGetClassDevs でデバイス情報セットを取得
  2. SetupDiEnumDeviceInterfaces でインターフェイスを列挙
  3. SetupDiGetDeviceInterfaceDetail でデバイスパス(シンボリックリンク)を取得

C# での SetupAPI 宣言(抜粋)

internal static class SetupApi
{
    [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern IntPtr SetupDiGetClassDevs(
        ref Guid ClassGuid,
        IntPtr Enumerator,
        IntPtr hwndParent,
        uint Flags);

    [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool SetupDiEnumDeviceInterfaces(
        IntPtr DeviceInfoSet,
        IntPtr DeviceInfoData,
        ref Guid InterfaceClassGuid,
        uint MemberIndex,
        ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);

    [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool SetupDiGetDeviceInterfaceDetail(
        IntPtr DeviceInfoSet,
        ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
        IntPtr DeviceInterfaceDetailData,
        uint DeviceInterfaceDetailDataSize,
        out uint RequiredSize,
        IntPtr DeviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool SetupDiGetDeviceInterfaceDetail(
        IntPtr DeviceInfoSet,
        ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
        ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
        uint DeviceInterfaceDetailDataSize,
        out uint RequiredSize,
        ref SP_DEVINFO_DATA DeviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    internal static extern bool SetupDiDestroyDeviceInfoList(
        IntPtr DeviceInfoSet);

    internal const uint DIGCF_PRESENT = 0x00000002;
    internal const uint DIGCF_DEVICEINTERFACE = 0x00000010;

    [StructLayout(LayoutKind.Sequential)]
    internal struct SP_DEVICE_INTERFACE_DATA
    {
        public int cbSize;
        public Guid InterfaceClassGuid;
        public int Flags;
        public IntPtr Reserved;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
    {
        public int cbSize;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string DevicePath;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct SP_DEVINFO_DATA
    {
        public int cbSize;
        public Guid ClassGuid;
        public int DevInst;
        public IntPtr Reserved;
    }
}

ポイントは SP_DEVICE_INTERFACE_DETAIL_DATA.cbSize の値です。

  • x86(32bit):cbSize = 6
  • x64(64bit):cbSize = 8

これは構造体が「先頭に DWORD、その後に可変長 WCHAR 配列」というレイアウトになっているためで、ポインタサイズに応じてアラインメントが変わることによります。Q&A のサンプルもこの値をきちんと切り替えています。

ハブのデバイスパス列挙のイメージコード

IEnumerable<string> EnumerateUsbHubs()
{
    var guid = GUID_DEVINTERFACE_USB_HUB;
    IntPtr devInfo = SetupApi.SetupDiGetClassDevs(
        ref guid, IntPtr.Zero, IntPtr.Zero,
        SetupApi.DIGCF_DEVICEINTERFACE | SetupApi.DIGCF_PRESENT);

    if (devInfo == IntPtr.Zero)
        yield break;

    try
    {
        var ifData = new SetupApi.SP_DEVICE_INTERFACE_DATA
        {
            cbSize = Marshal.SizeOf<SetupApi.SP_DEVICE_INTERFACE_DATA>()
        };

        for (uint index = 0; SetupApi.SetupDiEnumDeviceInterfaces(
                 devInfo, IntPtr.Zero, ref guid, index, ref ifData); index++)
        {
            // 必要バッファサイズを取得
            SetupApi.SetupDiGetDeviceInterfaceDetail(
                devInfo, ref ifData,
                IntPtr.Zero, 0,
                out uint requiredSize,
                IntPtr.Zero);

            var detail = new SetupApi.SP_DEVICE_INTERFACE_DETAIL_DATA
            {
                cbSize = IntPtr.Size == 8 ? 8 : 6
            };
            var devInfoData = new SetupApi.SP_DEVINFO_DATA
            {
                cbSize = Marshal.SizeOf<SetupApi.SP_DEVINFO_DATA>()
            };

            if (SetupApi.SetupDiGetDeviceInterfaceDetail(
                    devInfo, ref ifData,
                    ref detail,
                    requiredSize,
                    out requiredSize,
                    ref devInfoData))
            {
                yield return detail.DevicePath;
            }
        }
    }
    finally
    {
        SetupApi.SetupDiDestroyDeviceInfoList(devInfo);
    }
}

手順2:ハブを開く(CreateFile)

列挙したデバイスパスは「シンボリックリンク名」です。これを CreateFile で 通常のファイルと同じように 開きます。Target はあくまで「USBハブ デバイス」である点が重要です。デバイス(GUID_DEVINTERFACE_USB_DEVICE)のパスを開いてもこの IOCTL は使えません。

const uint GENERIC_READ  = 0x80000000;
const uint GENERIC_WRITE = 0x40000000;
const uint FILE_SHARE_READ  = 0x00000001;
const uint FILE_SHARE_WRITE = 0x00000002;
const uint OPEN_EXISTING    = 3;

SafeFileHandle OpenHub(string devicePath)
{
    var handle = NativeMethods.CreateFile(
        devicePath,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        IntPtr.Zero,
        OPEN_EXISTING,
        0,
        IntPtr.Zero);

    if (handle.IsInvalid)
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

    return handle;
}

手順3:IOCTL_USB_GET_NODE_INFORMATION でポート数を取得

ハブがいくつの下流ポートを持っているかは、古くからある IOCTL_USB_GET_NODE_INFORMATION で取得します。入出力バッファは USB_NODE_INFORMATION 構造体です。

関連構造体(必要部分のみ)

enum USB_HUB_NODE
{
    UsbHub = 0,
    UsbMIParent = 1
}

[StructLayout(LayoutKind.Sequential)]
struct USB_HUB_DESCRIPTOR
{
    public byte  bDescriptorLength;
    public byte  bDescriptorType;
    public byte  bNumberOfPorts;
    public ushort wHubCharacteristics;
    public byte  bPowerOnToPowerGood;
    public byte  bHubControlCurrent;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    public byte[] DeviceRemovable;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    public byte[] PortPowerCtrlMask;
}

[StructLayout(LayoutKind.Sequential)]
struct USB_HUB_INFORMATION
{
    public USB_HUB_DESCRIPTOR HubDescriptor;
    [MarshalAs(UnmanagedType.Bool)]
    public bool HubIsBusPowered;
}

[StructLayout(LayoutKind.Sequential)]
struct USB_MI_PARENT_INFORMATION
{
    public uint NumberOfInterfaces;
}

[StructLayout(LayoutKind.Explicit)]
struct USB_NODE_INFORMATION
{
    [FieldOffset(0)] public USB_HUB_NODE NodeType;
    [FieldOffset(4)] public USB_HUB_INFORMATION HubInformation;
    [FieldOffset(4)] public USB_MI_PARENT_INFORMATION MiParentInformation;
}

IOCTL 定数定義(CTL_CODE マクロ相当)

IOCTL コードは CTL_CODE マクロ相当のヘルパで定義します。

static uint CTL_CODE(uint deviceType, uint function, uint method, uint access)
{
    return (deviceType << 16)
         | (access    << 14)
         | (function  << 2)
         | method;
}

const uint FILE_DEVICE_USB = 0x22;
const uint METHOD_BUFFERED = 0;
const uint FILE_ANY_ACCESS = 0;

const uint USB_GET_NODE_INFORMATION = 258;
const uint USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION = 289;

static readonly uint IOCTL_USB_GET_NODE_INFORMATION =
    CTL_CODE(FILE_DEVICE_USB,
             USB_GET_NODE_INFORMATION,
             METHOD_BUFFERED,
             FILE_ANY_ACCESS);

static readonly uint IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION =
    CTL_CODE(FILE_DEVICE_USB,
             USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION,
             METHOD_BUFFERED,
             FILE_ANY_ACCESS);

ポート数を取得するコード例

byte GetPortCount(SafeFileHandle hubHandle)
{
    USB_NODE_INFORMATION nodeInfo = new USB_NODE_INFORMATION
    {
        NodeType = USB_HUB_NODE.UsbHub
    };

    int size = Marshal.SizeOf<USB_NODE_INFORMATION>();
    IntPtr ptr = Marshal.AllocHGlobal(size);

    try
    {
        Marshal.StructureToPtr(nodeInfo, ptr, false);

        if (!NativeMethods.DeviceIoControl(
                hubHandle,
                IOCTL_USB_GET_NODE_INFORMATION,
                ptr, (uint)size,
                ptr, (uint)size,
                out uint bytesReturned,
                IntPtr.Zero))
        {
            throw new System.ComponentModel.Win32Exception(
                Marshal.GetLastWin32Error());
        }

        nodeInfo = Marshal.PtrToStructure<USB_NODE_INFORMATION>(ptr);
        return nodeInfo.HubInformation.HubDescriptor.bNumberOfPorts;
    }
    finally
    {
        Marshal.FreeHGlobal(ptr);
    }
}

手順4:各ポートの SuperSpeedPlus 情報を取得

USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION の C# 定義

[StructLayout(LayoutKind.Sequential)]
struct USB_DEVICE_CAPABILITY_SUPERSPEEDPLUS_SPEED
{
    public uint Speed;
}

[StructLayout(LayoutKind.Sequential)]
struct USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION
{
    public uint ConnectionIndex;
    public uint Length;
    public USB_DEVICE_CAPABILITY_SUPERSPEEDPLUS_SPEED RxSuperSpeedPlus;
    public uint RxLaneCount;
    public USB_DEVICE_CAPABILITY_SUPERSPEEDPLUS_SPEED TxSuperSpeedPlus;
    public uint TxLaneCount;
}

公式ドキュメントでもこの構造体のフィールド順は同じです。

最重要ポイント:Length に構造体サイズを入れる

Length フィールドは必ず呼び出し側で構造体サイズを設定 してください。

  • IOCTL の入出力バッファ長は「sizeof(USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION)」であることが要求されています。
  • 多くの Windows API と同様に、Length は将来の拡張を見越した「構造体の自己申告サイズ」として扱われることが多く、ゼロのままだと STATUS_INVALID_PARAMETER などで失敗したり、フィールドの一部が無視される可能性があります。
  • 一部のサンプルコードでは Length の設定を省いていますが、実運用コードでは必ず設定することをおすすめします。

ポートごとに IOCTL を発行するループ

void DumpSuperSpeedPlusInfo(SafeFileHandle hubHandle)
{
    byte portCount = GetPortCount(hubHandle);

    int ssSize = Marshal.SizeOf<USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION>();

    for (uint port = 1; port <= portCount; port++)
    {
        var ssp = new USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION
        {
            ConnectionIndex = port,
            Length = (uint)ssSize   // ★ここが重要
        };

        IntPtr ptr = Marshal.AllocHGlobal(ssSize);

        try
        {
            Marshal.StructureToPtr(ssp, ptr, false);

            bool ok = NativeMethods.DeviceIoControl(
                hubHandle,
                IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION,
                ptr, (uint)ssSize,
                ptr, (uint)ssSize,
                out uint bytesReturned,
                IntPtr.Zero);

            if (!ok)
            {
                int err = Marshal.GetLastWin32Error();
                // Windows 10 や未対応ハブでは ERROR_NOT_SUPPORTED(50) などになり得る
                System.Diagnostics.Debug.WriteLine(
                    $"Port {port}: IOCTL failed. Error={err}");
                continue;
            }

            ssp = Marshal.PtrToStructure<USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION>(ptr);

            uint rxLanes = ssp.RxLaneCount + 1;
            uint txLanes = ssp.TxLaneCount + 1;

            System.Diagnostics.Debug.WriteLine(
                $"Port {port}: RxSpeed={ssp.RxSuperSpeedPlus.Speed}, " +
                $"RxLanes={rxLanes}, TxSpeed={ssp.TxSuperSpeedPlus.Speed}, " +
                $"TxLanes={txLanes}");
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

Speed の具体的な数値が何ビット/秒を意味するかは USB 3.1/3.2 仕様で定義されている SuperSpeedPlus デバイスケイパビリティのビットフィールドに従います。USB-IF では、SuperSpeed(USB 3.0/3.2 Gen1)は 5 Gbit/s、SuperSpeedPlus(Gen2)は 10 Gbit/s、Gen2x2 では 20 Gbit/s のリンク速度を規定しており、レーン数は「GenNxM の M」が対応します。

OSバージョンと 0 が返るケースの読み方

SuperSpeedPlus 関連 IOCTL は Windows 11 24H2 世代で正式に「新機能」として追加されています。Windows 10 など古い OS 上で同じコードを動かした場合、以下のような挙動が考えられます。

パターンDeviceIoControl の戻り値Speed フィールド考えられる原因
OS・ハブとも未対応false未定義GetLastError = ERROR_NOT_SUPPORTED (50) など
IOCTL 自体は実装済みだが情報なしtrue0 のままポート/デバイスが SuperSpeedPlus で動作していない、またはハブが SSP 情報を報告しない
正常(SuperSpeedPlus 接続)true0 以外レーン数&速度ビットから USB 3.2 Gen2x2 などが判別可能

Microsoft Q&A の検証では、Windows 10 上で IOCTL を発行すると DeviceIoControl 自体は成功するものの、Rx/Tx.Speed が 0 のまま、という報告があります。これは「IOCTL が将来追加されることを前提にスタブ実装されている」か、「ハブ/スタックが単に情報を返していない」可能性が高く、必ずしもバグとは限りません。

したがって、「Speed が 0 だから失敗」と即断するのではなく、まずは DeviceIoControl の戻り値・GetLastError を確認することが重要です。

よくある落とし穴と対策

1. DLL を kernelbase.dll で宣言してしまう

  • 動作する環境もありますが、将来的な互換性が不透明。
  • 公式ドキュメントやサンプルは kernel32.dll を前提としているため、トラブルシュート時に情報が検索しづらくなります。
  • 常に kernel32.dll を指定するクセを付けておくと無難です。

2. ConnectionIndex を 0 から回してしまう

  • ConnectionIndex は「1始まり」のポート番号です。
  • 0 を指定すると STATUS_INVALID_PARAMETER 相当のエラーになり得ます。

3. Length を設定していない

  • Length を 0 のまま渡すと、ドライバ側でサイズチェックに失敗することがあります。
  • 必ず (uint)Marshal.SizeOf<USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION>() を代入してください。

4. 構造体レイアウト(Pack)の不一致

  • 多くの USB 関連構造体は 4 バイト境界を前提としていますが、StructLayout の Pack を 1 にしてしまうとオフセットがずれ、ERROR_INVALID_USER_BUFFER (1784) などが発生します。
  • 特に x64 ではデフォルトの Pack=8 のままで正しく動くことが多いので、「明示的に Pack=1 に変える」よりはデフォルトのままにして、どうしても必要な場合のみ USB ヘッダと突き合わせて調整するのがおすすめです。

5. ハブではなくデバイスのパスを開いている

  • USB デバイス自体(ストレージやキーボードなど)のデバイスパスに対して、USB ハブ向け IOCTL を投げても意味がありません。
  • SuperSpeedPlus 情報を取得できるのはあくまで ハブ ドライバ なので、ターゲットは必ず GUID_DEVINTERFACE_USB_HUB です。

6. SetupAPI の cbSize を間違えている

  • SP_DEVICE_INTERFACE_DATA.cbSize、SP_DEVINFO_DATA.cbSize はそれぞれ Marshal.SizeOf<T>() で設定してください。
  • SP_DEVICE_INTERFACE_DETAIL_DATA.cbSize は IntPtr サイズに応じて 6 または 8 にする必要があります(前述)。

SuperSpeed / SuperSpeedPlus と USB 3.1/3.2 の読み解き方

USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION から取得した Speed とレーン数を、USB 3.1/3.2 のマーケティング用名称(Gen1/Gen2/Gen2x2 など)にきれいにマッピングしたくなりますが、実際には USB 3.1 仕様で定義された「SuperSpeedPlus デバイスケイパビリティ構造体」のサブリンクスピード属性を解釈する必要があります。

とはいえ、実用上は次のような読み方でも十分です。

代表的なモードマーケティング名称リンク速度レーン数イメージ
USB 3.2 Gen1x1SuperSpeed USB 5Gbps5 Gbit/s1 レーン(SS)
USB 3.2 Gen2x1SuperSpeed USB 10Gbps10 Gbit/s1 レーン(SSP)
USB 3.2 Gen2x2SuperSpeed USB 20Gbps10 Gbit/s × 2 レーン2 レーン(SSP x2)

上表と RxLaneCount/TxLaneCount を組み合わせることで、「このポートは USB 3.2 Gen2x2 でリンクアップしていそうだ」といった推測が可能になります(正確な判定には BOS ディスクリプタなども併用するとベターです)。

小さなユーティリティとしてまとめるときの設計のヒント

ここまでの部品を組み合わせると、例えば次のような「USB ハブとポートのリンク状態を一覧する」コンソールツールが作れます。

  • ステップ1:EnumerateUsbHubs() で全ハブのデバイスパスを列挙
  • ステップ2:各ハブを開き、GetPortCount() でポート数を取得
  • ステップ3:各ポートについて DumpSuperSpeedPlusInfo() を呼び、SuperSpeedPlus 情報を収集
  • ステップ4:結果を JSON や CSV に落としておけば、ケーブルやハブの検証にも使える

実機検証の際は、Windows 11 24H2 世代環境で、USB 3.2 Gen2x2 対応ポート+ケーブル+デバイスを組み合わせることで、RxLaneCount/TxLaneCount が 1(=2レーン)になるケースを狙うと挙動が確認しやすくなります。

まとめ – DLL と実装手順の整理

  • DLL は kernel32.dll を指定するのが正解。KernelBase.dll は実装詳細であり、互換性の観点から直接指定する理由はありません。
  • IOCTL のターゲットは USB ハブ(GUID_DEVINTERFACE_USB_HUB)。SetupAPI でハブを列挙し、そのパスを CreateFile で開きます。
  • ポート数は IOCTL_USB_GET_NODE_INFORMATION で取得し、bNumberOfPorts をベースに 1 〜 N でループします。
  • 各ポートに対して IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION を発行し、USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION の Rx/TxSuperSpeedPlus と Rx/TxLaneCount を読み取ります。
  • Length に構造体サイズを必ず設定し、P/Invoke の構造体レイアウト(特に cbSize、Pack)を OS 側の定義と揃えることが安定動作のカギです。
  • Speed が 0 だからといって即異常とは限らないことに注意し、まずは DeviceIoControl の戻り値と GetLastError を確認して OS/ドライバの対応状況を切り分けましょう。

以上を押さえておけば、IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION を使って、USB 3.1/3.2 の実際のリンク速度やレーン構成をアプリケーション側から高い精度で把握できるようになります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次