C++ interoperability is broken (Windows platform)

Swift is currently at 6.0.1, and C++ interoperability seems to work on Windows. Until it turns out that it doesn't.

The same simple source code:

@_cdecl("meaningOfLife")
public func meaningOfLife() -> Int {
    return 42
}

The same build command:

swiftc dynamicLibrary.swift -emit-library -cxx-interoperability-mode=default

And voila! Dynamic library is created. Unless you add import Foundation to your code. In this case things are broken again:

Compiler output
<module-includes>:1:10: note: in file included from <module-includes>:1:
1 | #include "_FoundationCShims.h"
  |          `- note: in file included from <module-includes>:1:
2 |

C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_FoundationCShims.h:17:10: note: in file included from C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_FoundationCShims.h:17:
15 |
16 | #include "_CShimsTargetConditionals.h"
17 | #include "_CStdlib.h"
   |          `- note: in file included from C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_FoundationCShims.h:17:
18 | #include "CFUniCharBitmapData.inc.h"
19 | #include "CFUniCharBitmapData.h"

C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_CStdlib.h:23:2: note: in file included from C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_CStdlib.h:23:
 21 |
 22 | #if __has_include(<ctype.h>)
 23 | #include <ctype.h>
    |  `- note: in file included from C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_CStdlib.h:23:
 24 | #endif
 25 |

<unknown>:0: error: could not build C module '_FoundationCShims'
C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\share\ucrt.modulemap:15:12: error: module 'ucrt.C.complex' is incompatible with feature 'cplusplus'
 13 | module ucrt [system] {
 14 |   module C {
 15 |     module complex {
    |            `- error: module 'ucrt.C.complex' is incompatible with feature 'cplusplus'
 16 |       /* disallow the header in C++ mode as it forwards to `ccomplex`.  */
 17 |       requires !cplusplus

C:\Users\dima\AppData\Local\Programs\Swift\Platforms\6.0.1\Windows.platform\Developer\SDKs\Windows.sdk\usr\include\_FoundationCShims/_CStdlib.h:122:10: note: submodule of top-level module 'ucrt' implicitly imported here
120 |
121 | #if __has_include(<complex.h>)
122 | #include <complex.h>
    |          `- note: submodule of top-level module 'ucrt' implicitly imported here
123 | #endif
124 |

Has anyone managed to get their GitHub Actions workflows back up and working for C++ interop enabled projects on windows-latest by chance?

I naively assumed "oh, I can just use/install winget to bring MSVC to a version where the STL doesn't break swift" like this, but it's not even possible to install winget on Windows Server, because it uses a .msixbundle which requires the Microsoft Store infrastructure to install, which is not something that Windows Server environments support.

All of my Windows CI for all C++ interop enabled projects has been broken for 2 months now, and I am hopeful to get that working again. If anyone knows of any short-term workarounds, or if there is a particular Swift 6 release on the horizon that might be pulling in the corresponding Clang changes to work with Microsoft's STL again? I think it would be very helpful.

Thanks! :call_me_hand:

1 Like

I figured out a workaround, super ugly hack, the only limitation is I believe it does not work for .testTarget(), but this is everything you need to get Swift/C++ interop enabled projects to work on Windows with recent versions of the Microsoft STL:

Working CI of this workaround being utilized can be found here, leveraging Swift/C++ interop on Swift 5.10 and MSVC build tools 14.41.34120, the current version at the time of writing using the windows-latest GitHub Actions runner.

.target(
  name: "MySwiftLib",
  dependencies: [
    .target(name: "MyCxxLib")
  ],
  cxxSettings: [
    .define("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH", .when(platforms: [.windows])),
    .define("_ALLOW_KEYWORD_MACROS", to: "1", .when(platforms: [.windows])),
    .define("static_assert(_conditional, ...)", to: "", .when(platforms: [.windows])),
  ],
  swiftSettings: [
    .interoperabilityMode(.Cxx)
  ]
),

I tried your example in a newly created empty package, and it gives
error: cannot find 'cxxSettings' in scope
for me. Tried just of curiosity, because

  1. I do not use Swift Package Manager, and use only swiftc command line compiler;
  2. I stopped using C++ interop entirely, as it seem to be fundamentally broken on Windows.

Whoops, thanks for pointing that out.

It’s only a snippet; use swift package init to create a complete package manifest, and a proper directory structure, then paste the above inside the targets: [] array.

Also ensure you are using // swift-tools-version: 5.10 at the very top of the file.

I did use swift package init. Swift 6.0.2. Does your code snippet work for Swift 5.10 only?

No it should technically work for 5.9+, would you be able to share your Package.swift manifest file?

Also if it’s any consolation C++ interop certainly works, even on windows, there are some rough patches, but windows successfully builds and runs this example, which are all massive C++ libraries being imported into swift at the top of that file.

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "hello",
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "hello",
            targets: ["hello"]),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .target(
            name: "hello"),
            cxxSettings: [
    .define("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH", .when(platforms: [.windows])),
    .define("_ALLOW_KEYWORD_MACROS", to: "1", .when(platforms: [.windows])),
    .define("static_assert(_conditional, ...)", to: "", .when(platforms: [.windows])),
  ],
  swiftSettings: [
    .interoperabilityMode(.Cxx)
  ]
        .testTarget(
            name: "helloTests",
            dependencies: ["hello"]
        ),
    ]
)

Most likey I pasted your snippet in a wrong place.

Nope you’ve pasted it in the correct place, you are simply missing in the above portion a closing ),

That is a closing parentheses and a comma.

Like so:

swiftSettings: [
    .interoperabilityMode(.Cxx)
  ]),

Then the only thing you’re missing here is a C++ target to test with, so go ahead and make a single directory with contents like

Sources/
  hellocxx/
    include/
      hellocxx.h
    hellocxx.cpp

And fill in some very basic c++ code, like a single function declaration in the header, and the function definition in the source file, and add that new target to your Package.swift manifest.

And lastly, add this new C++ target dependency to your hello swift target:

.target(
  name: "hello",
  dependencies: [
    .target(name: "hellocxx")
  ],
  // …

You should then be able to import hellocxx in any of your swift files inside the hello directory, and call the C++ code declared in your hellocxx.h file directly from swift.

2 Likes

The workaround above works well for getting around static_assert() at least in the short term until Swift’s embedded clang is fixed for that part of the Microsoft STL.

However, there appears to be one more issue, I actually do believe it to be the only other remaining error regarding Microsoft’s STL, but it doesn’t make much sense to me, when importing C++ targets in Swift on windows with Swift/C++ interop, even when cxxLanguageStandard: .cxx17 is specified, if you attempt to #include <any> it throws the following error:

vcruntime.modulemap:261:10: error: module 'std.any' requires feature 'cpluplus17'

Which does not make much sense given that the project is set for the C++17 language standard. If anyone knows how to get around that one by chance, I suppose a way that preferably doesn’t involve us having to roll our own <any> header with a custom implementation and “shimming” it in? But sometimes us early adopters have to do what we have to do :sweat_smile:

If that’s verbatim, it has a typo in it, which should be fixed in the Windows module map (i.e. not your fault, you found a bug!)

2 Likes

oops

3 Likes

Made an issue here:

3 Likes

Great catch, I completely didn’t notice that typo at first glance!

@gregcotten thanks for creating an issue for that!

Gave me a good excuse to make my first PR to swift, you guys made it easy. :call_me_hand:

As a final windows swift/c++ interop bug, I’ve noticed the following:

A typical pattern that I follow with smart pointers like std::shared_ptr, is that I’ll write an extension around the smart pointer in Swift, abstracting away pointee accessors on the end of the Swift code, for a more familiar syntax to how C++ APIs are called natively, as an example:

// implementation detail, not important.
class MyClass
{
 public:
  void doSomething();
} SWIFT_IMMORTAL_REFERENCE;

// declare MyClassPtr shared pointer to MyClass.
using MyClassPtr = std::shared_ptr<MyClass>;
// so swift code can call doSomething() directly.
public extension MyClassPtr
{
  func doSomething()
  {
    pointee.doSomething()
  }
}

This works on macOS and Linux, but on Windows (Swift 5.10) I get the following error:

error: cannot find 'pointee' in scope

This tells me that the instance member, pointee is being hidden, but I believe it should be within visible scope, if there’s some way to achieve that.

The pattern of smart pointers to foreign reference types is not currently supported in C++ interop. The compiler-generated var pointee normally unwraps the pointer and returns a value, which is not something you could do with a reference type: when annotating a type as SWIFT_*_REFERENCE, you're essentially making a promise to the compiler that this type is only ever passed around by pointer or reference and never by value.

Swift should certainly do a better job at diagnosing this. For now I would recommend refactoring the C++ API to either use raw pointers with types annotated as SWIFT_*_REFERENCE, or smart pointers of value types, if possible.

3 Likes

I see, so more of a bug that it was even working like this on macOS and Linux in the first place

(a combination of extensions of smart pointers whose var pointee was returning the underlying value of types also marked as SWIFT_*_REFERENCE).

I can definitely see a use case for this though, particularly in the realm of reference pointers, a common paradigm across many C++ projects, perhaps something nice to support in the future?

Anyways, thanks for the clarification!