Add initial Windows support

Only scanning has been implemented so far. The most work was really just
understanding WinRT well enough to get to this point.
This commit is contained in:
Ayke van Laethem 2020-05-30 20:55:20 +02:00
parent 7a11ef8562
commit 22553053ff
11 changed files with 655 additions and 7 deletions

@ -9,5 +9,16 @@ jobs:
- checkout
- run: tinygo version
- run:
name: "Run smoke tests"
command: make smoketest
name: "Run TinyGo smoke tests"
command: make smoketest-tinygo
- run:
name: "Run Linux smoke tests"
command: make smoketest-linux
- run:
name: "Install Windows cross compiler"
command: |
# Install the tools themselves.
apt-get install -y gcc-mingw-w64-x86-64
- run:
name: "Run Windows smoke tests"
command: make smoketest-windows

@ -1,7 +1,9 @@
TINYGO=tinygo
smoketest:
smoketest: smoketest-tinygo smoketest-linux smoketest-windows
smoketest-tinygo:
# Test all examples (and some boards)
$(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/advertisement
@md5sum test.hex
@ -12,6 +14,13 @@ smoketest:
# Test some more boards that are not tested above.
$(TINYGO) build -o test.hex -size=short -target=pca10056-s140v7 ./examples/advertisement
@md5sum test.hex
# Test on the host
go build -o /tmp/go-build-discard ./examples/advertisement
go build -o /tmp/go-build-discard ./examples/heartrate
smoketest-linux:
# Test on Linux.
GOOS=linux go build -o /tmp/go-build-discard ./examples/advertisement
GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate
GOOS=linux go build -o /tmp/go-build-discard ./examples/scanner
smoketest-windows:
# Test on Windows.
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/scanner

24
adapter_windows.go Normal file

@ -0,0 +1,24 @@
package bluetooth
import (
"github.com/aykevl/go-bluetooth/winbt"
"github.com/go-ole/go-ole"
)
type Adapter struct {
handler func(Event)
watcher *winbt.IBluetoothLEAdvertisementWatcher
}
var defaultAdapter Adapter
// DefaultAdapter returns the default adapter on the current system.
func DefaultAdapter() (*Adapter, error) {
return &defaultAdapter, nil
}
// Enable configures the BLE stack. It must be called before any
// Bluetooth-related calls (unless otherwise indicated).
func (a *Adapter) Enable() error {
return ole.RoInitialize(1) // initialize with multithreading enabled
}

77
gap_windows.go Normal file

@ -0,0 +1,77 @@
package bluetooth
import (
"github.com/aykevl/go-bluetooth/winbt"
)
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
// is to cancel the scan when a particular device has been found.
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
if a.watcher != nil {
// Cannot scan more than once: which one should ScanStop()
// stop?
return errScanning
}
a.watcher, err = winbt.NewBluetoothLEAdvertisementWatcher()
if err != nil {
return
}
defer a.watcher.Release()
// Listen for incoming BLE advertisement packets.
err = a.watcher.AddReceivedEvent(func(watcher *winbt.IBluetoothLEAdvertisementWatcher, args *winbt.IBluetoothLEAdvertisementReceivedEventArgs) {
var result ScanResult
result.RSSI = args.RawSignalStrengthInDBm()
addr := args.BluetoothAddress()
for i := range result.Address {
result.Address[i] = byte(addr)
addr >>= 8
}
advertisement := args.Advertisement()
result.AdvertisementPayload = &advertisementFields{
AdvertisementFields{
LocalName: advertisement.LocalName(),
},
}
callback(a, result)
})
if err != nil {
return
}
// Wait for when advertisement has stopped by a call to StopScan().
// Advertisement doesn't seem to stop right away, there is an
// intermediate Stopping state.
stoppingChan := make(chan struct{})
err = a.watcher.AddStoppedEvent(func(watcher *winbt.IBluetoothLEAdvertisementWatcher, args *winbt.IBluetoothLEAdvertisementWatcherStoppedEventArgs) {
// Note: the args parameter has an Error property that should
// probably be checked, but I'm not sure when stopping the
// advertisement watcher could ever result in an error (except
// for bugs).
close(stoppingChan)
})
if err != nil {
return
}
err = a.watcher.Start()
if err != nil {
return err
}
// Wait until advertisement has stopped, and finish.
<-stoppingChan
a.watcher = nil
return nil
}
// StopScan stops any in-progress scan. It can be called from within a Scan
// callback to stop the current scan. If no scan is in progress, an error will
// be returned.
func (a *Adapter) StopScan() error {
if a.watcher == nil {
return errNotScanning
}
return a.watcher.Stop()
}

5
go.mod

@ -2,4 +2,7 @@ module github.com/aykevl/go-bluetooth
go 1.14
require github.com/muka/go-bluetooth v0.0.0-20200518110738-ed2c87e2f9fa
require (
github.com/go-ole/go-ole v1.2.4
github.com/muka/go-bluetooth v0.0.0-20200518110738-ed2c87e2f9fa
)

2
go.sum

@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

250
winbt/advertisement.go Normal file

@ -0,0 +1,250 @@
package winbt
import (
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
)
type WatcherStatus uint32
const (
WatcherStatusCreated WatcherStatus = 0
WatcherStatusStarted WatcherStatus = 1
WatcherStatusStopping WatcherStatus = 2
WatcherStatusStopped WatcherStatus = 3
WatcherStatusAborted WatcherStatus = 4
)
type IBluetoothLEAdvertisementWatcher struct {
ole.IInspectable
}
type IBluetoothLEAdvertisementWatcherVtbl struct {
ole.IInspectableVtbl
GetMinSamplingInterval uintptr // ([out] [retval] Windows.Foundation.TimeSpan* value);
GetMaxSamplingInterval uintptr // ([out] [retval] Windows.Foundation.TimeSpan* value);
GetMinOutOfRangeTimeout uintptr // ([out] [retval] Windows.Foundation.TimeSpan* value);
GetMaxOutOfRangeTimeout uintptr // ([out] [retval] Windows.Foundation.TimeSpan* value);
GetStatus uintptr // ([out] [retval] Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcherStatus* value);
GetScanningMode uintptr // ([out] [retval] Windows.Devices.Bluetooth.Advertisement.BluetoothLEScanningMode* value);
SetScanningMode uintptr // ([in] Windows.Devices.Bluetooth.Advertisement.BluetoothLEScanningMode value);
GetSignalStrengthFilter uintptr // ([out] [retval] Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter** value);
SetSignalStrengthFilter uintptr // ([in] Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter* value);
GetAdvertisementFilter uintptr // ([out] [retval] Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter** value);
SetAdvertisementFilter uintptr // ([in] Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter* value);
Start uintptr // ();
Stop uintptr // ();
AddReceivedEvent uintptr // ([in] Windows.Foundation.TypedEventHandler<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher*, Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementReceivedEventArgs*>* handler, [out] [retval] EventRegistrationToken* token);
RemoveReceivedEvent uintptr // ([in] EventRegistrationToken token);
AddStoppedEvent uintptr // ([in] Windows.Foundation.TypedEventHandler<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher*, Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcherStoppedEventArgs*>* handler, [out] [retval] EventRegistrationToken* token);
RemoveStoppedEvent uintptr // ([in] EventRegistrationToken token);
}
func (v *IBluetoothLEAdvertisementWatcher) VTable() *IBluetoothLEAdvertisementWatcherVtbl {
return (*IBluetoothLEAdvertisementWatcherVtbl)(unsafe.Pointer(v.RawVTable))
}
func NewBluetoothLEAdvertisementWatcher() (*IBluetoothLEAdvertisementWatcher, error) {
inspectable, err := ole.RoActivateInstance("Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher")
if err != nil {
return nil, err
}
watcherItf := inspectable.MustQueryInterface(ole.NewGUID("A6AC336F-F3D3-4297-8D6C-C81EA6623F40"))
return (*IBluetoothLEAdvertisementWatcher)(unsafe.Pointer(watcherItf)), nil
}
func (v *IBluetoothLEAdvertisementWatcher) AddReceivedEvent(handler func(*IBluetoothLEAdvertisementWatcher, *IBluetoothLEAdvertisementReceivedEventArgs)) (err error) {
event := NewEvent(ole.NewGUID("{90EB4ECA-D465-5EA0-A61C-033C8C5ECEF2}"), func(event *Event, argsInspectable *ole.IInspectable) {
args := (*IBluetoothLEAdvertisementReceivedEventArgs)(unsafe.Pointer(argsInspectable.MustQueryInterface(IID_IBluetoothLEAdvertisementReceivedEventArgs)))
defer args.Release()
handler(v, args)
})
hr, _, _ := syscall.Syscall(
v.VTable().AddReceivedEvent,
3,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(event)),
uintptr(unsafe.Pointer(&event.token)),
)
return makeError(hr)
}
func (v *IBluetoothLEAdvertisementWatcher) AddStoppedEvent(handler func(*IBluetoothLEAdvertisementWatcher, *IBluetoothLEAdvertisementWatcherStoppedEventArgs)) (err error) {
event := NewEvent(ole.NewGUID("{9936A4DB-DC99-55C3-9E9B-BF4854BD9EAB}"), func(event *Event, argsInspectable *ole.IInspectable) {
args := (*IBluetoothLEAdvertisementWatcherStoppedEventArgs)(unsafe.Pointer(argsInspectable.MustQueryInterface(IID_IBluetoothLEAdvertisementWatcherStoppedEventArgs)))
defer args.Release()
handler(v, args)
})
hr, _, _ := syscall.Syscall(
v.VTable().AddStoppedEvent,
3,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(event)),
uintptr(unsafe.Pointer(&event.token)),
)
return makeError(hr)
}
func (v *IBluetoothLEAdvertisementWatcher) Start() error {
hr, _, _ := syscall.Syscall(
v.VTable().Start,
1,
uintptr(unsafe.Pointer(v)),
0,
0)
return makeError(hr)
}
func (v *IBluetoothLEAdvertisementWatcher) Stop() error {
hr, _, _ := syscall.Syscall(
v.VTable().Stop,
1,
uintptr(unsafe.Pointer(v)),
0,
0)
return makeError(hr)
}
func (v *IBluetoothLEAdvertisementWatcher) Status() WatcherStatus {
var status WatcherStatus
hr, _, _ := syscall.Syscall(
v.VTable().GetStatus,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&status)),
0)
mustSucceed(hr)
return status
}
type IBluetoothLEAdvertisementReceivedEventArgs struct {
ole.IInspectable
}
type IBluetoothLEAdvertisementReceivedEventArgsVtbl struct {
ole.IInspectableVtbl
RawSignalStrengthInDBm uintptr // ([out] [retval] INT16* value);
BluetoothAddress uintptr // ([out] [retval] UINT64* value);
AdvertisementType uintptr // ([out] [retval] Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementType* value);
Timestamp uintptr // ([out] [retval] Windows.Foundation.DateTime* value);
Advertisement uintptr // ([out] [retval] Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisement** value);
}
func (v *IBluetoothLEAdvertisementReceivedEventArgs) VTable() *IBluetoothLEAdvertisementReceivedEventArgsVtbl {
return (*IBluetoothLEAdvertisementReceivedEventArgsVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IBluetoothLEAdvertisementReceivedEventArgs) RawSignalStrengthInDBm() (rssi int16) {
hr, _, _ := syscall.Syscall(
v.VTable().RawSignalStrengthInDBm,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&rssi)),
0)
mustSucceed(hr)
return
}
func (v *IBluetoothLEAdvertisementReceivedEventArgs) BluetoothAddress() (address uint64) {
hr, _, _ := syscall.Syscall(
v.VTable().BluetoothAddress,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&address)),
0)
mustSucceed(hr)
return
}
func (v *IBluetoothLEAdvertisementReceivedEventArgs) Advertisement() (advertisement *IBluetoothLEAdvertisement) {
hr, _, _ := syscall.Syscall(
v.VTable().Advertisement,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&advertisement)),
0)
mustSucceed(hr)
return
}
type IBluetoothLEAdvertisementWatcherStoppedEventArgs struct {
ole.IInspectable
}
type IBluetoothLEAdvertisement struct {
ole.IInspectable
}
type IBluetoothLEAdvertisementVtbl struct {
ole.IInspectableVtbl
GetFlags uintptr // ([out] [retval] Windows.Foundation.IReference<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFlags>** value);
SetFlags uintptr // ([in] Windows.Foundation.IReference<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFlags>* value);
GetLocalName uintptr // ([out] [retval] HSTRING* value);
SetLocalName uintptr // ([in] HSTRING value);
GetServiceUuids uintptr // ([out] [retval] Windows.Foundation.Collections.IVector<GUID>** value);
GetManufacturerData uintptr // ([out] [retval] Windows.Foundation.Collections.IVector<Windows.Devices.Bluetooth.Advertisement.BluetoothLEManufacturerData*>** value);
GetDataSections uintptr // ([out] [retval] Windows.Foundation.Collections.IVector<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementDataSection*>** value);
GetManufacturerDataByCompanyId uintptr // ([in] UINT16 companyId, [out] [retval] Windows.Foundation.Collections.IVectorView<Windows.Devices.Bluetooth.Advertisement.BluetoothLEManufacturerData*>** dataList);
GetSectionsByType uintptr // ([in] BYTE type, [out] [retval] Windows.Foundation.Collections.IVectorView<Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementDataSection*>** sectionList);
}
func (v *IBluetoothLEAdvertisement) VTable() *IBluetoothLEAdvertisementVtbl {
return (*IBluetoothLEAdvertisementVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IBluetoothLEAdvertisement) LocalName() string {
var hstring ole.HString
hr, _, _ := syscall.Syscall(
v.VTable().GetLocalName,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&hstring)),
0)
if hr != 0 {
// Should not happen.
panic(ole.NewError(hr))
}
name := hstring.String()
ole.DeleteHString(hstring)
return name
}
func (v *IBluetoothLEAdvertisement) DataSections() (vector *IVector) {
hr, _, _ := syscall.Syscall(
v.VTable().GetDataSections,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&vector)),
0)
mustSucceed(hr)
return
}
type IBluetoothLEAdvertisementDataSection struct {
ole.IInspectable
}
type IBluetoothLEAdvertisementDataSectionVtbl struct {
ole.IInspectableVtbl
GetDataType uintptr // ([out] [retval] BYTE* value)
SetDataType uintptr // ([in] BYTE value)
GetData uintptr // ([out] [retval] Windows.Storage.Streams.IBuffer** value)
SetData uintptr // ([in] Windows.Storage.Streams.IBuffer* value)
}
func (v *IBluetoothLEAdvertisementDataSection) VTable() *IBluetoothLEAdvertisementDataSectionVtbl {
return (*IBluetoothLEAdvertisementDataSectionVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IBluetoothLEAdvertisementDataSection) DataType() (value byte) {
hr, _, _ := syscall.Syscall(
v.VTable().GetDataType,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&value)),
0)
mustSucceed(hr)
return
}

56
winbt/event.c Normal file

@ -0,0 +1,56 @@
// This file implements the C side of WinRT events.
// Unfortunately, this cannot be done entirely in pure go, for two reasons:
// * An Event object must be shared with the C world (WinRT) after
// syscall.Syscall returns. This is not allowed in Go, to keep the option
// open to switch to a moving GC in the future. For this, it is needed to
// allocate the Event object on the C heap (using malloc).
// * Building a virtual function table is very difficult (if not impossible)
// in pure Go. It might be possible using reflect, but due the the previous
// issue I haven't investigated that.
#include <stdint.h>
// Note: these functions have a different signature but because they are only
// used as function pointers (and never called) and because they use C name
// mangling, the signature doesn't really matter.
void winbt_Event_Invoke(void);
void winbt_Event_QueryInterface(void);
// This is the contract the functions below should adhere to:
// https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown
static uint64_t winbt_Event_AddRef(void) {
// This is safe, see winbt_Event_Release.
return 2;
}
static uint64_t winbt_Event_Release(void) {
// Pretend there is one reference left.
// The docs say:
// > This value is intended to be used only for test purposes.
// Also see:
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/august/windows-with-c-the-windows-runtime-application-model
return 1;
}
// The Vtable structure for WinRT event interfaces.
typedef struct {
void *QueryInterface;
void *AddRef;
void *Release;
void *Invoke;
} EventVtbl_t;
// The Vtable itself. It can be kept constant.
static const EventVtbl_t winbt_EventVtbl = {
(void*)winbt_Event_QueryInterface,
(void*)winbt_Event_AddRef,
(void*)winbt_Event_Release,
(void*)winbt_Event_Invoke,
};
// A small helper function to get the Vtable.
const EventVtbl_t * winbt_getEventVtbl(void) {
return &winbt_EventVtbl;
}

82
winbt/event.go Normal file

@ -0,0 +1,82 @@
package winbt
// This file implements a COM object that can be used for various event
// callbacks. In C++, it would use an ITypedEventHandler instantiation.
import (
"unsafe"
"github.com/go-ole/go-ole"
)
// const void * winbt_getEventVtbl(void);
import "C"
// Event implements event handler interfaces (ITypedEventHandler). This is
// therefore a valid COM object, derived from IUnknown.
type Event struct {
ole.IUnknown
IID *ole.GUID
Callback func(*Event, *ole.IInspectable)
token uintptr
}
// NewEvent creates a new COM object for event callbacks. The IID must be the
// IID for a particular event, which is unfortunately not included in the *.idl
// files but has to be found in the relevant C++/WinRT header files.
func NewEvent(iid *ole.GUID, callback func(*Event, *ole.IInspectable)) *Event {
// Another way to find the IID is to print the requested IID in the
// QueryInterface method below. The first queried IID is likely the one
// that is the event interface IID.
// The event must be allocated on the C heap, because it is not allowed to
// retain a pointer on the Go heap after a non-Go call returns (e.g.
// syscall.Syscall).
event := (*Event)(C.malloc(C.size_t(unsafe.Sizeof(Event{}))))
event.RawVTable = (*interface{})(C.winbt_getEventVtbl())
event.IID = iid
event.Callback = callback
return event
}
// The two functions below implement IUnknown and an interface I couldn't
// really get the definition of (possibly ITypedEventHandler, although
// ITypedEventHandler is really a C++ template). The closest thing I could find
// to documentation are the C++/WinRT headers and this mention by Kenny Kerr
// (the original developer of C++/WinRT):
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/august/windows-with-c-the-windows-runtime-application-model
// > [...] Fortunately, all of these interfaces are generated by the MIDL
// > compiler, which takes care to specialize each one, and its on these
// > specializations that it attaches the GUID representing the interface
// > identifier. As complicated as the previous typedef might appear, it
// > defines a COM interface that derives directly from IUnknown and provides
// > a single method called Invoke.
//export winbt_Event_QueryInterface
func winbt_Event_QueryInterface(eventPtr, iidPtr unsafe.Pointer, ppvObject *unsafe.Pointer) uintptr {
// This function must adhere to the QueryInterface defined here:
// https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown
iid := (*ole.GUID)(iidPtr)
event := (*Event)(eventPtr)
if ole.IsEqualGUID(iid, event.IID) {
// This is us.
*ppvObject = eventPtr
return ole.S_OK
}
if ole.IsEqualGUID(iid, ole.IID_IUnknown) {
// This is our parent. There are some limitations as to what we can
// return here, but returning the *Event pointer is fine.
*ppvObject = eventPtr
return ole.S_OK
}
return 0x80004002 // E_NOTINTERFACE
}
//export winbt_Event_Invoke
func winbt_Event_Invoke(eventPtr, senderPtr, argsPtr unsafe.Pointer) uintptr {
// See the quote above.
event := (*Event)(eventPtr)
argsInspectable := (*ole.IInspectable)(argsPtr)
event.Callback(event, argsInspectable)
return ole.S_OK
}

73
winbt/vector.go Normal file

@ -0,0 +1,73 @@
package winbt
import (
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
)
type IVector struct {
ole.IInspectable
}
type IVectorVtbl struct {
ole.IInspectableVtbl
// These methods have been obtained from windows.foundation.collections.h
// in the WinRT API.
// read methods
GetAt uintptr // (_In_opt_ unsigned index, _Out_ T_abi *item)
GetSize uintptr // (_Out_ unsigned *size)
GetView uintptr // (_Outptr_result_maybenull_ IVectorView<T_logical> **view)
IndexOf uintptr // (_In_opt_ T_abi value, _Out_ unsigned *index, _Out_ boolean *found)
// write methods
SetAt uintptr // (_In_ unsigned index, _In_opt_ T_abi item)
InsertAt uintptr // (_In_ unsigned index, _In_opt_ T_abi item)
RemoveAt uintptr // (_In_ unsigned index)
Append uintptr // (_In_opt_ T_abi item)
RemoveAtEnd uintptr // ()
Clear uintptr // ()
// bulk transfer methods
GetMany uintptr // (_In_ unsigned startIndex, _In_ unsigned capacity, _Out_writes_to_(capacity,*actual) T_abi *value, _Out_ unsigned *actual)
ReplaceAll uintptr // (_In_ unsigned count, _In_reads_(count) T_abi *value)
}
func (v *IVector) VTable() *IVectorVtbl {
return (*IVectorVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IVector) At(index int) (element unsafe.Pointer) {
// The caller will need to cast the element to the correct type (for
// example, *IBluetoothLEAdvertisementDataSection).
hr, _, _ := syscall.Syscall(
v.VTable().GetAt,
3,
uintptr(unsafe.Pointer(v)),
uintptr(index),
uintptr(unsafe.Pointer(&element)),
)
mustSucceed(hr)
return
}
func (v *IVector) Size() int {
// Note that because the size is defined as `unsigned`, and `unsigned`
// means 32-bit in Windows (even 64-bit windows), the size is always a
// uint32.
// Casting to int because that is the common data type for sizes in Go. It
// should practically always fit on 32-bit Windows and definitely always
// fit on 64-bit Windows (with a 64-bit Go int).
var size uint32
hr, _, _ := syscall.Syscall(
v.VTable().GetSize,
2,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&size)),
0)
mustSucceed(hr)
return int(size)
}

61
winbt/winbt.go Normal file

@ -0,0 +1,61 @@
// Package winbt provides a thin layer over the WinRT Bluetooth interfaces. It
// is not designed to be used directly by applications: the bluetooth package
// will wrap the API exposed here in a nice platform-independent way.
//
// You can find the original *.idl and *.h files in a directory like this,
// after installing the Windows SDK:
//
// C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\winrt
//
// Some helpful articles to understand WinRT at a low level:
// https://blog.xojo.com/2019/07/02/accessing-windows-runtime-winrt/
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/august/windows-with-c-the-windows-runtime-application-model
// https://blog.magnusmontin.net/2017/12/30/minimal-uwp-wrl-xaml-app/
// https://yizhang82.dev/what-is-winrt
// https://www.slideshare.net/goldshtn/deep-dive-into-winrt
package winbt
import (
"github.com/go-ole/go-ole"
)
var (
IID_IBluetoothLEAdvertisementReceivedEventArgs = ole.NewGUID("27987DDF-E596-41BE-8D43-9E6731D4A913")
IID_IBluetoothLEAdvertisementWatcherStoppedEventArgs = ole.NewGUID("DD40F84D-E7B9-43E3-9C04-0685D085FD8C")
)
// printGUIDs prints the GUIDs this IInspectable implements. It is primarily
// intended for debugging.
func printGUIDs(inspectable *ole.IInspectable) {
guids, err := inspectable.GetIids()
if err != nil {
println("could not get GUIDs for IInspectable:", err.Error())
return
}
for _, guid := range guids {
println("guid:", guid.String())
}
}
// makeError makes a *ole.OleError if hr is non-nil. If it is nil, it will
// return nil.
// This is an utility function to easily convert an HRESULT into a Go error
// value.
func makeError(hr uintptr) error {
if hr != 0 {
return ole.NewError(hr)
}
return nil
}
// mustSucceed can be called to check the return value of getters, which should
// always succeed. If hr is non-zero, it will panic with an error message.
func mustSucceed(hr uintptr) {
if hr != 0 {
// Status is a getter, so should never return an error unless
// an invalid `v` is passed in (for example, `v` is nil) - in
// which case, there is definitely a bug and we should fail
// early.
panic("winbt: unexpected error: " + ole.NewError(hr).String())
}
}