Howto: Using WinRT apis from Island/Windows

howto
island

(Carlo Kok) #1

The api itself is c based, but the upcoming beta has the winrt apis:

namespace ConsoleApplication625;
uses 
  rtl.winrt;
type
  HSTR = public record 
  private
     fValue: HSTRING;
  public
    constructor; empty;
    constructor(aValue: String);
    begin 
      WindowsCreateString(aValue.FirstChar, aValue.Length, @fValue);
    end;

    constructor(aValue: ^rtl.WCHAR);
    begin 
      WindowsCreateString(aValue, ExternalCalls.wcslen(aValue), @fValue);
    end;

    finalizer;
    begin 
      if fvalue <> nil then 
        WindowsDeleteString(fValue);
    end;

    method ToString: String; override;
    begin 
      if fValue = nil then exit nil;
      var lLen: UInt32;
      exit String.FromPChar(WindowsGetStringRawBuffer(fValue, @lLen), lLen);
    end;

    property Ptr: ^HSTRING read @fValue;
    property Value: HSTRING read fValue;
  end;

  Program = class
  public

    class method Main(args: array of String): Int32;
    begin
      var si: ^__x_ABI_CWindows_CSystem_CProfile_CSystemManufacturers_CISmbiosInformationStatics;
      var g := Guid.Parse('080CCA7C-637C-48C4-B728-F9273812DB8E');
      RoInitialize(RO_INIT_TYPE.MULTITHREADED);
      
      RoGetActivationFactory(new HSTR(RuntimeClass_Windows_System_Profile_SystemManufacturers_SmbiosInformation).Value, ^rtl.Guid(@g), ^^Void(@si));

      var lRes: HSTR;
      si^.lpVtbl^.get_SerialNumber(si, lRes.Ptr);
      si^.lpVtbl^.Release(si);
      writeLn(lRes.ToString);

      RoUninitialize();
    end;

  end;

end.

(joseasl) #2

There is no like button on this post, but I do like it!
There is so much potential here.
Can we expect an aspect to map the c-like interface to an island object/interface? So there is no need for …(si,…) or to call Release?
This should allow us to use XAML with Island. I need to try it.


(Benoît Bousquet) #3

How would one go about doing this in Swift?

I honestly haven’t done a whole lot of COM and I’m trying to move my C++/CX code to Silver. Would the following work?

EDIT: Making progress (the key was using CIBluetoothLEDeviceStatics instead of CIBluetoothLEDevice)

In this instance, I’m trying to call Windows::Devices::Bluetooth::BluetoothLEDevice 's FromIdAsync method:

// ...
RoInitialize(.RO_INIT_MULTITHREADED)
// ...
var cid: HSTRING
let rc = RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice
WindowsCreateString(rc, ExternalCalls.wcslen(rc), &cid)
var obj: UnsafePointer<Void>
RoGetActivationFactory(cid, &IID___x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics, &obj)
if let o = obj as? UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics> {
    (*(*obj).lpTvtbl).FromIdAsync()
    // This returns an IAsyncOperation<BluetoothLEDevice>
}
// ...
RoUninitialize()
// ...

(marc hoffman) #4

yeah, that looks roughly right.


(Benoît Bousquet) #5

Let’s just say that stuff is quite verbose. It’s ok though - the stuff I need to use it for isn’t too complex. I guess it will be a crash course in COM for me…


(Carlo Kok) #6

Yeah. I’m still looking for ways to make it more manageable; using the IWebBrowser* interfaces myself in a project and finding it way too verbose too. But at least it should get you going.


(Benoît Bousquet) #7

Is there a way to look at the winrt.fx file? Seeing the actual definitions would be quite helpful as Fire’s “go to definition” feature doesn’t always work and the COM stuff is pretty verbose.


(marc hoffman) #8

HeaderImporter.exe has a codeine command line option that lets you generate .cs, .pas, .swift or .java code for the headers in the .fx, eg:

HeaderImporter codegen /path/to/winrt.fx /path/to/winrt.pas Oxygene

(Benoît Bousquet) #9

That’s a Windows EXE file, it seems (in the macOS Fire.app). Is that on purpose?


(marc hoffman) #10

It’s a .NET exe. you can run it on Mac, using Mono:

mono HeaderImporter codegen /path/to/winrt.fx /path/to/winrt.pas Oxygene

(if you don’t have Mono installed, you can use

/path/to/Fire.app/Contents/Resources/Mono/bin/mono HeaderImporter codegen /path/to/winrt.fx /path/to/winrt.pas Oxygene

(Benoît Bousquet) #11

How would one hook up a TypedEventHandler to, say, a BluetoothLEAdvertisementWatcher?

var stoppedHandlerToken: EventRegistrationToken?

// hstr: HSTRING built from RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher
var guid = RemObjects.Elements.System.Guid.Parse("A6AC336F-F3D3-4297-8D6C-C81EA6623F40")
var obj: UnsafePointer<Void>
if RoGetActivationFactory(hstr, (&guid as! UnsafePointer<GUID>), &obj != S_OK {
    return nil
}
let object = obj as! UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CAdvertisement_CIBluetoothLEAdvertisementWatcher>

// Need to instantiate TypedEventHandler<BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementWatcherStoppedEventArgs>
// this is: __FITypedEventHandler_2_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcher_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcherStoppedEventArgs

var handlerObj: UnsafePointer<__FITypedEventHandler_2_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcher_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcherStoppedEventArgs>

// should I be using RoGetActivationFactory again? I've coded this in C++/CX but it was somewhat straightforward:
// _watcher->Stopped += ref new TypedEventHandler<BluetoothLEAdvertisementWatcher ^, BluetoothLEAdvertisementWatcherStoppedEventArgs ^>(classInstance, &MyClass::OnAdvertisementWatcherStopped);
// ... missing code ...

var token = EventRegistrationToken()
if (*(*object).lpVtbl).add_Stopped(object, handlerObj, &token) == S_OK {
    stoppedEventHandler = token
}

// ...

(*(*object).lpVtbl).remove_Stopped(object, stoppedEventHandlerToken)

(Carlo Kok) #12

I would do something like:

import rtl.winrt;

@COMImport(typeOf(rtl.winrt.__FITypedEventHandler_2_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcher_Windows__CDevices__CBluetooth__CAdvertisement__CBluetoothLEAdvertisementWatcherStoppedEventArgs), Guid = "9936a4db-dc99-55c3-9e9b-bf4854bd9eab")
public protocol CBluetoothLEAdvertisementWatcherStoppedEventArgs
{
}

public struct CBluetoothLEAdvertisementWatcherStoppedEventArgsPtr
{
}

@COMObject
class Impl: CBluetoothLEAdvertisementWatcherStoppedEventArgs
{
}

writeLn("The magic happens here.")

Impl now fails to compile because it doesn’t implement “invoke” (which is the point of this exercise)

How this works:
@COMImport is an aspect that “fills” the protocol and matching *Ptr type.
@COMObject creates QueryInterface, AddRef, Release etc to be com usable.

To use it as a com object, you can do something like:

var ptr: CBluetoothLEAdvertisementWatcherStoppedEventArgsPtr = Impl(); // (or existing reference)


(*(*object).lpVtbl).add_Stopped(&ptr as! UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CAdvertisement_CIBluetoothLEAdvertisementWatcher>, stoppedEventHandlerToken)

(Benoît Bousquet) #13

I’m struggling to use the AsyncOperation obtained from BluetoothLEDevice::FromAsync. I have made some simple wrapper classes for the WinRT classes I’m using. Here is my code:

import rtl
import rtl.winrt

@COMImport(typeOf(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice), Guid = "9156b79f-c54a-5277-8f8b-d2cc43c7e004")
public protocol IAsyncOperationCompletedHandler_CBluetoothLEDevice {
}

public struct IAsyncOperationCompletedHandler_CBluetoothLEDevicePtr {
}

@COMObject
public class AsyncOperationCompletedHandler_BluetoothLEDevice : IAsyncOperationCompletedHandler_CBluetoothLEDevice {
	private weak var completion: (UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDevice>?) -> Void

	init(completion: (UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDevice>?) -> Void) {
		self.completion = completion
	}
	public func Invoke(_ asyncInfo: UnsafePointer<__FIAsyncOperation_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice>, _ status: AsyncStatus) -> HRESULT {
		var device: UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDevice>?
		(*(*asyncInfo).lpVtbl).GetResults(asyncInfo, &device)
		completion(device)
		return S_OK
	}
}

public class BluetoothLEDevice {

	private static __field IID___x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics = RemObjects.Elements.System.Guid.Parse("C8CF1A19-F0B6-4BF0-8689-41303DE2D9F4")

	class func FromIdAsync(deviceId: String, completion: (BluetoothLEDevice?) -> Void) {
		let basid = HStringWrapper(wstr: RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice)
		var obj: UnsafePointer<Void>
		guard RoGetActivationFactory(basid.value, (&IID___x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics as! UnsafePointer<GUID>), &obj) == S_OK else {
			completion(nil)
			return
		}
		guard let bas = obj as? UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics> else {
			completion(nil)
			return
		}
		defer {
			(*(*bas).lpVtbl).Release(bas)
		}
		var asyncOperation: UnsafePointer<__FIAsyncOperation_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice>
		guard (*(*bas).lpVtbl).FromIdAsync(bas, HStringWrapper(string: deviceId).value, &asyncOperation) == S_OK else {
			completion(nil)
			return
		}
		var completedHandler: IAsyncOperationCompletedHandler_CBluetoothLEDevice = AsyncOperationCompletedHandler_BluetoothLEDevice { asyncInfo in
			guard let asyncInfo = asyncInfo else {
				completion(nil)
				return
			}
			completion(BluetoothLEDevice(object: asyncInfo))
		}
		let res = (*(*asyncOperation).lpVtbl).put_Completed(asyncOperation, (&completedHandler as! UnsafePointer<__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice>))
		guard res == S_OK else {
			completion(nil)
			return
		}
	}
}

It seems to work fine until I call put_Completed (bottom part of the source code). At that point, the app just poofs, I get no exception, no HRESULT to check, nothing. This is code I’m calling directly from a console application. Note: asyncOperation is not nil and if I pass nil as the second parameter to put_Completed I get no crash.


(Carlo Kok) #14

Hrmm that’s usually a crash of sorts. Do you have a full testcase I can try here?


(Benoît Bousquet) #15

I’ll prepare a quick test case when I get to the office (in 2 hours-ish).


(Benoît Bousquet) #16

Here’s a Water solution that reproduces this crash:

ConsoleApplication4.zip (6.0 KB)


(RemObjects) #17

Thanks, logged as bugs://80062


(Carlo Kok) #18

Reproduced; curious issue; I’ll let you know when I find a fix.


(Carlo Kok) #19

I found a calling convention related bug in the code there. I’m fixing those now, then retesting.


(Carlo Kok) #20

With the next build, ie tomorrow (or if it’s time sensitive I can upload a build; do you need VS or Water?)


	class func FromIdAsync(deviceId: String, completion: (BluetoothLEDevice?) -> Void) {
		let basid = HStringWrapper(wstr: RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice)
		var obj: UnsafePointer<Void>
		guard RoGetActivationFactory(basid.value, (&IID___x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics as! UnsafePointer<GUID>), &obj) == S_OK else {
			completion(nil)
			return
		}
		guard let bas = obj as? UnsafePointer<__x_ABI_CWindows_CDevices_CBluetooth_CIBluetoothLEDeviceStatics> else {
			completion(nil)
			return
		}
		defer {
			(*(*bas).lpVtbl).Release(bas)
		}
		var asyncOperation: UnsafePointer<__FIAsyncOperation_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice>
		guard (*(*bas).lpVtbl).FromIdAsync(bas, HStringWrapper(string: deviceId).value, &asyncOperation) == S_OK else {
			completion(nil)
			return
		}
		var completedHandler: IAsyncOperationCompletedHandler_CBluetoothLEDevice = AsyncOperationCompletedHandler_BluetoothLEDevice { asyncInfo in
			guard let asyncInfo = asyncInfo else {
				completion(nil)
				return
			}
			completion(BluetoothLEDevice(object: asyncInfo))
		}
        var handler: IAsyncOperationCompletedHandler_CBluetoothLEDevicePtr = completedHandler as! IAsyncOperationCompletedHandler_CBluetoothLEDevicePtr;
		let res = (*(*asyncOperation).lpVtbl).put_Completed(asyncOperation, *((&handler) as! UnsafePointer<UnsafePointer<__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CBluetoothLEDevice>>))
		guard res == S_OK else {
			completion(nil)
			return
		}
	}

works. Or at least prints:
Started
FromIdAsync: ERROR
Finished

which probably makes sense as I don’t have that device.