1

Does anyone know what is the proper way to create a WinRT object in C? I'm trying to convert my C++ code that uses WinRT API to plain C code. And right now I'm able to get a few WinRT static functions working. However, for the objects required by the static function, like the __FIAsyncOperation_1_Windows_CDevicesCEnumerationCDeviceInformation for the get_Completed function in FIAsyncOperation_1_WindowsCDevicesCHumanInterfaceDevice_CHidDeviceVtbl, I can't find a proper way to create the object. First, I can't find the iid of this object in the idl file. Second, I'm not sure about the namespace of the object.

I did find how this class being declare in C++ macro,

#ifndef DEF___FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation_USE
 #define DEF___FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation_USE
 #if !defined(RO_NO_TEMPLATE_NAME)
 namespace ABI { namespace Windows { namespace Foundation {
 template <>
 struct __declspec(uuid("07faa053-eb2f-5cba-b25b-d9d57be6715f"))
 IAsyncOperation<ABI::Windows::Devices::Enumeration::DeviceInformation*> : IAsyncOperation_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::Devices::Enumeration::DeviceInformation*, ABI::Windows::Devices::Enumeration::IDeviceInformation*>>
 {
     static const wchar_t* z_get_rc_name_impl()
     {
         return L"Windows.Foundation.IAsyncOperation`1<Windows.Devices.Enumeration.DeviceInformation>";
     }
 };
 // Define a typedef for the parameterized interface specialization's mangled name.
 // This allows code which uses the mangled name for the parameterized interface to access the
 // correct parameterized interface specialization.
 typedef IAsyncOperation<ABI::Windows::Devices::Enumeration::DeviceInformation*> __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation_t;
 #define __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation ABI::Windows::Foundation::__FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation_t
 /* Foundation */ } /* Windows */ } /* ABI */ }
    
 #endif // !defined(RO_NO_TEMPLATE_NAME)
 #endif /* DEF___FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation_USE */

So, I tried to use this uuid and name_impl here to create the object, like this

             namespace = L"Windows.Foundation.IAsyncOperation`1<Windows.Devices.Enumeration.DeviceInformation>";
             hr = WindowsCreateStringReferenceFunc(namespace, (UINT32)wcslen(namespace), &namespace_string_header, &namespace_string);
             static const IID async_iid = { 0x07faa053, 0xeb2f, 0x5cba, { 0xb2, 0x5b, 0xd9, 0xd5, 0x7b, 0xe6, 0x71, 0x5f } };
             if (SUCCEEDED(hr)) {
                 __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation* test;
                 hr = RoGetActivationFactoryFunc(namespace_string, &async_iid, &test);
                 if (!SUCCEEDED(hr)) {
                     printf("Couldn't find Windows.Foundation.IAsyncOperation`1<Windows.Devices.Enumeration.DeviceInformation>: %d\n", hr);
                 }
             }

And after the build, the program return

Couldn't find Windows.Foundation.IAsyncOperation`1<Windows.Devices.Enumeration.DeviceInformation>: -2147221164

As I don't have the error code mapping, I don't know which part goes wrong. So can anyone tell me the correct way to create an object of WinRT in c?

I had asked this question in the Microsoft forum and they replied their Q&A currently not support this type of question. And I had also read this question before, but the answer cannot solve my problem.

Update1: This is the code that I want to convert from C++ to C

        hstring selector = winrt::to_hstring("System.Devices.InterfaceClassGuid:=\"{4D1E55B2-F16F-11CF-88CB-001111000030}\"") +
                            winrt::to_hstring(" System.DeviceInterface.Hid.VendorId: = ") + winrt::to_hstring(0x0d28) +
                            winrt::to_hstring(" AND System.DeviceInterface.Hid.ProductId : = ") + winrt::to_hstring(0x0204);
        Windows::Foundation::Collections::IVector<hstring> prop{ winrt::single_threaded_vector<hstring>() };
        prop.Append(to_hstring("System.ItemNameDisplay"));
        prop.Append(to_hstring("System.Devices.DeviceInstanceId"));
        prop.Append(to_hstring("System.Devices.Parent"));
        prop.Append(to_hstring("System.Devices.LocationPaths"));
        prop.Append(to_hstring("System.Devices.Children"));
        prop.Append(to_hstring("System.Devices.DeviceManufacturer"));
        DeviceInformationCollection collection = DeviceInformation::FindAllAsync(selector, prop).get();

As there is no get function in C, I would need to create an async object to handle the async operation in C. I also need to create a IVector object to enumerate the additional properties of the devices.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
Leslie
  • 11
  • 3
  • This class is not creatable. DeviceInformation is not either. You can start from IDeviceInformationStatics::CreateWatcher (like shown in here https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.deviceinformation). Here is a similar C code: https://pastebin.com/raw/HxAb1P8A – Simon Mourier Jun 08 '21 at 08:05
  • @SimonMourier, if this is not creatable, then what should I input into the function that requires this object. As there are similar cases in the WinRT code, for example, the IVector objects for enumerating additional parameter. And if there is some kind of struct for C++ code to create this object, then shouldn't there be some alternative do this in C? – Leslie Jun 08 '21 at 09:25
  • The system should provide you one, for example from IDeviceInformationStatics::CreateFromIdAsync – Simon Mourier Jun 08 '21 at 11:34
  • @SimonMourier, the problem is there is no such function / I can't find such function for these object – Leslie Jun 08 '21 at 12:46
  • Show the complete C++ part you want to port to C – Simon Mourier Jun 08 '21 at 17:20
  • @SimonMourier, the question updated – Leslie Jun 09 '21 at 02:40
  • That's exactly what I said and shown in my sample code, FindAllAsyncAqsFilterAndAdditionalProperties (corresponds to FindAllAsync because C++/WinRT provides nice overloads all with the same name) is a method of IDeviceInformationStatics that will get you the async object back. However, for vectors you must "implement" (provide functions to the vtable) your own __FIIterable_1_HSTRING as AFAIK, Microsoft doesn't provide a creatable vector class. C++/WinRT provides one (like with single_threaded_vector). In other words, using WinRT with C is technically possible but far from easy (ie: crazy). – Simon Mourier Jun 09 '21 at 08:32
  • You'll have to investigate how generics are exposed at the ABI. I believe that requires that you calculate the generic interface's IID. The C++/WinRT repository has code that does it. – IInspectable Jun 11 '21 at 08:20
  • @SimonMourier, thanks for your help, you are correct that I need to provide my own functions to the vtable to make this work. Though I actually don't understand what did you mean when I read your comment, XD it's a bit hard for me to understand without an example. – Leslie Jun 11 '21 at 13:12

2 Answers2

0

Okay, so after a few investigations from a GitHub repo and some help from the comment, I found the answer to my question. There is actually no contrustor function for objects like the __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformation What I need to do is to implement the functions listed in the vtbl struct. For example when I want to have a object of __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection, I will need to implement the functions listed in __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollectionVtbl, which are

typedef struct __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollectionVtbl
{
    BEGIN_INTERFACE

    HRESULT (STDMETHODCALLTYPE* QueryInterface)(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This,
        REFIID riid,
        void** ppvObject);
    ULONG (STDMETHODCALLTYPE* AddRef)(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This);
    ULONG (STDMETHODCALLTYPE* Release)(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This);
    HRESULT (STDMETHODCALLTYPE* Invoke)(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This,
        __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* asyncInfo,
        AsyncStatus asyncStatus);

    END_INTERFACE
} __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollectionVtbl;

So I should have something like this

static HRESULT STDMETHODCALLTYPE async_query_interface(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This,
    REFIID riid,
    void** ppvObject)
{
    if (!ppvObject) {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;
    static const IID async_iid = { 0x4a458732, 0x527e, 0x5c73, { 0x9a, 0x68, 0xa7, 0x3d, 0xa3, 0x70, 0xf7, 0x82 } };
    if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &async_iid)) {
        *ppvObject = This;
        This->lpVtbl->AddRef(This);
        return S_OK;
    }
    return E_NOINTERFACE;
}

static ULONG STDMETHODCALLTYPE async_add_ref(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This)
{
    return 1;
}

static ULONG STDMETHODCALLTYPE async_release(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This)
{
    return 1;
}

static HRESULT STDMETHODCALLTYPE async_invoke(__FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* This,
    __FIAsyncOperation_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection* asyncInfo,
    AsyncStatus asyncStatus)
{
    //The callback when the async complete or have some problem
}

And the iid here static const IID async_iid = { 0x4a458732, 0x527e, 0x5c73, { 0x9a, 0x68, 0xa7, 0x3d, 0xa3, 0x70, 0xf7, 0x82 } }; can be found in the header file in the winrt folder(windows.devices.enumeration.h),

namespace ABI { namespace Windows { namespace Foundation {
template <>
struct __declspec(uuid("4a458732-527e-5c73-9a68-a73da370f782"))
IAsyncOperationCompletedHandler<ABI::Windows::Devices::Enumeration::DeviceInformationCollection*> : IAsyncOperationCompletedHandler_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::Devices::Enumeration::DeviceInformationCollection*, __FIVectorView_1_Windows__CDevices__CEnumeration__CDeviceInformation*>>
{
    static const wchar_t* z_get_rc_name_impl()
    {
        return L"Windows.Foundation.AsyncOperationCompletedHandler`1<Windows.Devices.Enumeration.DeviceInformationCollection>";
    }
};

And when all these been prepared, I just need to do this to get the __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection object

    __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollection async_op;
    __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CEnumeration__CDeviceInformationCollectionVtbl async_vtbl = {
        .QueryInterface = async_query_interface,
        .AddRef = async_add_ref,
        .Release = async_release,
        .Invoke = async_invoke,
    };
    async_op.lpVtbl = &async_vtbl;
    hr = async_dev_collection->lpVtbl->put_Completed(async_dev_collection, &async_op);

For the example that I found my answer, it uses this method to create a __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController object, anyone who interested or still don't understand from my answer can have a look.

Leslie
  • 11
  • 3
  • That works, sure, but it glosses over some vitally important details: `1` The actual names of the functions are not important. The ones you are using seem to have been generated by tools. `2` More importantly, the IID's for generic interfaces are *calculated*. They aren't just randomly generated, but rather derived from the IID's of the types involved. – IInspectable Jun 12 '21 at 10:49
0

Basically I've created a small infrastructure as demonstrated in this sample for consuming UWP with plain C.

For implementing your own handlers you need several steps.

1st declare your object structure - you could implement several interfaces with a single object. Don't forget to add space for the end mark.

struct bthgenhandler {
    struct stdifaceopts;
    __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CBluetoothDevice;
    struct stdiftrailer;
    char* markend;
};

For a simple interface you can just embed the structures inside (and if you are using MSVC).

2nd initialize your interfaces vtable and what it implements (while for the second you will need an extra terminating element):

    static __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CBluetoothDeviceVtbl bthfromaddrhandlervtbl = { QueryInterface, AddRef, Release, bthgetRfcommsrvcsInvoke };
    
    static const IID* bthgetRfcommsrvcsimplements[] = { &IID_IUnknown, &IID___FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CRfcomm__CRfcommDeviceServicesResult, 0 };

3rd Create your actual object - you can use malloc too - if you have decided to embed any data (after the markend - set shouldbefreed and your initial objects count to 0 in order for your object to be automatically released):

static struct bthgenhandler bthgetRfcommsrvcshandler = { .lpVtbl = &bthfromaddrhandlervtbl, bthgetRfcommsrvcsimplements, 1, &bthgetRfcommsrvcshandler, };

4th Call the method requiring your callback object with any of your object interface vtbls (QueryInterface will find the right interface anyway - currently we only have 1 in this example though):

__FIAsyncOperation_1_Windows__CDevices__CBluetooth__CRfcomm__CRfcommDeviceServicesResult_put_Completed(asyncop, &bthgetRfcommsrvcshandler.lpVtbl);

Now for the IIDS - what I've come up is with a separate header where I copy the UIIDs from the C++ part of the header as strings (for example:)

GEN_IID_FROM_STRING("522c25d1-866b-5de4-bd8e-1feb5ae60d47", __FIAsyncOperationCompletedHandler_1_Windows__CDevices__CBluetooth__CRfcomm__CRfcommDeviceServicesResult)

Where "522c25d1-866b-5de4-bd8e-1feb5ae60d47" comes from windows.devices.bluetooth.h.

And lastly for activating objects you have two helpers - activateclassdirect and activateclasslight - sometimes if the first one doesn't work try the other one.

AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66