WebUSB용 기기 빌드

WebUSB API를 최대한 활용할 수 있도록 기기를 빌드합니다.

Reilly Grant
Reilly Grant

이 도움말에서는 WebUSB API를 최대한 활용할 수 있도록 기기를 빌드하는 방법을 설명합니다. API 자체에 관한 간략한 소개는 웹에서 USB 기기에 액세스를 참고하세요.

배경

범용 직렬 버스 (USB)는 주변기기를 데스크톱 및 모바일 컴퓨팅 기기에 연결하기 위한 가장 일반적인 물리적 인터페이스가 되었습니다. USB 사양에는 버스의 전기 특성과 기기와의 통신을 위한 일반 모델을 정의하는 것 외에도 일련의 기기 클래스 사양이 포함됩니다. 이는 기기 제조업체가 구현할 수 있는 저장소, 오디오, 동영상, 네트워킹 등 특정 기기 클래스를 위한 일반 모델입니다. 이러한 기기 클래스 사양의 장점은 운영체제 공급업체가 클래스 사양에 따라 단일 드라이버('클래스 드라이버')를 구현할 수 있고 이 클래스를 구현하는 모든 기기가 지원된다는 점입니다. 이는 모든 제조업체가 자체 기기 드라이버를 작성해야 하는 것에 비해 크게 개선되었습니다.

그러나 일부 기기는 이러한 표준화된 기기 클래스 중 하나에 맞지 않습니다. 대신 제조업체는 기기에 공급업체별 클래스를 구현하는 것으로 라벨을 지정할 수 있습니다. 이 경우 운영체제는 공급업체의 드라이버 패키지에 제공된 정보(일반적으로 특정 공급업체별 프로토콜을 구현하는 것으로 알려진 공급업체 및 제품 ID 집합)에 따라 로드할 기기 드라이버를 선택합니다.

USB의 또 다른 기능은 기기가 연결된 호스트에 여러 인터페이스를 제공할 수 있다는 것입니다. 각 인터페이스는 표준화된 클래스를 구현하거나 공급업체별로 다를 수 있습니다. 운영체제에서 기기를 처리할 적절한 드라이버를 선택했다면 각 인터페이스는 다른 드라이버에서 소유권을 주장할 수 있습니다. 예를 들어 USB 웹캠은 일반적으로 USB 동영상 클래스 (카메라용)와 USB 오디오 클래스 (마이크용) 구현 등 두 개의 인터페이스를 제공합니다. 운영체제는 단일 '웹캠 드라이버'를 로드하지 않고 대신 기기의 개별 기능을 담당하는 독립적인 동영상 및 오디오 클래스 드라이버를 로드합니다. 이러한 인터페이스 클래스 조합은 유연성을 제공합니다.

API 기본사항

대부분의 표준 USB 클래스에는 상응하는 웹 API가 있습니다. 예를 들어 페이지는 getUserMedia()를 사용하여 동영상 클래스 기기에서 동영상을 캡처하거나 KeyboardEvents 또는 PointerEvents를 수신 대기하거나 Gamepad 또는 WebHID API를 사용하여 인간 인터페이스 (HID) 클래스 기기에서 입력 이벤트를 수신할 수 있습니다. 모든 기기가 표준화된 클래스 정의를 구현하는 것은 아니지만, 모든 기기가 기존 웹 플랫폼 API에 상응하는 기능을 구현하는 것은 아닙니다. 이 경우 WebUSB API는 사이트에서 공급업체별 인터페이스를 주장하고 페이지 내에서 직접 지원을 구현할 수 있는 방법을 제공하여 이러한 공백을 메울 수 있습니다.

WebUSB를 통해 액세스할 수 있는 기기의 구체적인 요구사항은 USB 기기를 관리하는 방법의 차이로 인해 플랫폼마다 약간 다릅니다. 하지만 기본 요구사항은 기기에서 제어하고자 하는 인터페이스를 주장하는 드라이버가 이미 있어서는 안 된다는 것입니다. OS 공급업체가 제공하는 일반 클래스 드라이버이거나 공급업체에서 제공하는 기기 드라이버일 수 있습니다. USB 기기는 다중 인터페이스를 제공할 수 있고, 각 인터페이스는 자체 드라이버를 가질 수 있으므로, 일부 인터페이스는 드라이버에 의해 요청되고 다른 인터페이스는 브라우저에서 액세스할 수 있는 기기를 빌드할 수 있습니다.

예를 들어 고급 USB 키보드는 운영체제의 입력 하위 시스템에서 요구하는 HID 클래스 인터페이스와 구성 도구에서 사용할 수 있도록 WebUSB에서 계속 사용할 수 있는 공급업체별 인터페이스를 제공할 수 있습니다. 이 도구를 제조업체의 웹사이트에 제공하면 사용자가 플랫폼별 소프트웨어를 설치하지 않고도 매크로 키 및 조명 효과와 같은 기기 동작의 측면을 변경할 수 있습니다. 이러한 기기의 구성 설명어는 다음과 같습니다.

필드 설명
구성 설명어
0x09 bLength 설명어 크기
0x02 bDescriptorType 구성 설명어
0x0039 wTotalLength 이 설명어의 전체 길이
0x02 bNumInterfaces 인터페이스 수
0x01 bConfigurationValue Configuration 1
0x00 iConfiguration 구성 이름 (없음)
0b1010000 bmAttributes 원격 절전 모드 해제 기능이 있는 자체 구동 기기
0x32 bMaxPower 최대 전력은 2mA 단위로 표시됨
인터페이스 설명어
0x09 bLength 설명어 크기
0x04 bDescriptorType 인터페이스 설명어
0x00 bInterfaceNumber 인터페이스 0
0x00 bAlternateSetting 대체 설정 0 (기본값)
0x01 bNumEndpoints 엔드포인트 1개
0x03 bInterfaceClass HID 인터페이스 클래스
0x01 bInterfaceSubClass 부팅 인터페이스 서브클래스
0x01 bInterfaceProtocol 키보드
0x00 iInterface 인터페이스 이름 (없음)
HID 설명자
0x09 bLength 설명어 크기
0x21 bDescriptorType HID 설명자
0x0101 bcdHID HID 버전 1.1
0x00 bCountryCode 하드웨어 대상 국가
0x01 bNumDescriptors 따라야 할 HID 클래스 설명자 수
0x22 bDescriptorType 보고서 설명어 유형
0x003F wDescriptorLength 보고서 설명어의 전체 길이
엔드포인트 설명자
0x07 bLength 설명어 크기
0x05 bDescriptorType 엔드포인트 설명자
0b10000001 bEndpointAddress 엔드포인트 1 (IN)
0b00000011 bmAttributes 중단
0x0008 wMaxPacketSize 8바이트 패킷
0x0A bInterval 10ms 간격
인터페이스 설명어
0x09 bLength 설명어 크기
0x04 bDescriptorType 인터페이스 설명어
0x01 bInterfaceNumber 인터페이스 1
0x00 bAlternateSetting 대체 설정 0 (기본값)
0x02 bNumEndpoints 엔드포인트 2개
0xFF bInterfaceClass 공급업체별 인터페이스 클래스
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface 인터페이스 이름 (없음)
엔드포인트 설명자
0x07 bLength 설명어 크기
0x05 bDescriptorType 엔드포인트 설명자
0b10000010 bEndpointAddress 엔드포인트 1 (IN)
0b00000010 bmAttributes 단체메일
0x0040 wMaxPacketSize 64바이트 패킷
0x00 bInterval 대량 엔드포인트의 경우 해당 사항 없음
엔드포인트 설명자
0x07 bLength 설명어 크기
0x05 bDescriptorType 엔드포인트 설명자
0b00000011 bEndpointAddress 엔드포인트 3 (OUT)
0b00000010 bmAttributes 단체메일
0x0040 wMaxPacketSize 64바이트 패킷
0x00 bInterval 대량 엔드포인트의 경우 해당 사항 없음

구성 설명어는 연결된 여러 설명어로 구성됩니다. 각각은 식별이 가능하도록 bLengthbDescriptorType 필드로 시작합니다. 첫 번째 인터페이스는 연결된 HID 설명자와 입력 이벤트를 운영체제에 전달하는 데 사용되는 단일 엔드포인트가 있는 HID 인터페이스입니다. 두 번째 인터페이스는 기기에 명령어를 전송하고 응답을 수신하는 데 사용할 수 있는 두 개의 엔드포인트가 있는 공급업체별 인터페이스입니다.

WebUSB 설명자

WebUSB는 펌웨어 수정 없이 많은 기기에서 작동할 수 있지만, WebUSB 지원을 나타내는 특정 설명어를 기기에 표시하면 추가 기능이 사용 설정됩니다. 예를 들어 기기가 연결되어 있을 때 브라우저에서 사용자를 연결할 수 있는 방문 페이지 URL을 지정할 수 있습니다.

Chrome의 WebUSB 알림 스크린샷
WebUSB 알림.

바이너리 기기 객체 저장소 (BOS)는 USB 3.0에 도입된 개념이지만 버전 2.1의 일부로 USB 2.0 기기에도 백포팅되었습니다. WebUSB 지원을 선언하는 것은 BOS 설명어에 다음 플랫폼 기능 설명어를 포함하는 것으로 시작됩니다.

필드 설명
바이너리 기기 객체 저장소 설명어
0x05 bLength 설명어 크기
0x0F bDescriptorType 바이너리 기기 객체 저장소 설명어
0x001D wTotalLength 이 설명어의 전체 길이
0x01 bNumDeviceCaps BOS의 기기 기능 설명자 수
WebUSB 플랫폼 기능 설명어
0x18 bLength 설명어 크기
0x10 bDescriptorType 기기 기능 설명어
0x05 bDevCapabilityType 플랫폼 기능 설명어
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID Little Endian 형식의 WebUSB 플랫폼 기능 설명자 GUID
0x0100 bcdVersion WebUSB 설명자 버전 1.0
0x01 bVendorCode WebUSB의 bRequest 값
0x01 iLandingPage 방문 페이지 URL

플랫폼 기능 UUID는 이를 기기의 기본 정보를 제공하는 WebUSB 플랫폼 기능 설명어로 식별합니다. 브라우저가 기기에 관한 자세한 정보를 가져오기 위해 bVendorCode 값을 사용하여 기기에 추가 요청을 실행합니다. 현재 지정된 유일한 요청은 URL 설명자를 반환하는 GET_URL입니다. 문자열 설명자와 유사하지만 가장 적은 바이트로 URL을 인코딩하도록 설계되었습니다. "https://google.com"의 URL 설명자는 다음과 같습니다.

필드 설명
URL 설명자
0x0D bLength 설명어 크기
0x03 bDescriptorType URL 설명자
0x01 bScheme https://
"google.com" URL UTF-8로 인코딩된 URL 콘텐츠

기기가 브라우저에 처음 연결되면 이 표준 GET_DESCRIPTOR 제어 전송을 실행하여 BOS 설명자를 읽습니다.

bmRequestType bRequest wValue wIndex wLength 데이터 (응답)
0b10000000 0x06 0x0F00 0x0000 * BOS 설명자

이 요청은 일반적으로 두 번 실행됩니다. 첫 번째에는 호스트가 대규모 전송을 커밋하지 않고 wTotalLength 필드의 값을 찾을 수 있도록 첫 번째에 충분한 큰 wLength를 사용하여 수행되고, 이후 전체 설명어 길이를 알 수 있을 때 다시 수행됩니다.

WebUSB 플랫폼 기능 설명어의 iLandingPage 필드가 0이 아닌 값으로 설정된 경우 브라우저는 bRequest를 플랫폼 기능 설명어의 bVendorCode 값으로 설정하고 wValueiLandingPage 값으로 설정하여 제어 전송을 실행하여 WebUSB 관련 GET_URL 요청을 실행합니다. GET_URL (0x02)의 요청 코드는 wIndex에 포함됩니다.

bmRequestType bRequest wValue wIndex wLength 데이터 (응답)
0b11000000 0x01 0x0001 0x0002 * URL 설명자

마찬가지로 이 요청은 먼저 읽고 있는 설명어의 길이를 조사하기 위해 두 번 발행될 수 있습니다.

플랫폼별 고려사항

WebUSB API는 USB 기기 액세스를 위한 일관된 인터페이스를 제공하려고 시도하지만, 개발자는 기기에 액세스하기 위해 웹브라우저 요구사항과 같은 애플리케이션에 적용되는 요구사항을 알고 있어야 합니다.

macOS

macOS에는 특별한 것이 필요하지 않습니다. WebUSB를 사용하는 웹사이트는 기기에 연결하여 커널 드라이버 또는 다른 애플리케이션에서 요청하지 않은 모든 인터페이스를 요구할 수 있습니다.

Linux

Linux는 macOS와 비슷하지만 기본적으로 대부분의 배포판은 USB 기기를 열 수 있는 권한이 있는 사용자 계정을 설정하지 않습니다. udev라는 시스템 데몬은 기기 액세스가 허용된 사용자와 그룹을 할당하는 일을 담당합니다. 이와 같은 규칙은 지정된 공급업체 및 제품 ID와 일치하는 기기의 소유권을 주변기기 액세스 권한이 있는 사용자의 공통 그룹인 plugdev 그룹에 할당합니다.

SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", ATTR{idProduct}=="XXXX", GROUP="plugdev"

XXXX를 기기의 16진수 공급업체 및 제품 ID로 바꿉니다. 예를 들어 ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11"는 Nexus One 휴대전화와 일치합니다. 올바르게 인식되려면 일반적으로 '0x' 접두사를 사용하지 않고 모두 소문자로 작성해야 합니다. 기기의 ID를 찾으려면 명령줄 도구 lsusb를 실행합니다.

이 규칙은 /etc/udev/rules.d 디렉터리의 파일에 배치해야 하며 기기가 연결되는 즉시 적용됩니다. udev를 다시 시작할 필요는 없습니다.

Android

Android 플랫폼은 Linux를 기반으로 하지만 시스템 구성을 수정할 필요는 없습니다. 기본적으로 운영체제에 드라이버가 내장되어 있지 않은 모든 기기는 브라우저에서 사용할 수 있습니다. 하지만 개발자는 기기에 연결할 때 추가 단계가 발생한다는 점을 알고 있어야 합니다. 사용자가 requestDevice() 호출에 대한 응답으로 기기를 선택하면 Android는 Chrome에서 기기에 액세스하도록 허용할지 묻는 메시지를 표시합니다. 사용자가 이미 기기에 연결할 수 있는 권한이 있는 웹사이트로 돌아가 웹사이트에서 open()를 호출하는 경우에도 이 메시지가 다시 표시됩니다.

또한 기본적으로 포함된 드라이버 수가 더 적으므로 데스크톱 Linux보다 Android에서 더 많은 기기에 액세스할 수 있습니다. 예를 들어, Android SDK에는 직렬 기기와 통신하기 위한 API가 없기 때문에 일반적으로 USB-직렬 어댑터로 구현되는 USB CDC-ACM 클래스는 USB CDC-ACM 클래스입니다.

ChromeOS

ChromeOS도 Linux를 기반으로 하므로 시스템 구성을 수정할 필요가 없습니다. permission_broker 서비스는 USB 기기에 대한 액세스를 제어하며, 소유권이 주장되지 않은 인터페이스가 하나 이상 있는 한 브라우저에서 USB 기기에 액세스하도록 허용합니다.

Windows

Windows 드라이버 모델에는 추가 요구사항이 있습니다. 위의 플랫폼과 달리 사용자 애플리케이션에서 USB 기기를 여는 기능은 로드된 드라이버가 없더라도 기본값이 아닙니다. 대신 애플리케이션이 기기에 액세스하는 데 사용하는 인터페이스를 제공하기 위해 로드해야 하는 특수 드라이버인 WinUSB가 있습니다. 이는 시스템에 설치된 맞춤 드라이버 정보 파일 (INF)을 사용하거나 열거 중에 Microsoft OS 호환성 설명자를 제공하도록 기기 펌웨어를 수정하여 실행할 수 있습니다.

드라이버 정보 파일 (INF)

드라이버 정보 파일은 기기를 처음 접했을 때 해야 할 일을 Windows에 알려줍니다. 사용자의 시스템에 이미 WinUSB 드라이버가 포함되어 있으므로 INF 파일에서 공급업체 및 제품 ID를 이 새로운 설치 규칙과 연결하기만 하면 됩니다. 아래 파일은 기본 예입니다. 확장자가 .inf인 파일에 저장하고 'X'로 표시된 섹션을 변경한 다음 마우스 오른쪽 버튼으로 클릭하고 컨텍스트 메뉴에서 'Install'을 선택합니다.

[Version]
Signature   = "$Windows NT$"
Class       = USBDevice
ClassGUID   = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider    = %ManufacturerName%
CatalogFile = WinUSBInstallation.cat
DriverVer   = 09/04/2012,13.54.20.543

; ========== Manufacturer/Models sections ===========

[Manufacturer]
%ManufacturerName% = Standard,NTx86,NTia64,NTamd64

[Standard.NTx86]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTia64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTamd64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

; ========== Class definition ===========

[ClassInstall32]
AddReg = ClassInstall_AddReg

[ClassInstall_AddReg]
HKR,,,,%ClassName%
HKR,,NoInstallClass,,1
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,LowerLogoVersion,,5.2

; =================== Installation ===================

[USB_Install]
Include = winusb.inf
Needs   = WINUSB.NT

[USB_Install.Services]
Include = winusb.inf
Needs   = WINUSB.NT.Services

[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

; =================== Strings ===================

[Strings]
ManufacturerName              = "Your Company Name Here"
ClassName                     = "Your Company Devices"
USB\MyCustomDevice.DeviceDesc = "Your Device Name Here"

[Dev_AddReg] 섹션에서는 기기의 DeviceInterfaceGUID 집합을 구성합니다. 애플리케이션에서 Windows API를 통해 인터페이스를 찾아서 연결하려면 모든 기기 인터페이스에 GUID가 있어야 합니다. New-Guid PowerShell cmdlet 또는 온라인 도구를 사용하여 임의의 GUID를 생성합니다.

Zadig 도구는 개발 목적으로 USB 인터페이스용으로 로드된 드라이버를 WinUSB 드라이버로 교체하기 위한 간편한 인터페이스를 제공합니다.

Microsoft OS 호환성 설명자

위의 INF 파일 접근 방식은 모든 사용자의 머신을 미리 구성해야 하므로 번거롭습니다. Windows 8.1 이상에서는 커스텀 USB 설명자 사용을 통한 대안을 제공합니다. 이러한 설명자는 기기가 처음 연결될 때 일반적으로 INF 파일에 포함된 정보를 Windows 운영체제에 제공합니다.

WebUSB 설명자를 설정하면 Microsoft의 OS 호환성 설명자도 쉽게 추가할 수 있습니다. 먼저 이 추가 플랫폼 기능 설명어를 사용하여 BOS 설명어를 확장합니다. 이를 고려하여 wTotalLengthbNumDeviceCaps를 업데이트해야 합니다.

필드 설명
Microsoft OS 2.0 플랫폼 기능 설명어
0x1C bLength 설명어 크기
0x10 bDescriptorType 기기 기능 설명어
0x05 bDevCapabilityType 플랫폼 기능 설명어
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID Little Endian 형식의 Microsoft OS 2.0 플랫폼 호환성 설명자 GUID
0x06030000 dwWindowsVersion 호환 가능한 최소 Windows 버전 (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength 설명자 집합의 총 길이
0x02 bMS_VendorCode 추가 Microsoft 설명자를 검색하기 위한 bRequest 값
0x00 bAltEnumCode 기기에서 대체 열거를 지원하지 않습니다.

WebUSB 설명자와 마찬가지로 이러한 설명자와 관련된 제어 전송에 사용할 bRequest 값을 선택해야 합니다. 이 예에서는 0x02를 선택했습니다. wIndex에 있는 0x07는 기기에서 Microsoft OS 2.0 설명자 집합을 검색하는 명령어입니다.

bmRequestType bRequest wValue wIndex wLength 데이터 (응답)
0b11000000 0x02 0x0000 0x0007 * MS OS 2.0 설명자 집합

USB 기기에는 여러 기능이 있을 수 있으므로 설명어 집합의 첫 번째 부분에서는 이어지는 속성이 연결된 함수를 설명합니다. 아래 예에서는 복합 기기의 인터페이스 1을 구성합니다. 설명자는 OS에 이 인터페이스에 관한 두 가지 중요한 정보를 제공합니다. 호환 가능한 ID 설명자는 이 기기가 WinUSB 드라이버와 호환된다고 Windows에 알립니다. 레지스트리 속성 설명자는 위 INF 예의 [Dev_AddReg] 섹션과 유사하게 작동하며, 이 함수에 기기 인터페이스 GUID를 할당하도록 레지스트리 속성을 설정합니다.

필드 설명
Microsoft OS 2.0 설명자 집합 헤더
0x000A wLength 설명어 크기
0x0000 wDescriptorType 설명어 세트 헤더 설명어
0x06030000 dwWindowsVersion 호환 가능한 최소 Windows 버전 (Windows 8.1)
0x00B2 wTotalLength 설명자 집합의 총 길이
Microsoft OS 2.0 구성 하위 집합 헤더
0x0008 wLength 설명어 크기
0x0001 wDescriptorType 구성 하위 집합 헤더 설명
0x00 bConfigurationValue 구성 1에 적용 (일반적으로 1부터 색인이 생성되더라도 0부터 색인이 생성됨)
0x00 bReserved 0으로 설정해야 합니다.
0x00A8 wTotalLength 이 헤더를 포함한 하위 집합의 총 길이
Microsoft OS 2.0 함수 하위 집합 헤더
0x0008 wLength 설명어 크기
0x0002 wDescriptorType 함수 하위 집합 헤더 설명자
0x01 bFirstInterface 함수의 첫 번째 인터페이스
0x00 bReserved 0으로 설정해야 합니다.
0x00A0 wSubsetLength 이 헤더를 포함한 하위 집합의 총 길이
Microsoft OS 2.0 호환 ID 설명자
0x0014 wLength 설명어 크기
0x0003 wDescriptorType 호환 가능한 ID 설명어
"WINUSB\0\0" CompatibileID 8바이트로 패딩된 ASCII 문자열
"\0\0\0\0\0\0\0\0" SubCompatibleID 8바이트로 패딩된 ASCII 문자열
Microsoft OS 2.0 레지스트리 속성 설명자
0x0084 wLength 설명어 크기
0x0004 wDescriptorType 레지스트리 속성 설명어
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength 속성 이름의 길이
"DeviceInterfaceGUIDs\0" PropertyName UTF-16LE로 인코딩된 null 종결자가 있는 속성 이름
0x0050 wPropertyDataLength 속성 값의 길이
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID와 UTF-16LE로 인코딩된 2개의 null 종결자

Windows는 기기에 이 정보를 한 번만 쿼리합니다. 기기가 유효한 설명어로 응답하지 않으면 다음에 기기가 연결될 때 다시 요청하지 않습니다. Microsoft는 기기를 열거할 때 생성된 레지스트리 항목을 설명하는 USB 기기 레지스트리 항목 목록을 제공했습니다. 테스트할 때는 Windows가 설명어를 다시 읽으려고 시도하도록 기기에 생성된 항목을 삭제하세요.

자세한 내용은 이러한 설명자 사용 방법에 대한 Microsoft의 블로그 게시물을 참조하세요.

WebUSB 설명자와 Microsoft OS 설명자를 모두 포함하는 WebUSB 인식 기기를 구현하는 코드의 예는 다음 프로젝트에서 확인할 수 있습니다.