Windows Runtime (WinRT) APIs support
Windows 10 has been exposing APIs via Windows Runtime (WinRT) APIs. These APIs can also be used on Desktop, not just for Windows Store for UWP applications.
One example would be the Windows.Devices.Bluetooth Namespace which is described as:
The Windows.Devices.Bluetooth namespace defines a set of Windows Runtime API that allows UWP app and desktop apps to interact with Bluetooth devices
Qt 6.2 is using this with the MSVC compiler and has dropped the previous Win32 API version that MinGW was using.
This WinRT support is being offered by the The C++/WinRT language projection which is :
C++/WinRT is an entirely standard C++ language projection for Windows Runtime (WinRT) APIs, implemented as a header-file-based library, and designed to provide you with first-class access to the modern Windows API. With C++/WinRT, you can author and consume Windows Runtime APIs using any standards-compliant C++17 compiler.
Note that the project has a (closed) issue named MinGW GCC support, but since clang-cl can handle MSVC quirks, I think LLVM-MinGW would be in a better shape than the GCC MinGW.
This would be an extra argument for the QTBUG-107516 - Migrate from GCC MinGW to LLVM-MinGW migration.
I did some experiments with using the C++/WinRT headers with clang in mingw mode a couple of years ago. I don't remember the specifics, but I think I got it working, kinda, but it required a some (more or less messy?) hacks/tweaks back then. Then for async stuff, it also required some Clang feature which wasn't enabled by default (and I think didn't quite work out back then).
Hopefully the situation is better today and not worse - I'd appreciate if someone wants to pick up looking into it!
- Would it be as simple as adding winrt headers in mingw-w64?
- Does that header need to be synced with wine?
- Would it be as simple as adding winrt headers in mingw-w64?
- Does that header need to be synced with wine?
I'm not very familiar with these APIs, but I think it's kinda separate - it all originates from the same kinds of IDL interfaces, but iirc C++/WinRT provides a higher level wrapper for those APIs. In this case, I think it all builds on top of Microsoft headers (but ones with acceptable licenses actually) - I don't quite know how to integrate it on top of mingw-w64/wine headers. For what I've seen, you'd essentially take the cppwinrt folder from MS and integrate it in our headers (or just initially, just keeping it separate somewhere) and looking at what it takes to use it.
@cristianadam Can you provide the steps how to enable those bluetooth APIs in qt6? I can play with that but can not guarantee if it works or not. If it is off-topic here feel free to email me or ping me in any GitHub thread.
I've had a look at QtBase 6.4's config.summary file for MSVC 2019 and found this line:
cpp/winrt base ......................... yes
Then looked at this CMake feature probe, and it boils down to this CMakeLists.txt project:
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
project(test-winrt)
file(WRITE ${CMAKE_BINARY_DIR}/test-winrt.cpp [=[
#include <winrt/base.h>
int main(void)
{
return 0;
}
]=])
add_executable(test-winrt ${CMAKE_BINARY_DIR}/test-winrt.cpp)
target_link_libraries(test-winrt runtimeobject)
If this compiles, everything is fine.
I took the latest binary release from cpprt from https://github.com/microsoft/cppwinrt/releases/download/2.0.220912.1/Microsoft.Windows.CppWinRT.2.0.220912.1.nupkg. Extracted the nupkg (a zip file) archive and ran the cppwinrt.exe executable as:
$ cppwinrt.exe -in local -out .
which generated on my Windows 11 Machine a winrt directory with 1306 files.
Then tried to build the example above (after adding a target_include_directories(test-winrt PRIVATE ..) line) with:
MSVC 2022 and clang-cl 15.0 and got the following error:
FAILED: CMakeFiles/test-winrt.dir/test-winrt.cpp.obj
C:\PROGRA~1\LLVM\bin\clang-cl.exe /nologo -TP -IC:\Projects\cpprt\test\.. /DWIN32 /D_WINDOWS /GR /EHsc /Zi /Ob0 /Od /RTC1 -MDd -std:c++17 /showIncludes /FoCMakeFiles\test-winrt.dir\test-winrt.cpp.obj /FdCMakeFiles\test-winrt.dir\ -c -- C:\Projects\cpprt\test\build\test-winrt.cpp
In file included from C:\Projects\cpprt\test\build\test-winrt.cpp:1:
In file included from C:\Projects\cpprt\test\..\winrt/base.h:53:
c:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.34.31823\include\experimental/coroutine(30,2): error: The <experimental/coroutine>, <experimental/generator>, and <experimental/resumable> headers currently do not support Clang. You can define _SILENCE_CLANG_COROUTINE_MESSAGE to silence this message and acknowledge that this is unsupported.
#error The <experimental/coroutine>, <experimental/generator>, and <experimental/resumable> headers currently do not \
^
1 error generated.
ninja: build stopped: subcommand failed.
Afterwards tried it with LLVM-MinGW 15.0.0 and got this errors:
FAILED: CMakeFiles/test-winrt.dir/test-winrt.cpp.obj
C:\llvm-mingw\bin\c++.exe -IC:/Projects/cpprt/test/.. -std=gnu++17 -MD -MT CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -MF CMakeFiles\test-winrt.dir\test-winrt.cpp.obj.d -o CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -c C:/Projects/cpprt/test/build-llvm/test-winrt.cpp
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:58:35: error: no member named 'experimental' in namespace 'std'
using coroutine_handle = std::experimental::coroutine_handle<T>;
~~~~~^
C:/Projects/cpprt/test/../winrt/base.h:60:33: error: no member named 'experimental' in namespace 'std'
using suspend_always = std::experimental::suspend_always;
~~~~~^
C:/Projects/cpprt/test/../winrt/base.h:61:32: error: no member named 'experimental' in namespace 'std'
using suspend_never = std::experimental::suspend_never;
~~~~~^
C:/Projects/cpprt/test/../winrt/base.h:465:1: error: a type specifier is required for all declarations
WINRT_IMPL_LINK(LoadLibraryW, 4)
^
C:/Projects/cpprt/test/../winrt/base.h:462:42: note: expanded from macro 'WINRT_IMPL_LINK'
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:WINRT_IMPL_" #function "=" #function))
^
C:/Projects/cpprt/test/../winrt/base.h:465:1: error: use of undeclared identifier 'linker'
C:/Projects/cpprt/test/../winrt/base.h:462:59: note: expanded from macro 'WINRT_IMPL_LINK'
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:WINRT_IMPL_" #function "=" #function))
^
C:/Projects/cpprt/test/../winrt/base.h:465:33: error: expected ';' after top level declarator
WINRT_IMPL_LINK(LoadLibraryW, 4)
^
;
6 errors generated.
By setting set(CMAKE_CXX_STANDARD 20) I've got clang-cl to compile without problems. 🎉
With LLVM-MinGW, after doing an #if defined(_MSC_VER) around the WINRT_IMPL_LINK macros I've got the following errors:
C:\llvm-mingw\bin\c++.exe -IC:/Projects/cpprt/test/.. -std=gnu++20 -MD -MT CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -MF CMakeFiles\test-winrt.dir\test-winrt.cpp.obj.d -o CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -c C:/Projects/cpprt/test/build-llvm/test-winrt.cpp
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:697:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) require : require_one<D, I>...
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:710:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) base : base_one<D, I>...
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1740:27: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) type
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1752:27: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) type : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1764:27: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) type : inspectable_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1770:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IAgileObject : unknown_abi {};
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1772:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IAgileReference : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1777:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IMarshal : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1787:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IGlobalInterfaceTable : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1794:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IStaticLifetime : inspectable_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1800:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IStaticLifetimeCollection : inspectable_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1811:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IWeakReference : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1816:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IWeakReferenceSource : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1821:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IRestrictedErrorInfo : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1827:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IErrorInfo : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1836:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) ILanguageExceptionErrorInfo2 : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1846:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IContextCallback : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1851:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IServerSecurity : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1859:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IBufferByteAccess : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:1864:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) IMemoryBufferByteAccess : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:5322:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) variadic_delegate_abi : unknown_abi
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:5382:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) delegate_base : Windows::Foundation::IUnknown
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:5432:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) delegate : impl::delegate_base<void, Args...>
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:5438:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) delegate<R(Args...)> : impl::delegate_base<R, Args...>
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:6110:32: error: use of undeclared identifier '__iso_volatile_load32'
int32_t const result = __iso_volatile_load32(reinterpret_cast<int32_t const volatile*>(target));
^
C:/Projects/cpprt/test/../winrt/base.h:6111:9: error: use of undeclared identifier '_ARM64_BARRIER_ISH'
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
^
C:/Projects/cpprt/test/../winrt/base.h:6098:59: note: expanded from macro 'WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER'
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
^
C:/Projects/cpprt/test/../winrt/base.h:6126:32: error: use of undeclared identifier '__iso_volatile_load64'
int64_t const result = __iso_volatile_load64(target);
^
C:/Projects/cpprt/test/../winrt/base.h:6127:9: error: use of undeclared identifier '_ARM64_BARRIER_ISH'
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
^
C:/Projects/cpprt/test/../winrt/base.h:6098:59: note: expanded from macro 'WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER'
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
^
C:/Projects/cpprt/test/../winrt/base.h:6202:13: error: use of undeclared identifier '_InterlockedIncrement64'
_InterlockedIncrement64((int64_t*)&m_count);
^
C:/Projects/cpprt/test/../winrt/base.h:6213:13: error: use of undeclared identifier '_InterlockedDecrement64'
_InterlockedDecrement64((int64_t*)&m_count);
^
C:/Projects/cpprt/test/../winrt/base.h:6248:22: error: use of undeclared identifier '_InterlockedCompareExchange128'
if (1 == _InterlockedCompareExchange128((int64_t*)this, 0, 0, (int64_t*)¤t_value))
^
C:/Projects/cpprt/test/../winrt/base.h:6330:32: error: use of undeclared identifier '_InterlockedCompareExchangePointer'
if (nullptr == _InterlockedCompareExchangePointer(reinterpret_cast<void**>(&m_value.object), *reinterpret_cast<void**>(&object), nullptr))
^
C:/Projects/cpprt/test/../winrt/base.h:7269:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) root_implements_composing_outer
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:7277:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) root_implements_composing_outer<true>
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:7295:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) root_implements_composable_inner
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:7305:23: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
struct __declspec(empty_bases) root_implements_composable_inner<D, true> : producer<D, INonDelegatingInspectable>
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:7320:23: warning: unknown attribute 'novtable' ignored [-Wunknown-attributes]
struct __declspec(novtable) root_implements
^~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:8080:22: warning: unknown attribute 'empty_bases' ignored [-Wunknown-attributes]
class __declspec(empty_bases) produce_dispatch_to_overridable_base
^~~~~~~~~~~
<built-in>:447:38: note: expanded from here
#define __declspec(a) __attribute__((a))
^
30 warnings and 8 errors generated.
ninja: build stopped: subcommand failed.
C:/Projects/cpprt/test/../winrt/base.h:6111:9: error: use of undeclared identifier '_ARM64_BARRIER_ISH'
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
^
C:/Projects/cpprt/test/../winrt/base.h:6098:59: note: expanded from macro 'WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER'
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
^
Hmm, are you compiling for arm64, or does this expand to something that includes code for all architectures, with the assumption that winnt.h provides lots of declarations for all architectures and only some of these codepaths actually ends up emitted? We're lacking these corresponding defines in our headers, but this probably shouldn't be very hard to implement.
In file included from C:/Projects/cpprt/test/build-llvm/test-winrt.cpp:1:
C:/Projects/cpprt/test/../winrt/base.h:6110:32: error: use of undeclared identifier '__iso_volatile_load32'
int32_t const result = __iso_volatile_load32(reinterpret_cast<int32_t const volatile*>(target));
^
This is also something that normally only is used on arm/arm64. IIRC these are compiler intrinsics in MSVC mode, while for mingw we usually implement them with inline asm or similar in headers. I'm not entirely sure for this one, whether we can do that or whether it needs to be a compiler intrinsic...
C:/Projects/cpprt/test/../winrt/base.h:6202:13: error: use of undeclared identifier '_InterlockedIncrement64'
_InterlockedIncrement64((int64_t*)&m_count);
^
This one should in generaly definitely be available - but maybe might need an explicit include of <intrin.h> somewhere?
Hmm, are you compiling for arm64
Yes, I am running the test on a Windows11 Arm64 laptop.
Hmm, are you compiling for arm64
Yes, I am running the test on a Windows11 Arm64 laptop.
Oh, ok, I see. It's possible that there are fewer such missing bits in the headers if building for x86_64, but we're of course interested in fixing all these to make things work for all arches.
I am completely clueless about C++/WinRT. Is it a fully header-based wrapper around an underlying "WinRT API" that is exposed as COM interfaces?
I am completely clueless about C++/WinRT. Is it a fully header-based wrapper around an underlying "WinRT API" that is exposed as COM interfaces?
Yup, pretty much. There's tooling (which unfortunately exists as executables, which don't run well on other OSes since they use lots of modern APIs that wine doesn't implement) that can read metadata files and generate these modern C++ headers that implement the APIs specified in the WinRT metadata.
So I downloaded the nuget package and generated the headers with cppwinrt.exe, then tried to compile a sample code based on https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/get-started#a-cwinrt-quick-start. I was able to get it to build and run with some hacks using llvm-mingw.
main.cpp:
//#include <windows.h>
#include <intrin.h>
// Copied from `InterlockedCompareExchange128` in `mingw-w64-tools/widl/include/winnt.h`
static __attribute__((always_inline)) unsigned char _InterlockedCompareExchange128( volatile __int64 *dest, __int64 xchg_high, __int64 xchg_low, __int64 *compare )
{
#ifdef __x86_64__
unsigned char ret;
__asm__ __volatile__( "lock cmpxchg16b %0; setz %b2"
: "=m" (dest[0]), "=m" (dest[1]), "=r" (ret),
"=a" (compare[0]), "=d" (compare[1])
: "m" (dest[0]), "m" (dest[1]), "3" (compare[0]), "4" (compare[1]),
"c" (xchg_high), "b" (xchg_low) );
return ret;
#else
return __sync_bool_compare_and_swap( (__int128 *)dest, *(__int128 *)compare, ((__int128)xchg_high << 64) | xchg_low );
#endif
}
// Sample code from https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/get-started#a-cwinrt-quick-start
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
int main()
{
winrt::init_apartment();
Uri rssFeedUri{ L"https://news.ycombinator.com/rss" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed = syndicationClient.RetrieveFeedAsync(rssFeedUri).get();
for (const SyndicationItem syndicationItem : syndicationFeed.Items())
{
winrt::hstring titleAsHstring = syndicationItem.Title().Text();
std::wcout << titleAsHstring.c_str() << std::endl;
}
}
Patch to winrt headers: (haven't actually tested the i386 block)
diff --git a/base.h b/base.h
index defe873..590bb4c 100644
--- a/base.h
+++ b/base.h
@@ -480,6 +480,7 @@ extern "C"
int32_t __stdcall WINRT_GetActivationFactory(void* classId, void** factory) noexcept;
}
+#ifdef _MSC_VER
#ifdef _M_HYBRID
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:#WINRT_IMPL_" #function "@" #count "=#" #function "@" #count))
#elif _M_ARM64EC
@@ -489,6 +490,13 @@ extern "C"
#else
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:WINRT_IMPL_" #function "=" #function))
#endif
+#elif defined(__GNUC__)
+# if defined(__x86_64__)
+# define WINRT_IMPL_LINK(function, count) __asm__(".weak WINRT_IMPL_" #function "\n.set WINRT_IMPL_" #function ", " #function);
+# elif defined(__i386__)
+# define WINRT_IMPL_LINK(function, count) __asm__(".weak _WINRT_IMPL_" #function "@" #count "\n.set _WINRT_IMPL_" #function "@" #count ", _" #function "@" #count);
+# endif
+#endif
WINRT_IMPL_LINK(LoadLibraryW, 4)
WINRT_IMPL_LINK(FreeLibrary, 4)
Build:
> path\to\llvm-mingw-20220802-ucrt-x86_64\bin\clang++.exe -std=c++20 -fdeclspec -U__declspec -lole32 -loleaut32 -g -O2 -o main.exe main.cpp -Ipath\to\parent\dir\of\winrt
There are a lot of warnings with unsupported __declspec, which you can suppress with -Wno-ignored-attributes for now...
There are two unsupported __declspecs:
__declspec(empty_bases)- I think this can simply be removed when compiling in MinGW mode.__declspec(novtable)- I don't think there is an equivalent for this in MinGW mode. If we don't have this would it bloat the resulting binary with unused vtables?
The compile flags -fdeclspec -U__declspec can be removed actually, since the remaining attribute used (selectany) is supported by __attribute__.
Something else we might have to consider is that the COM interfaces defined in the headers might have subtle bugs (ABI incompatibilities) when compiled with MinGW ABI. This may be a problem, or maybe not. (Frankly I'm not familiar with COM to know.)
Something else we might have to consider is that the COM interfaces defined in the headers might have subtle bugs (ABI incompatibilities) when compiled with MinGW ABI. This may be a problem, or maybe not. (Frankly I'm not familiar with COM to know.)
Indeed, there are some such known cases - I don't know if they crop up in the C++/WinRT headers or not though.
See WIDL_EXPLICIT_AGGREGATE_RETURNS in the mingw headers for this case. I think it would be fairly trivial to fix Clang to match MSVC in this aspect here (since all the code already is there, and it's just a matter of picking one behaviour or another). Fixing that would be a subtle C++ ABI break, breaking compatibility with any existing C++ code with such APIs - but I wonder if it might be worth doing anyway. But if we do it, it would be great if GCC could follow suit at the same time, but I'm not sure how easy/hard it is to fix in GCC. (See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52792 for what I think is a GCC bug report on that matter.)
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64384 is another GCC bug report about the same issue.
@alvinhochun thank you for the research.
I've put it together in a:
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(test-winrt)
file(WRITE ${CMAKE_BINARY_DIR}/test-winrt.cpp [=[
//#include <windows.h>
#include <intrin.h>
// Copied from `InterlockedCompareExchange128` in `mingw-w64-tools/widl/include/winnt.h`
static __attribute__((always_inline)) unsigned char _InterlockedCompareExchange128( volatile __int64 *dest, __int64 xchg_high, __int64 xchg_low, __int64 *compare )
{
#ifdef __x86_64__
unsigned char ret;
__asm__ __volatile__( "lock cmpxchg16b %0; setz %b2"
: "=m" (dest[0]), "=m" (dest[1]), "=r" (ret),
"=a" (compare[0]), "=d" (compare[1])
: "m" (dest[0]), "m" (dest[1]), "3" (compare[0]), "4" (compare[1]),
"c" (xchg_high), "b" (xchg_low) );
return ret;
#else
return __sync_bool_compare_and_swap( (__int128 *)dest, *(__int128 *)compare, ((__int128)xchg_high << 64) | xchg_low );
#endif
}
// Sample code from https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/get-started#a-cwinrt-quick-start
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
int main()
{
winrt::init_apartment();
Uri rssFeedUri{ L"https://news.ycombinator.com/rss" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed = syndicationClient.RetrieveFeedAsync(rssFeedUri).get();
for (const SyndicationItem syndicationItem : syndicationFeed.Items())
{
winrt::hstring titleAsHstring = syndicationItem.Title().Text();
std::wcout << titleAsHstring.c_str() << std::endl;
}
}
]=])
add_executable(test-winrt ${CMAKE_BINARY_DIR}/test-winrt.cpp)
target_include_directories(test-winrt PRIVATE ..)
target_compile_options(test-winrt PRIVATE -Wno-ignored-attributes -Wno-unknown-attributes)
add_custom_target(run COMMAND test-winrt)
I've tested on x64 and worked as expected with LLVM-MinGW 15.0.0.
On Arm64 we're down to these errors:
C:\llvm-mingw\bin\c++.exe -IC:/Projects/cppwinrt/test/.. -Wno-ignored-attributes -Wno-unknown-attributes -std=gnu++20 -MD -MT CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -MF CMakeFiles\test-winrt.dir\test-winrt.cpp.obj.d -o CMakeFiles/test-winrt.dir/test-winrt.cpp.obj -c C:/Projects/cppwinrt/test/build/test-winrt.cpp
In file included from C:/Projects/cppwinrt/test/build/test-winrt.cpp:22:
In file included from C:/Projects/cppwinrt/test/../winrt/Windows.Foundation.Collections.h:6:
C:/Projects/cppwinrt/test/../winrt/base.h:6116:32: error: use of undeclared identifier '__iso_volatile_load32'
int32_t const result = __iso_volatile_load32(reinterpret_cast<int32_t const volatile*>(target));
^
C:/Projects/cppwinrt/test/../winrt/base.h:6117:9: error: use of undeclared identifier '_ARM64_BARRIER_ISH'
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
^
C:/Projects/cppwinrt/test/../winrt/base.h:6104:59: note: expanded from macro 'WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER'
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
^
C:/Projects/cppwinrt/test/../winrt/base.h:6132:32: error: use of undeclared identifier '__iso_volatile_load64'
int64_t const result = __iso_volatile_load64(target);
^
C:/Projects/cppwinrt/test/../winrt/base.h:6133:9: error: use of undeclared identifier '_ARM64_BARRIER_ISH'
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
^
C:/Projects/cppwinrt/test/../winrt/base.h:6104:59: note: expanded from macro 'WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER'
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
^
4 errors generated.
ninja: build stopped: subcommand failed.
I've tried to find a fix but no luck 😔
Those ARM64 intrinsics and values are documented on https://learn.microsoft.com/en-us/cpp/intrinsics/arm64-intrinsics?view=msvc-170. We probably need to implement them in mingw-w64. The same applies to _InterlockedCompareExchange128 too actually (https://learn.microsoft.com/en-us/cpp/intrinsics/interlockedcompareexchange128?view=msvc-170). I just defined the function in the sample code as a hack.
I've put the code in Godbolt
#include <cstdint>
#include <intrin.h>
#if defined _M_ARM
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM_BARRIER_ISH));
#elif defined _M_ARM64
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
#endif
inline int32_t interlocked_read_32(int32_t const volatile* target) noexcept
{
#if defined _M_IX86 || defined _M_X64
int32_t const result = *target;
_ReadWriteBarrier();
return result;
#elif defined _M_ARM || defined _M_ARM64
int32_t const result = __iso_volatile_load32(reinterpret_cast<int32_t const volatile*>(target));
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
return result;
#else
#error Unsupported architecture
#endif
}
#if defined _WIN64
inline int64_t interlocked_read_64(int64_t const volatile* target) noexcept
{
#if defined _M_X64
int64_t const result = *target;
_ReadWriteBarrier();
return result;
#elif defined _M_ARM64
int64_t const result = __iso_volatile_load64(target);
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER
return result;
#else
#error Unsupported architecture
#endif
}
#endif
int main()
{
int32_t p32 = 0;
int64_t p64 = 0;
int32_t i32 = interlocked_read_32(&p32);
int64_t i64 = interlocked_read_64(&p64);
}
dissasenbly:
;ARM64
;Flags[SingleProEpi] functionLength[36] RegF[0] RegI[0] H[0] frameChainReturn[UnChained] frameSize[16]
;Flags[SingleProEpi] functionLength[36] RegF[0] RegI[0] H[0] frameChainReturn[UnChained] frameSize[16]
;Flags[SingleProEpi] functionLength[64] RegF[0] RegI[0] H[0] frameChainReturn[Chained] frameSize[48]
|int interlocked_read_32(int const volatile *)| PROC ; interlocked_read_32
|$LN3|
sub sp,sp,#0x10
str x0,[sp,#8]
ldr x8,[sp,#8]
ldr w8,[x8]
str w8,[sp]
dmb ish
ldr w0,[sp]
add sp,sp,#0x10
ret
ENDP ; |int interlocked_read_32(int const volatile *)|, interlocked_read_32
|__int64 interlocked_read_64(__int64 const volatile *)| PROC ; interlocked_read_64
|$LN3|
sub sp,sp,#0x10
str x0,[sp]
ldr x8,[sp]
ldr x8,[x8]
str x8,[sp,#8]
dmb ish
ldr x0,[sp,#8]
add sp,sp,#0x10
ret
ENDP ; |__int64 interlocked_read_64(__int64 const volatile *)|, interlocked_read_64
|main| PROC
|$LN3|
stp fp,lr,[sp,#-0x30]!
mov fp,sp
mov w8,#0
str w8,[sp,#0x10]
mov x8,#0
str x8,[sp,#0x18]
add x0,sp,#0x10
bl |int interlocked_read_32(int const volatile *)|
mov w0,w0
str w0,[sp,#0x14]
add x0,sp,#0x18
bl |__int64 interlocked_read_64(__int64 const volatile *)|
str x0,[sp,#0x20]
mov w0,#0
ldp fp,lr,[sp],#0x30
ret
ENDP ; |main|
Now all I would need is to convert the disassembly into inline C/C++ assembly code... 😅
For the ISO volatile loads/stores, if I understand correctly, e.g. __iso_volatile_load32(__int32 const volatile *ptr) could be implemented e.g. like this for mingw targets:
__int32 SOME_INLINE_ATTRIBUTE __iso_volatile_load32(__int32 const volatile *ptr) {
return *ptr;
}
If I understand correctly, the reason for these intrinsics to exist in the first place, is that MSVC traditionally has treated volatile loads/stores differently from the spec, making them atomic, while the C standards say they aren't. With these __iso_volatile_* intrinsics, you'd get the same non-atomic volatile operations as the standard says you'd get for regular volatile operations, even when using the MSVC default settings. (With MSVC you can also pass /volatile:iso to turn every volatile access into the same as these intrinsics; see e.g. https://learn.microsoft.com/en-us/cpp/intrinsics/arm64-intrinsics?view=msvc-170#IsoVolatileLoadStore.) As far as I understand, the difference between atomic/non-atomic for these isn't directly visible in the final assembly output here, but it is visible in e.g. the output LLVM IR from the compiler.
For mingw mode, the default is to have the standard volatile semantics (and running in the MSVC mode isn't expected), so just turning these into regular language-level volatile loads/stores should be fine. I guess they could be stashed along with other inline implementations of MSVC intrinsics in e.g. crt/intrin.h.
I've tried a new Godbolt version for arm64 gcc and with the base.h diff:
diff -Naur winrt-orig/base.h winrt/base.h
--- winrt-orig/base.h 2022-10-12 13:59:50.726224100 +0200
+++ winrt/base.h 2022-10-12 14:03:56.464396400 +0200
@@ -452,6 +452,7 @@
int32_t __stdcall WINRT_GetActivationFactory(void* classId, void** factory) noexcept;
}
+#if defined(_MSC_VER)
#ifdef _M_HYBRID
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:#WINRT_IMPL_" #function "@" #count "=#" #function "@" #count))
#elif _M_ARM64EC
@@ -461,6 +462,13 @@
#else
#define WINRT_IMPL_LINK(function, count) __pragma(comment(linker, "/alternatename:WINRT_IMPL_" #function "=" #function))
#endif
+#elif defined(__GNUC__)
+# if defined(__x86_64__) || defined(__aarch64__)
+# define WINRT_IMPL_LINK(function, count) __asm__(".weak WINRT_IMPL_" #function "\n.set WINRT_IMPL_" #function ", " #function);
+# elif defined(__i386__)
+# define WINRT_IMPL_LINK(function, count) __asm__(".weak _WINRT_IMPL_" #function "@" #count "\n.set _WINRT_IMPL_" #function "@" #count ", _" #function "@" #count);
+# endif
+#endif
WINRT_IMPL_LINK(LoadLibraryW, 4)
WINRT_IMPL_LINK(FreeLibrary, 4)
@@ -6091,7 +6099,11 @@
#if defined _M_ARM
#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM_BARRIER_ISH));
#elif defined _M_ARM64
-#define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
+# if defined __GNUC__
+# define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER __asm__ __volatile__ ("dmb ish");
+# else
+# define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH));
+# endif
#endif
namespace winrt::impl
and the new functions:
#if defined(__GNUC__) && defined(__aarch64__)
static __attribute__((always_inline)) __int32 __iso_volatile_load32(__int32 const volatile *ptr) {
return *ptr;
}
static __attribute__((always_inline)) __int64 __iso_volatile_load64(__int64 const volatile *ptr) {
return *ptr;
}
#endif
The example from above compiled and ran on Windows 11 arm64! 🎉
I submitted a PR: https://github.com/microsoft/cppwinrt/pull/1200
Regarding WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER, do you think _ARM64_BARRIER_ISH and other values listed in https://learn.microsoft.com/en-us/cpp/intrinsics/arm64-intrinsics?view=msvc-170#BarrierRestrictions should be provided by mingw-w64 too?
I submitted a PR: microsoft/cppwinrt#1200
Great! (Didn’t check the contents of it at the moment.)
Regarding
WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER, do you think_ARM64_BARRIER_ISHand other values listed in https://learn.microsoft.com/en-us/cpp/intrinsics/arm64-intrinsics?view=msvc-170#BarrierRestrictions should be provided by mingw-w64 too?
Yes, we should provide those too. And then we should probably provide the __dmb intrinsic too.
Yes, we should provide those too. And then we should probably provide the
__dmbintrinsic too.
__dmb is implemented, as seen at https://clang.llvm.org/docs/LanguageExtensions.html#arm-aarch64-language-extensions
But I didn't know how to use it with ish, other than __asm__("dmb ish").
Yes, we should provide those too. And then we should probably provide the
__dmbintrinsic too.
__dmbis implemented, as seen at https://clang.llvm.org/docs/LanguageExtensions.html#arm-aarch64-language-extensions
It is implemented in Clang, but it’s only exposed if building in MSVC mode (or maybe with -fms-extensions). MSVC specific intrinsics usually aren’t exposed by GCC or Clang in mingw mode, but are reimplemented in mingw headers with inline assembly.
We could just change the cppwinrt headers to use __sync_bool_compare_and_swap and the arm asm directly for __GNUC__ instead of needing to add _InterlockedCompareExchange128 and the other stuff to intrin.h. It'd be nice to make the headers usable on earlier versions of mingw-w64 too. I will submit a PR for this.
FYI I am working on adding a CMake build for cppwinrt itself and also trying to get the tests to work. If things go well there should soon be CI checks upstream to make sure things will continue to be compatible with llvm-mingw.
Hello, I'm interested in this, seems that it might allow me to compile some existing C++ code with Clang+MinGW. How can I test this WinRT support? Thanks.
Hello, I'm interested in this, seems that it might allow me to compile some existing C++ code with Clang+MinGW. How can I test this WinRT support? Thanks.
You need to build cppwinrt.exe from master, either with the official VS project + VS2022 or the CMake build + llvm-mingw, then generate the C++/WinRT projection headers with it and use them to compile your code.
generate the C++/WinRT projection headers with it
Is there a documentation about that step?
generate the C++/WinRT projection headers with it
Is there a documentation about that step?
See my reponse at https://github.com/mstorsjo/llvm-mingw/issues/307#issuecomment-1273726972. It's just a matter of running cppwinrt.exe:
$ cppwinrt.exe -in local -out .