mstorsjo / llvm-mingw

An LLVM/Clang/LLD based mingw-w64 toolchain
Other
1.76k stars 176 forks source link

Windows Runtime (WinRT) APIs support #307

Open cristianadam opened 1 year ago

cristianadam commented 1 year ago

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.

mstorsjo commented 1 year ago

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!

Biswa96 commented 1 year ago
  1. Would it be as simple as adding winrt headers in mingw-w64?
  2. Does that header need to be synced with wine?
mstorsjo commented 1 year ago
  • 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.

Biswa96 commented 1 year ago

@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.

cristianadam commented 1 year ago

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.

cristianadam commented 1 year ago

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.
cristianadam commented 1 year ago

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*)&current_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.
mstorsjo commented 1 year ago
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?

cristianadam commented 1 year ago

Hmm, are you compiling for arm64

Yes, I am running the test on a Windows11 Arm64 laptop.

mstorsjo commented 1 year ago

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.

alvinhochun commented 1 year ago

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?

mstorsjo commented 1 year ago

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.

alvinhochun commented 1 year ago

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...

alvinhochun commented 1 year ago

There are two unsupported __declspecs:

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.)

mstorsjo commented 1 year ago

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.)

mstorsjo commented 1 year ago

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64384 is another GCC bug report about the same issue.

cristianadam commented 1 year ago

@alvinhochun thank you for the research.

I've put it together in a:

CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.16) set(CMAKE_CXX_STANDARD 20) project(test-winrt) file(WRITE ${CMAKE_BINARY_DIR}/test-winrt.cpp [=[ //#include #include // 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 #include #include 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 πŸ˜”

alvinhochun commented 1 year ago

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.

cristianadam commented 1 year ago

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... πŸ˜…

mstorsjo commented 1 year ago

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.

cristianadam commented 1 year ago

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! πŸŽ‰

alvinhochun commented 1 year ago

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?

mstorsjo commented 1 year ago

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_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?

Yes, we should provide those too. And then we should probably provide the __dmb intrinsic too.

cristianadam commented 1 year ago

Yes, we should provide those too. And then we should probably provide the __dmb intrinsic 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").

mstorsjo commented 1 year ago

Yes, we should provide those too. And then we should probably provide the __dmb intrinsic too.

__dmb is 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.

alvinhochun commented 1 year ago

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.

justanotheranonymoususer commented 1 year ago

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.

alvinhochun commented 1 year ago

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.

Biswa96 commented 1 year ago

generate the C++/WinRT projection headers with it

Is there a documentation about that step?

cristianadam commented 1 year ago

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 .
justanotheranonymoususer commented 1 year ago

Thanks. Here's what I did, maybe it will be helpful to somebody:

Did I do it right? Could any of you perhaps upload your winrt folder for me to do a sanity check comparison?

cristianadam commented 1 year ago

Thanks. Here's what I did, maybe it will be helpful to somebody:

  • Clone https://github.com/microsoft/cppwinrt
  • Run x64 Native Tools Command Prompt for VS 2022 from Start
  • cd to the cppwinrt folder and run build_test_all.cmd per the project's README
  • Go to _build\x64\Release and grab the winrt folder

Did I do it right? Could any of you perhaps upload your winrt folder for me to do a sanity check comparison?

Looks about right. I did the same thing, then I compiled the following CMakeLists.txt file:

cmake_minimum_required(VERSION 3.16)

set(CMAKE_CXX_STANDARD 20)

project(test-winrt)

file(WRITE ${CMAKE_BINARY_DIR}/test-winrt.cpp [=[
// 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 ../repo/_build/x64/Release)

add_custom_target(run COMMAND test-winrt)

With LLVM-MinGW 15.0.0 (arm64) and it all worked without any hacks!

$ cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
-- The C compiler identification is Clang 15.0.0
-- The CXX compiler identification is Clang 15.0.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/llvm-mingw/bin/cc.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/llvm-mingw/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Projects/cpprt/test/build

c:\Projects\cpprt\test
$ cmake --build build\ --target run
[3/3] cmd.exe /C "cd /D C:\Projects\cpprt\test\build && C:\Projects\cpprt\test\build\test-winrt.exe"
Making a Go program faster with a one-character change
Optimizations can have unexpectedly large effects when combined with caches
Typing

@alvinhochun Hat off for pulling this out! Thank you!

@mstorsjo can we get a winrt directory bundled with the next release? πŸ™

mstorsjo commented 1 year ago

@mstorsjo can we get a winrt directory bundled with the next release? πŸ™

Sure, we'll just have to figure out the cross-build story. (All releases are cross-built - out of the downloadable releases, no files of those are ever built on Windows.)

If I understand correctly, all the headers generated by the cppwinrt.exe tool are properly redistributable with a permissive enough license. This is totally mandatory and a dealbreaker if it isn't.

Then, if I understand correctly, the cppwinrt.exe tool only works on Windows, and it reads the Windows-bundled winmd files to generate headers. So if you run the tool on an older version of Windows, you'd get different/older winrt headers generated. Is this correct?

In that case, we'd need to set up a repo somewhere (github.com/mingw-w64 maybe?), with the generated output of the tool, which we then can download on other OSes, use for cross compilation, and bundle in llvm-mingw. Then someone can periodically update that repo, and we'd make llvm-mingw bundle newer versions of it in each release.

WDYT about that, @alvinhochun?

justanotheranonymoususer commented 1 year ago

@cristianadam I tried the sample you posted from learn.microsoft.com, but without cmake. I moved the winrt to include of the compiler, then ran the following:

Compiler\bin\g++.exe -std=c++20 -O2 test.cpp -target x86_64-w64-mingw32 -o test.exe

First I tried with c++17, it emitted errors like the following, even though cppwinrt's README says C++17 is what's required.

error: no member named 'experimental' in namespace 'std'
    using coroutine_handle = std::experimental::coroutine_handle<T>;

Then, after changing it to c++20, I got the following:

ld.lld: error: undefined symbol: _WINRT_IMPL_SetErrorInfo@8
>>> referenced by C:/Users/1/AppData/Local/Temp/test-137e7d.o

ld.lld: error: undefined symbol: _SetErrorInfo@8
>>> referenced by C:/Users/1/AppData/Local/Temp/test-137e7d.o:(__ZN5winrt13hresult_error37fallback_RoOriginateLanguageExceptionEiPvS1_@12)
>>> referenced by C:/Users/1/AppData/Local/Temp/test-137e7d.o:(winrt::hresult winrt::impl::get_runtime_activation_factory_impl<false>(winrt::param::hstring const&, winrt::guid const&, void**))
>>> referenced by C:/Users/1/AppData/Local/Temp/test-137e7d.o:(winrt::hresult winrt::impl::get_runtime_activation_factory_impl<true>(winrt::param::hstring const&, winrt::guid const&, void**))

ld.lld: error: undefined symbol: _WINRT_IMPL_GetErrorInfo@8
>>> referenced by C:/Users/1/AppData/Local/Temp/test-137e7d.o

...

Any ideas?

Edit: I added -lOleAut32 -lole32 and now it works! πŸŽ‰

Edit 2: I added #include <winrt/Windows.UI.Xaml.Media.h> and now I'm getting:

In file included from c:\Compiler\test.cpp:5:
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.Media.h:9:       
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.h:20:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Composition.2.h:11:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Composition.1.h:6:
c:/Compiler/include/winrt/impl/Windows.UI.Composition.0.h:1525:106: error: no    
      type named 'float3x2' in namespace 'winrt::Windows::Foundation::Numerics'
            virtual int32_t __stdcall SetMatrix3x2Parameter(void*, winrt::Windows::Foundation::Numerics::float...     
                                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

Is it something with my setup?

alvinhochun commented 1 year ago

I'm too tired to respond properly to the other replies (will do that tomorrow), but I can address this now:

Edit 2: I added #include <winrt/Windows.UI.Xaml.Media.h> and now I'm getting:

In file included from c:\Compiler\test.cpp:5:
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.Media.h:9:       
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.h:20:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Composition.2.h:11:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Composition.1.h:6:
c:/Compiler/include/winrt/impl/Windows.UI.Composition.0.h:1525:106: error: no    
      type named 'float3x2' in namespace 'winrt::Windows::Foundation::Numerics'
            virtual int32_t __stdcall SetMatrix3x2Parameter(void*, winrt::Windows::Foundation::Numerics::float...     
                                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

Is it something with my setup?

You just ran into a case of a missing mingw-w64 header, which I actually added specifically for cppwinrt a while ago with https://github.com/mingw-w64/mingw-w64/commit/2b6272b31132e156dd1fc3722c1aa96b705a90dd. For now, you can download windowsnumerics.impl.h from that commit to give it a try.

justanotheranonymoususer commented 1 year ago

Yes, it helped, thanks! Next thing I got was:

In file included from c:\Compiler\test.cpp:61:
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.Markup.h:9:
In file included from c:/Compiler/include/winrt/Windows.UI.Xaml.h:30:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Xaml.Media.Animation.2.h:10:
In file included from c:/Compiler/include/winrt/impl/Windows.UI.Xaml.Media.Animation.1.h:6:       
c:/Compiler/include/winrt/impl/Windows.UI.Xaml.Media.Animation.0.h:2306:54: error: too many       
      arguments provided to function-like macro invocation
            virtual int32_t __stdcall GetCurrentTime(int64_t*) noexcept = 0;
                                                     ^
c:/Compiler/include/winbase.h:57:9: note: macro 'GetCurrentTime' defined here
#define GetCurrentTime() GetTickCount ()
        ^

I'm not the first one to encounter this. I added #undef GetCurrentTime before including winrt stuff and it compiled! I'll keep experimenting but it looks promising, thanks guys.

Biswa96 commented 1 year ago

can we get a winrt directory bundled with the next release?

If you want winrt headers I can try to add it in msys2/mingw-packages. That way llvm-mingw would not be bloated with extra headers. But cross compilation would not be straight-forward.

mstorsjo commented 1 year ago

can we get a winrt directory bundled with the next release?

If you want winrt headers I can try to add it in msys2/mingw-packages. That way llvm-mingw would not be bloated with extra headers. But cross compilation would not be straight-forward.

For the llvm-mingw perspective, cross compilation is the primary concern, and I'm sure cross compiling users will want it in one form or another. But that doesn't exclude packaging it in msys2 in a way that directly uses cppwinrt.exe. (But that would be weird in a way if it ends up packaging stuff based on whatever was available on the runner that built that package.)

cristianadam commented 1 year ago

With the winrt directory copied into c:\llvm-mingw\include, the feature cxx20 enabled and this patch:

diff --git a/src/network/configure.cmake b/src/network/configure.cmake
index 0de16ae90b..7e613f579f 100644
--- a/src/network/configure.cmake
+++ b/src/network/configure.cmake
@@ -210,6 +210,7 @@ qt_config_compile_test(networklistmanager
     LABEL "Network List Manager"
     CODE
 "#include <netlistmgr.h>
+#include <ocidl.h>
 #include <wrl/client.h>

 int main(void)
diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
index 72023c6628..35ce6345ad 100644
--- a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
+++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
@@ -155,7 +155,7 @@ bool QNetworkListManagerEvents::checkBehindCaptivePortal()
             VariantInit(&variant);
             const auto scopedVariantClear = qScopeGuard([&variant]() { VariantClear(&variant); });

-            const wchar_t *versions[] = { NA_InternetConnectivityV6, NA_InternetConnectivityV4 };
+            const wchar_t *versions[] = { L"NA_InternetConnectivityV6", L"NA_InternetConnectivityV4" };
             for (const auto version : versions) {
                 hr = propertyBag->Read(version, &variant, nullptr);
                 if (SUCCEEDED(hr)
diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
index cf9a08ca84..aba8528fda 100644
--- a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
+++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
@@ -15,7 +15,7 @@
 #include <wrl/wrappers/corewrappers.h>
 #include <comdef.h>

-#if QT_CONFIG(cpp_winrt) && !defined(Q_CC_CLANG)
+#if QT_CONFIG(cpp_winrt)
 #define SUPPORTS_WINRT 1
 #endif

I was able to configure and build Qt6's qtbase.

  cpp/winrt base ......................... yes

and the plugins\networkinformation\qnetworklistmanager.dll got built and had references to winrt!

πŸŽ‰

alvinhochun commented 1 year ago

@mstorsjo can we get a winrt directory bundled with the next release? πŸ™

Sure, we'll just have to figure out the cross-build story. (All releases are cross-built - out of the downloadable releases, no files of those are ever built on Windows.)

If I understand correctly, all the headers generated by the cppwinrt.exe tool are properly redistributable with a permissive enough license. This is totally mandatory and a dealbreaker if it isn't.

My impression is they are MIT-licensed (https://github.com/microsoft/cppwinrt/issues/929) but the headers themselves don't include the licence header by default. If we do ship generated headers we may need to add that somehow. Cppwinrt actually has a -license option which may or may not be enough.

Then, if I understand correctly, the cppwinrt.exe tool only works on Windows, and it reads the Windows-bundled winmd files to generate headers. So if you run the tool on an older version of Windows, you'd get different/older winrt headers generated. Is this correct?

Cppwinrt can generate headers from the system winmd files provided in %SystemRoot%\System32\WinMetadata, that is if you specify -in local. But it can also use the winmd file included in the Windows SDK (e.g. C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.19041.0\Windows.winmd) by specifying either a full path to the file (or containing folder), sdk or a specific Windows SDK version, in which cppwinrt will automatically look for the latest or specified SDK install.

It's true that cppwinrt currently only builds and runs on Windows, but from what I've seen so far the only Windows-specific parts are the registry reads for finding the Windows SDK and some tinyxml xmllite usage for parsing Platforms\UAP\<version>\Platform.xml in the Windows SDK, so it should be possible to port it to Linux without too much trouble. (I don't plan to look into this yet though.)

In that case, we'd need to set up a repo somewhere (github.com/mingw-w64 maybe?), with the generated output of the tool, which we then can download on other OSes, use for cross compilation, and bundle in llvm-mingw. Then someone can periodically update that repo, and we'd make llvm-mingw bundle newer versions of it in each release.

WDYT about that, @alvinhochun?

I'm not sure yet, but here are some things to note:

mstorsjo commented 1 year ago

My impression is they are MIT-licensed (microsoft/cppwinrt#929) but the headers themselves don't include the licence header by default. If we do ship generated headers we may need to add that somehow. Cppwinrt actually has a -license option which may or may not be enough.

Ok, good.

Cppwinrt can generate headers from the system winmd files provided in %SystemRoot%\System32\WinMetadata, that is if you specify -in local. But it can also use the winmd file included in the Windows SDK (e.g. C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.19041.0\Windows.winmd) by specifying either a full path to the file (or containing folder), sdk or a specific Windows SDK version, in which cppwinrt will automatically look for the latest or specified SDK install.

Ah, ok. So if using an SDK as source, there's a fair bit more of reproducibility.

Still for the llvm-mingw build process, we probably don't want to have "fetch a WinSDK" as a step - so for that, and for many other consumers, having a repo with pregenerated headers probably is valuable.

It's true that cppwinrt currently only builds and runs on Windows, but from what I've seen so far the only Windows-specific parts are the registry reads for finding the Windows SDK and some tinyxml usage for parsing Platforms\UAP\<version>\Platform.xml in the Windows SDK, so it should be possible to port it to Linux without too much trouble. (I don't plan to look into this yet though.)

Hmm, ok. I think I tried executing binaries of it in Wine a couple years ago, and it failed due to using some WinRT style API (or API for dealing with WinRT style APIs?) that wasn't implemented back then, but maybe things have changed.

If it could be built into a native unix executable with some amount of effort, that would of course be the best.

In that case, we'd need to set up a repo somewhere (github.com/mingw-w64 maybe?), with the generated output of the tool, which we then can download on other OSes, use for cross compilation, and bundle in llvm-mingw. Then someone can periodically update that repo, and we'd make llvm-mingw bundle newer versions of it in each release. WDYT about that, @alvinhochun?

I'm not sure yet, but here are some things to note:

Yep, bundling the headers like the WinSDK does, sounds reasonable as a first step. Secondly, if the cppwinrt can be made a proper standalone native unix executable, it would indeed be good to ship that along as well.

Ideally maybe mingw-w64 (and/or wine?) should ship winmd files - as a future next step? I'm clearly not familiar enough with them, how they work, what they contain, and that whole ecosystem yet in any case.

  • So far we have only been considering the usage of the system WinRT APIs, which is sufficiently supported by shipping pre-generated cppwinrt headers. If we consider supporting using custom WinRT components, then we have to also ship the cppwinrt tool for generating compatible cppwinrt headers from the component's winmd file.

Ah, good point, I hadn't thought about that aspect yet. Yes, with that in mind, having the cppwinrt tool available would be good.

  • The headers of different components must be generated from the same cppwinrt version because things may rely on implementation details of the generated winrt/base.h. The headers contains built-in version checks, but...
  • When not specified, a dummy version number is used to build cppwinrt, which increases the chance of accidentally mixing incompatible cppwinrt headers. If we build cppwinrt ourselves, preferably we should follow the upstream versioning and remember to set the version number properly.

Thanks, that's good to keep in mind.

  • There are still outstanding cppwinrt test failures with Clang that needs to be looked into, so I would expect more updates incoming.

Yeah, clearly. With most things like this, there will always be things missing, but if whatever we have works for some use cases, it's usually better than nothing :-)

I'll probably make the next llvm-mingw release when LLVM 16.0.0 is released (probably with a prerelease from the first RC, at the end of January 2023, and final release around March), so there's still quite some time to get things to a usable-enough state until then.

Thanks for your efforts on this matter so far!

alvinhochun commented 1 year ago

the only Windows-specific parts are [...] some tinyxml usage

(Ouch I meant to say xmllite...)

alvinhochun commented 1 year ago

can we get a winrt directory bundled with the next release?

If you want winrt headers I can try to add it in msys2/mingw-packages. That way llvm-mingw would not be bloated with extra headers. But cross compilation would not be straight-forward.

@Biswa96 Since you are here, perhaps you might want to try packaging the cppwinrt tool for CLANG64. The tagged release https://github.com/microsoft/cppwinrt/releases/tag/2.0.221117.1 should be good for an initial version. Remember to pass -DCPPWINRT_BUILD_VERSION="2.0.221117.1" to CMake. It would be nice if you can also verify the tests under msys2.

alvinhochun commented 1 year ago

Some updates:

Status of C++/WinRT on mingw-w64

The current upstream HEAD (newer than release 2.0.221121.5) does work mostly fine on both Clang 15 (w/ libc++) and GCC 12. The exception is anything that involves coroutine -- there is a lot of potential for miscompilation, so I would advice to test things thoroughly and avoid -O3 for now. Clang 14 and GCC 11 can work, but their coroutine support is not as stable, so don't use coroutines at all if you must support these earlier compiler versions.

Other than that, there are still some more work needed to be done -- some test cases are still excluded for mingw-w64 which I haven't have time to start looking into fixing them properly, and I haven't looked into authoring/consuming custom WinRT components at all.

Otherwise, normal usage of the WinRT APIs provided by Windows should work without issues.

Delivery of the headers

My current idea is that the C++/WinRT headers should not be bundled with any mingw-w64 toolchains, following the reason why using the C++/WinRT headers in the official Windows SDK is discouraged. The tool or headers if bundled can rapidly become outdated, which is not something we want now when more fixes to upstream may still be needed in the near future.

Also, when using the official cppwinrt NuGet package with Visual Studio, there is a built-in mechanism that overrides the default include paths so that the newer generated C++/WinRT headers replaces the outdated WinSDK C++/WinRT headers completely to avoid confusion. The way llvm-mingw and other mingw-w64 toolchains are set up now do not support anything similar to that.

Therefore, at this time, instead of trying to have C++/WinRT included in llvm-mingw releases, I am experimenting (slowly) with providing it as a separate package instead and also trying to provide some sort of CMake build integration, in a separate project https://github.com/alvinhochun/mingw-w64-cppwinrt (Don't actually depend on it yet.) In the meantime you can compile the cppwinrt tool from master, then manually generate the headers with Windows.winmd from https://github.com/microsoft/windows-rs/tree/master/crates/libs/metadata/default which is available in MIT license, so there won't be any licensing issues unlike using the system local metadata or the ones from WinSDK.

driver1998 commented 1 year ago

We might also need a way to create winmd files ourselves. MIDL 3.0 is not open source, but C#/WinRT has cswinmd, which generates winmd from C# declarations. Rust for Windows also have similar support, but I don't know if it can be used separately. Of course the best would be upgrade widl from wine to support winmd output.

alvinhochun commented 1 year ago

We might also need a way to create winmd files ourselves.

Yes, that would be required for authoring your own components, but I am really putting this on low priority because I don't yet see the real-world use case for this.

(Though one can still use MIDL from the Windows SDK to make the winmd file. It sucks have to manually do it every time the IDL file is changed, but at least it's not fully impossible.)

MIDL 3.0 is not open source

Unfortunately. But at least MIDL 3.0 is quite well documented (unlike MIDL 2.0 / MIDLRT) which probably helps a bit.

, but C#/WinRT has cswinmd, which generates winmd from C# declarations. Rust for Windows also have similar support, but I don't know if it can be used separately. Of course the best would be upgrade widl from wine to support winmd output.

I am not sure whether it will be easier to adapt some C#/Mono tooling to generate the winmd file or to implement winmd writing capabilities to widl. The compiler infrastructure in Mono should be able to write winmd files easily but we will need an MIDL parser in C#. Widl can already parse MIDL (we "just" need to add MIDL 3.0 syntax) but there isn't an FOSS library for writing winmd files. (microsoft/winmd is read-only but perhaps it could be extended to support writing too?) Both paths seem difficult.

Anyway, I filed an issue for widl: https://bugs.winehq.org/show_bug.cgi?id=53905

(Btw doesn't look like windows-rs currently has any built-in support for authoring components -- still requires manually running MIDL.)

driver1998 commented 1 year ago

Authoring will be needed if XAML (Windows.UI.Xaml) is used for UI, where WinRT classes are needed for your view model for data binding. But that is an even deeper rabbit hole (XAML compiler and such). Other than that, it is not that useful I agree.

Β It sucks have to manually do it every time the IDL file is changed

It's not like the experience in Visual Studio is any better. β•―β–‚β•°

driver1998 commented 1 year ago

Looks like science have gone too far... ;) mmexport1675495073860.png

Code: https://github.com/driver1998/XamlMinGW

masx200 commented 1 year ago
[ 71%]: linking.test check-if-it-is-a-good-array-test.exe
"C:\\Program Files\\LLVM\\bin\\clang++" -o build\windows\x64\test\check-if-it-is-a-good-array-test.exe build\.objs\check-if-it-is-a-good-array-test\windows\x64\test\test.cpp.obj build\.objs\check-if-it-is-a-good-array-test\windows\x64\test\index.ixx.obj -m64 -LC:\Users\Administrator\Documents\vcpkg-master\installed\x64-windows-static\lib -LC:\Users\Administrator\Documents\vcpkg-master\installed\x64-windows-static\lib\manual-link -lgmock -lgtest -lgmock_main -lgtest_main
error: execv(C:\Program Files\LLVM\bin\clang++ -o build\windows\x64\test\check-if-it-is-a-good-array-test.exe build\.objs\check-if-it-is-a-good-array-test\windows\x64\test\test.cpp.obj build\.objs\check-if-it-is-a-good-array-test\windows\x64\test\index.ixx.obj -m64 -LC:\Users\Administrator\Documents\vcpkg-master\installed\x64-windows-static\lib -LC:\Users\Administrator\Documents\vcpkg-master\installed\x64-windows-static\lib\manual-link -lgmock -lgtest -lgmock_main -lgtest_main) failed(1)

ld.lld: error: could not open 'liblibcpmt.a': No such file or directory
ld.lld: error: could not open 'libLIBCMT.a': No such file or directory
ld.lld: error: could not open 'libOLDNAMES.a': No such file or directory
clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
cristianadam commented 1 week ago

@alvinhochun any progress on this issue?

I have seen https://github.com/alvinhochun/mingw-w64-cppwinrt/ but I don't think cppwinrt headers / libraries are bundled with any mingw-w64 distro.