Report: Swift and C++ interoperability project progress in the Swift-5.7 time frame

And the C++ part...

/
//  main.cpp
//  CxxToSwift
//
//  Created by admin on 2023-01-30.
//

#include <iostream>
#include <stdint.h>
#include "MySwiftLib-Swift.h"

int main(int argc, const char * argv[]) {
    
    int64_t result = MySwiftLib::my_swift_add(2, 7);
    
    std::cout << "Hello, World! " << result << "\n";
    return 0;
}

//  % xcrun --toolchain "Swift Development Snapshot" clang++ main.cpp -lMySwiftLib

// output
// Hello, World! 9

Place the libMySwiftLib.dylib in a path where the linker can find it e.g. /usr/local/lib or the executable directory.

You will find the MySwiftLib-Swift.h header generated by the swift build under the .build directory.

Notes on Windows:

  • the generated dynamic library is MySwiftLib.dll and the generated header is MySwiftLib.h.
  • the Clang++ version is 15 (Clang++ 13 on macOS)
  • the c++ compile on Windows abort with errors most related to template specialization in the generated header file:
In file included from main.cpp:10:
./MySwiftLib.h:400:36: error: no variable template matches specialization
static inline const constexpr bool isUsableInGenericContext<bool> = true;
                                   ^

Maybe @compnerd can give us some hint on those C++ compile errors.

1 Like

Thank you very much. Maybe I would add an according GitHub project as a sample project (presuming you don‘t mind).

@compnerd: As I would like to have it run on Windows, I would appreciate any help (GitHub project needed first?). Thanks in advance.

No problem, go ahead sharing and improve Stephan !
On all the threads we can find more advanced experiments with the Swift -> C++ side but few or nothing with the reverse C++ -> Swift. My basic experiment is just a start, I would like to see a project with all the stuff showed in the UserGuide. I assume the Swift team at some point will have an exhaustive test project to share.

@Alex_L should probably look into the generated template error.

Thanks for digging into Swift -> C++ interop, sorry for the late reply.

This is most likely happening because you're not including the C++ interop core headers we ship that are part of the Swift distribution . You should add an include path to your clang compilation that points to something like <SWIFT_TOOLCHAIN_DIR>/usr/lib/swift/ .

Thanks for the hint. I added it to the little demo app.

Thanks Alex for the reply.

On macOS I compile with clang++ using xcrun with the latest installed toolchain, this explain why I don't have to add compile flag to include the path to the C++ interop core headers at <SWIFT_TOOLCHAIN_DIR>/usr/lib/swift. Is my understanding is correct ?

Now on Windows:

There is no xcrun command to select a toolchain, I always manually uninstall a toolchain and install a new one using the the Windows install found on swift.org download. For example my last try was using the swift-DEVELOPMENT-SNAPSHOT-2023-01-02-a-windows10.exe installer. I now understand that when working with Cxx to Swift interop on Windows we must add the include path to the C++ interop core headers you mentioned.

However, looking at the toolchain directory on Windows there is no interop directory and no headers:

    Directory: C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\lib\swift


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          2/3/2023   9:09 AM                migrator
d-----          2/3/2023   9:09 AM                pm

On macOS the toolchain I see the swiftToCxx directory with the headers.

admin@mbam1 swift % ls -al
total 0
drwxr-xr-x  19 root  wheel   608  2 Feb 10:35 .
drwxr-xr-x   9 root  wheel   288  2 Feb 03:11 ..
drwxr-xr-x   6 root  wheel   192  1 Feb 17:09 FrameworkABIBaseline
drwxr-xr-x   5 root  wheel   160  1 Feb 17:09 _InternalSwiftScan
drwxr-xr-x   5 root  wheel   160  1 Feb 17:08 _InternalSwiftStaticMirror
drwxr-xr-x   4 root  wheel   128  1 Feb 17:08 apinotes
drwxr-xr-x  33 root  wheel  1056  2 Feb 03:11 appletvos
drwxr-xr-x  34 root  wheel  1088  2 Feb 03:11 appletvsimulator
lrwxr-xr-x   1 root  wheel    15  2 Feb 10:35 clang -> ../clang/13.0.0
drwxr-xr-x  20 root  wheel   640  2 Feb 03:11 host
drwxr-xr-x  35 root  wheel  1120  2 Feb 03:12 iphoneos
drwxr-xr-x  34 root  wheel  1088  2 Feb 03:12 iphonesimulator
drwxr-xr-x  54 root  wheel  1728  2 Feb 03:12 macosx
drwxr-xr-x  12 root  wheel   384  1 Feb 17:08 migrator
drwxr-xr-x   5 root  wheel   160  1 Feb 19:26 pm
drwxr-xr-x  27 root  wheel   864  1 Feb 17:08 shims
drwxr-xr-x   5 root  wheel   160  1 Feb 17:08 swiftToCxx <==== No such directory on Windows
drwxr-xr-x  35 root  wheel  1120  2 Feb 03:13 watchos
drwxr-xr-x  35 root  wheel  1120  2 Feb 03:13 watchsimulator
admin@mbam1 swift % 

Is I misunderstand something to experiment with C++ -> swift interop on Windows ?

PS: On macOS things runs fine now calling Swift function, class all using primitive types. I have issue when I try using Swift types like String and Array. I have issue also using Swift struct from C++, all struct members has private access in the generated header. I keep more details about these issues in an upcoming post but I would like to know for now if Swift types are supposed to work in the latest toolchain ?

I just realize that if I use -clang-header-expose-decls=all-public, the compiler probably won't generate C++ declaration for types like String and Array. Maybe it is better to keep using @_expose(Cxx) with -enable-experimental-cxx-interop, with all types and functions set to public.

Can you elaborate more on this issue? I believe currently public properties in Swift struct are exposed as getX and setX (if declared as var) public functions in the generated C++ class.

The problem is Swift String type appear in the generated header as Swift::String and the Swift:: namespace does not exist. The swift:: namespace (all lower case) exist but do not include any representation for String. Same thing for Array.

// Swift code

public func hiMessage(name: String) -> String {
    return "Hello \(name)"
}

public func mulByTwo(arr: [Int]) -> [Int] {
    return arr.map { $0 * 2 }
}

// Generated header extract

inline Swift::String hiMessage(const Swift::String& name) noexcept SWIFT_WARN_UNUSED_RESULT {
  return Swift::_impl::_impl_String::returnNewValue([&](char * _Nonnull result) {
    _impl::swift_interop_returnDirect_MySwiftLib_uint64_t_0_8_void_ptr_8_16(result, _impl::$s10MySwiftLib9hiMessage4nameS2S_tF(_impl::swift_interop_passDirect_MySwiftLib_uint64_t_0_8_void_ptr_8_16(Swift::_impl::_impl_String::getOpaquePointer(name))));
  });
}

inline Swift::Array<swift::Int> mulByTwo(const Swift::Array<swift::Int>& arr) noexcept SWIFT_WARN_UNUSED_RESULT {
  return Swift::_impl::_impl_Array<swift::Int>::returnNewValue([&](char * _Nonnull result) {
    _impl::swift_interop_returnDirect_MySwiftLib_void_ptr_0_8(result, _impl::$s10MySwiftLib8mulByTwo3arrSaySiGAD_tF(_impl::swift_interop_passDirect_MySwiftLib_void_ptr_0_8(Swift::_impl::_impl_Array<swift::Int>::getOpaquePointer(arr))));
  });
}

When compiling C++ with the generated header, compile abort with a lot of errors like this:

In file included from main.cpp:10:
./MySwiftLib-Swift.h:799:8: error: use of undeclared identifier 'Swift'; did you mean 'swift'?
inline Swift::String hiMessage(const Swift::String& name) noexcept SWIFT_WARN_UNUSED_RESULT {
       ^~~~~
       swift
./MySwiftLib-Swift.h:774:11: note: 'swift' declared here
namespace swift {
          ^
./MySwiftLib-Swift.h:799:8: error: no type named 'String' in namespace 'swift'; did you mean 'std::string'?
inline Swift::String hiMessage(const Swift::String& name) noexcept SWIFT_WARN_UNUSED_RESULT {
       ^~~~~~~~~~~~~
       std::string
/Library/Developer/Toolchains/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-02-01-a.xctoolchain/usr/bin/../include/c++/v1/iosfwd:251:65: note: 'std::string' declared here
typedef basic_string<char, char_traits<char>, allocator<char> > string;
                                                                ^
In file included from main.cpp:10:
./MySwiftLib-Swift.h:799:38: error: use of undeclared identifier 'Swift'; did you mean 'swift'?
inline Swift::String hiMessage(const Swift::String& name) noexcept SWIFT_WARN_UNUSED_RESULT {
                                     ^~~~~
                                     swift
./MySwiftLib-Swift.h:774:11: note: 'swift' declared here
namespace swift {
          ^
./MySwiftLib-Swift.h:799:38: error: no type named 'String' in namespace 'swift'; did you mean 'std::string'?
inline Swift::String hiMessage(const Swift::String& name) noexcept SWIFT_WARN_UNUSED_RESULT {
                                     ^~~~~~~~~~~~~
                                     std::string
/Library/Developer/Toolchains/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-02-01-a.xctoolchain/usr/bin/../include/c++/v1/iosfwd:251:65: note: 'std::string' declared here
typedef basic_string<char, char_traits<char>, allocator<char> > string;
                                                                ^
In file included from main.cpp:10:
./MySwiftLib-Swift.h:800:10: error: use of undeclared identifier 'Swift'; did you mean 'swift'?
  return Swift::_impl::_impl_String::returnNewValue([&](char * _Nonnull result) {
         ^~~~~
         swift

You need to build with -Xfrontend -enable-experimental-cxx-interop and @_expose(Cxx) attribute applied to your Swift decls at the moment, and drop -clang-header-expose-decls=all-public . Then the compiler should also emit the standard library types you're missing here.

And now the issue with Struct...

// Swift code

public struct Point {
  public var x = 0.0, y = 0.0
    
  public mutating func moveBy(x deltaX: Double, y deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}
// C++ code

#include <iostream>
#include <stdint.h>
#include "MySwiftLib-Swift.h"

using namespace MySwiftLib;

int main(int argc, const char * argv[]) {
    
    auto point = Point();
    point.moveBy(1.0, 2.0);
    std::cout << "The point is now at " << point.getX() << ", " << point.getY() << "\n";
    
    return 0;
}

Compile error:

main.cpp:35:18: error: calling a private constructor of class 'MySwiftLib::Point'
    auto point = Point();
                 ^
./MySwiftLib-Swift.h:612:10: note: declared private here
  inline Point() {}
         ^
2 errors generated.

Looking the generated header, the constructor is private:

class Point final {
public:
  inline ~Point() {
    auto metadata = _impl::$s10MySwiftLib5PointVMa(0);
    auto *vwTableAddr = reinterpret_cast<swift::_impl::ValueWitnessTable **>(metadata._0) - 1;
#ifdef __arm64e__
    auto *vwTable = reinterpret_cast<swift::_impl::ValueWitnessTable *>(ptrauth_auth_data(reinterpret_cast<void *>(*vwTableAddr), ptrauth_key_process_independent_data, ptrauth_blend_discriminator(vwTableAddr, 11839)));
#else
    auto *vwTable = *vwTableAddr;
#endif
    vwTable->destroy(_getOpaquePointer(), metadata._0);
  }
  inline Point(const Point &other) {
    auto metadata = _impl::$s10MySwiftLib5PointVMa(0);
    auto *vwTableAddr = reinterpret_cast<swift::_impl::ValueWitnessTable **>(metadata._0) - 1;
#ifdef __arm64e__
    auto *vwTable = reinterpret_cast<swift::_impl::ValueWitnessTable *>(ptrauth_auth_data(reinterpret_cast<void *>(*vwTableAddr), ptrauth_key_process_independent_data, ptrauth_blend_discriminator(vwTableAddr, 11839)));
#else
    auto *vwTable = *vwTableAddr;
#endif
    vwTable->initializeWithCopy(_getOpaquePointer(), const_cast<char *>(other._getOpaquePointer()), metadata._0);
  }
  [[noreturn]] inline Point(Point &&) { abort(); }
  inline double getX() const;
  inline void setX(double value);
  inline double getY() const;
  inline void setY(double value);
  inline void moveBy(double deltaX, double deltaY);
private:
  inline Point() {}
  static inline Point _make() { return Point(); }
  inline const char * _Nonnull _getOpaquePointer() const { return _storage; }
  inline char * _Nonnull _getOpaquePointer() { return _storage; }

  alignas(8) char _storage[16];
  friend class _impl::_impl_Point;
};

Changing manually the constructor to public in the header turn the C++ compile to success.

We currently do not expose the default constructor in this case. If you add a public init constructor to your struct, you'll then be able to call Point::init() from C++.

1 Like

While that does compile, that kind of change will cause undefined behavior at runtime as you're not actually constructing a Swift value with such a default constructor.

Wow thanks a lot Alex !!, adding a public init to my struct and calling Point::init() from C++ works !!!!
I will try later your suggestion about changing the Swift compile flags and using the @_expose attribute to resolve my issues with Swift types usage in C++ and report.

1 Like

Mixed success with Swift String type from C++.

// Swift side

@_expose(Cxx, "hiMessage")
public func hiMessage(name: String) -> String {
    return "Hello \(name)"
}
// main.cpp

#include <iostream>
#include <stdint.h>
#include "MySwiftLib-Swift.h"

using namespace MySwiftLib;

int main(int argc, const char * argv[]) {
        
    // From User Guide:
    // String conforms to StringLiteralConvertible , so you can implicitly construct instances of swift::String directly from a C++ string literal, or any const char * C string:
    // swift::String string = "Hello world";
    
    swift::String string = "Hello world"; // Not working, the namespace must be Capitalized "Swift::" to work with Swift types.
    
    // main.cpp:15:5: error: no type named 'String' in namespace 'swift'; did you mean 'Swift::String'?
    //     swift::String string = "Hello world";
    //     ^~~~~~~~~~~~~
    //     Swift::String
    // ./MySwiftLib-Swift.h:2589:7: note: 'Swift::String' declared here
    // class String final {
    //       ^

    Swift::String name = "Steve";  // NOT OK. "Swift::" namespace ok but cannot init using C++ string literal
    // main.cpp:25:19: error: no viable conversion from 'const char[6]' to 'Swift::String'
    //     Swift::String name = "Steve"; 
    //                   ^      ~~~~~~~

    Swift::String swName = Swift::String("Steve");  // OK !! namespace "Swift::" ok and using explicit constructor
    
    // From User Guide:
    // You can convert a swift::String to a std::string using std::to_string:
    //
    // void printSwiftString(const swift::String &swStr) {
    //  std::string str = std::to_string(swStr);
    //  std::cout << "swift string is " << str << "\n";
    // }
    
    std::string stdName = std::to_string(swName); // NOT OK. std::to_string not found...
    
    // main.cpp:29:27: error: no matching function for call to 'to_string'
    // std::string stdName = std::to_string(swName);
    //                       ^~~~~~~~~~~~~~
    
    std::cout << stdName << "\n";
    
   Swift::String message = hiMessage(swName); // Compile ok, but like I said above I can't convert back to std::string to print the message

    return 0;
}


// User Guide https://github.com/apple/swift/blob/main/docs/CppInteroperability/UserGuide-CallingSwiftFromC%2B%2B.md

//  % xcrun --toolchain "Swift Development Snapshot" clang++ main.cpp -lMySwiftLib

PS: Any idea about using C++ to Swift on Windows (see my previous post on the Windows issue) ?

PS2: Question about Swift Structure imported in C++.
Why Swift struct are bridged over as a C++ class ?
Will it be possible at some point to use Swift struct in C++ as C++ struct ?

I don’t think that’s unexpected. My understanding is that the default constructor for Swift structs is internal unless declared otherwise, and only things that are public in Swift are exposed to C++.

1 Like

Effectively bbrk24 you're right. Not different then using a Swift framework/module from Swift...

Memberwise Initializers for Swift Structures

It’s easy to forget but if you don’t define your own custom initializers the Swift compiler gives you a default memberwise initializer for free. Even though I’ve now marked the struct as public the default initializer is still internal. From The Swift Programming Language:

if you want a public structure type to be initializable with a memberwise initializer when used in another module, you must provide a public memberwise initializer yourself as part of the type’s definition.

1 Like
swift::String string = "Hello world"; // Not working, the namespace must be Capitalized "Swift::" to work with Swift types.

Currently String is in the Swift namespace. The discrepancy between the two namespaces will be corrected soon to correspond to what's in the user manual (so please bear in mind that this is not yet source stable). This should work though:

Swift::String swiftString = "Hello world";

To convert back to std::string, you can just do an explicit cast:

auto cppString = (std::string) swiftString;

I will update the user guide document to account for that, I think we won't actually offer std::to_string but will stick to the cast instead.

PS: Any idea about using C++ to Swift on Windows (see my previous post on the Windows issue) ?

On macOS I compile with clang++ using xcrun with the latest installed toolchain, this explain why I don't have to add compile flag to include the path to the C++ interop core headers at <SWIFT_TOOLCHAIN_DIR>/usr/lib/swift. Is my understanding is correct ?

Essentially. On macOS, the open source toolchain you download from swift.org contains both Clang and Swift. In that case, clang from that toolchain is able to find the C++ interop core headers at <SWIFT_TOOLCHAIN_DIR>/usr/lib/swift because those headers are in the same toolchain as Clang.

However, on Windows if your clang is not in the same toolchain (this also applies to all platforms though, you can recombine compilers across macOS and Linux too), then you must include the path to the core headers.

However, looking at the toolchain directory on Windows there is no interop directory and no headers:

Interesting, I haven't seen this yet. Could you file an issue on GitHub - apple/swift: The Swift Programming Language and we will take a look at this Windows issue.

For now you can copy the headers from your macOS download, they will work on Windows too.

PS2: Question about Swift Structure imported in C++.
Why Swift struct are bridged over as a C++ class ?

In C++ there's no real difference between a class and a struct (unlike in Swift), the only difference is the default member access modifier (public in structs vs private in class). Thus there would be no difference between us expressing a Swift struct as a class or a struct in C++.

Will it be possible at some point to use Swift struct in C++ as C++ struct ?

Well we wouldn't change a class to a struct if that's what you mean, for the reason I mentioned above. Accessing Swift properties in Swift structs will have to go through getters and setters in C++ as well. Does that answer this specific struct question?

Thanks Alex your help is invaluable.

  • Does that answer this specific struct question? Yes.
  • Coping the headers from macOS to Windows solved the issue. I will file an issue about this.
  • Convert back Swift::String to std::string doing an explicit cast works ok.
  • Swift::String init from literal still fail.
main.cpp:31:19: error: no viable conversion from 'const char[12]' to 'Swift::String'
    Swift::String swiftString = "Hello world";
                  ^             ~~~~~~~~~~~~~
  • I can init from a std::string however like this:
    std::string name = "Steve";
    Swift::String swiftName = name;

One more question: Is the @_expose will be the official way to expose all the Swift side decl we want to use in C++ or you will re-introduce a compiler flag like -clang-header-expose-decls=all-public ? or other mechanism ? and keeping using @_expose for overloads naming.

Stay tuned. More to come about my experiments with Swift Array from C++.

Thanks again !

  • Swift::String init from literal still fail.

Good catch, I think now the cast is explicit. I will add an implicit string literal constructor though. If you could, please file a GitHub issue for it so you can see when I land the change.

One more question: Is the @_expose will be the official way to expose all the Swift side decl we want to use in C++ or you will re-introduce a compiler flag like -clang-header-expose-decls=all-public ? or other mechanism ? and keeping using @_expose for overloads naming.

@_expose won't be required in the near future, we will emit C++ bindings for any supported and/or supported public API from the Swift module if you have interoperability enabled. This section from the recently approved vision document provides more details. I didn't make this change in the implementation yet though, but will do in the very near future.