How to build clang modules to work with Swift/C++ interop

I would like to see examples of how to do this. Apple states that explicit clang modules don't work with C++ interop. ObjC++ has simple interop with C++. Swift does not. And so I'd like to know how to setup my C++ projects to build them as clang modules.

Answered by DTS Engineer in 813264022

Thanks for all the extra info.

I don’t have a solution to that specific problem yet, but I wanted to post what I’ve achieved so far, primarily to establish a baseline.

Note I was primarily working from Mixing Swift and C++. That was referenced upthread, but I’m including a link here primarily for the benefit of Future Quinn™.

Here’s what I did today:

  1. Using Xcode 16.1, I created a new Swift project from the macOS > Command Line Tool template.

  2. I chose File > New > File and selected the macOS > C++ File template, naming the file QContact.

  3. Xcode asked whether I wanted to create a bridging header. I agreed to that.

  4. I replaced the QContact.hpp and QContact.cpp file content with the code at the end of this post. As you can see, I’m an expert C++ programmer (-:

  5. In the target editor, I changed the C++ and Objective-C Interoperability build setting to C++ / Objective-C++.

  6. I added this line to the bridging header:

    #include "QContact.hpp"
    
  7. I replaced main.swift with this code:

    func main() {
        let quinn = QContact("Quinn “The Eskimo!”")
        print(quinn.fullName())
    }
    
    main()
    
  8. I chose Product > Run. It built successfully and printed:

    Mr Gumby
    

OK, so that’s the absolute minimum working.


For my next trick I tried moving the C++ code out into a Swift package. Why a Swift package? Well, I’ll come back to that below.

Here’s what I did:

  1. I chose File > New > Package and selected the Multiplatform > Library template. I chose a Testing System of None, just to simplify things. I set the name to QContacts.

  2. I deleted Sources/QContacts/QContacts.swift.

  3. I created a Sources/QContacts/include directory.

  4. I copied QContact.hpp to that.

  5. And QContact.cpp to Sources/QContacts.

  6. In Sources/QContacts/include, I created a new module map using File > New, choosing the macOS > Module Map template.

  7. I replaced the contents with this:

    module QContacts {
        header "QContact.h"
    
        export *
    }
    
  8. I selected my Mac as the run destination and chose Product > Build. It built successfully.

  9. I closed the package.

Now I imported the package into the tool project:

  1. In the tool project, I remove the include in my bridging header.

  2. And deleted this project’s copy of QContact.hpp and QContact.cpp.

  3. I chose File > Add Package Dependencies, clicked the Add Local button, and selected the QContacts package.

  4. In the resulting sheet I left all the options at their default and clicked Add Package.

  5. I added import QContacts to the top of main.swift.

  6. I chose Build > Run. It built successfully and printed the same message as before.

Neat-o!


Now there’s a reason I select a Swift package here. Swift Package Manager has good support for the traditional Unix-y ‘static library plus a bunch o’ headers’ module. That’s less well supported in Xcode. Xcode is much happier building frameworks. I suspect it’s possible to get Xcode to do what you want, but I didn’t have time to explore that today.

OTOH, if you’re happy building a framework then there’s actually sample code for that. See the links under the Language Interoperability with C++ topic.

Remember that, with mergeable libraries, a framework build structure no longer means that you have to link to the code dynamically. You can create a framework and then merge the framework [1] into your main executable.

Alternatively, you might find that a Swift package is something you enjoy working with.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well, everything except a tiny stub.


#ifndef QContact_hpp
#define QContact_hpp

#include <string>

class QContact {

  public:
    QContact(std::string fullName);
    ~QContact();

    std::string fullName() const;

  private:
    std::string _fullName;
};

#endif /* QContact_hpp */
#include "QContact.hpp"

QContact::QContact(std::string fullName) {
    this->_fullName = fullName;
}

QContact::~QContact() {}

std::string QContact::fullName() const {
    return this->_fullName;
}

Already followed all that and it doesn’t work.

Are you targeting Swift package manager? Or Xcode?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I have C++ Xcode projects and Swift projects. These only work right now with a c-style bridging header. Apple doesn’t have any sample apps that build clang modules sinice they were released many years ago. Watched the WWDC on explicit modules, but then read these don’t work with Swift interop.

Have a modulemap file, -fmodules defined, but Swift still can’t see the C++ sources. one of the strengths of Apple development was ease of interop between ObjC++ and C++. Have had to wait until Swift 5/6 for something not under a C-wrapper. My code is on Swift 5 if that matters. But switching to Swift 6 is a concurrency warning flood.

Also when I have Swift 5 set in Xcode 16, is that 5.xx (f.e. 5.9 has interop) or 5.0?

Also when I have Swift 5 set in Xcode 16, is that 5.xx (f.e. 5.9 has interop) or 5.0?

That’d be Swift 6 (-:

There’s a difference between the compiler version and the language mode. Xcode 16 only includes the Swift 6 compiler. The Swift Language Version build setting controls the language mode.

And, yes, that’s confusing, and there’s an active effort to improve how we talk about this stuff. See SE-0441 Formalize ‘language mode’ terminology.

AFAIK there’s no difference between the Swift 5 and 6 language modes when it comes to C++ interop. The mode increment is all about Swift concurrency enforcement, and it’s fine to ignore that for the moment.

C++ interop is still cutting edge stuff, so it’s not as smooth as it should be. However, you should be able to get the basics working.

I have C++ Xcode projects and Swift projects.

I’m presuming that last bit was meant to be “Swift Xcode projects”.

So how do you plan to glue them together? For example, do you want to create a framework in C++ and use it in Swift? Or vice versa? Or do you want to have a single target that includes both Swift and C++?

For context, I wanna play around with this to make sure that I understand it myself. Hopefully I’ll then be able to provide some better guidance. However, it’d best if my exploration was aligned with your general goal. So, I’d like to clarify your goal before I start playing (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I’m presuming that last bit was meant to be “Swift Xcode projects”.

Yes, it's all Xcode. In a workspace, with separate swift and C++ code. The C++ is vector math and a texture encoder/decoder. The Swift is visionOS stuff, a build profiler, etc. Mostly forced into Swift use by Swift-only frameworks like SwiftUI and RealityKit.

I'd like to move code off Swift and onto C++. This would primarily be Swift calling C++, and avoiding writing a bridging header.

So I think I have modules for C++ building, but then get errors trying to import them into Swift.

c++

module vectormath234 {
    // All headers are pulled in by this.
    requires cplusplus20
    
    // This defaults to namespace simdk
    header "vectormath234.h"
}

Swift

import vectormath234

module.modulemap:1:8 Module 'vectormath234' requires feature 'cplusplus20'

MathHelpers.swift:6:8 Could not build Objective-C module 'vectormath234'

I have -fmodules and -fcxx-modules set on the compile line.

Thanks Quinn if you have time to address this. I really appreciate your great dev support.

Also wondering how to interop one C++ lib with another via clang modules. This document says there is no syntax for that, and that only ObjC has the "@import module" syntax. Given that it's over 4 years since C++20, and still no C++ 20 module support, I've resigned to just use clang modules. But it's unclear what they do or don't support. The docs below state that C++ is possible, and just need the "requires cplusplus" designator, but see above errrors.

https://prereleases.llvm.org/18.1.0/rc3/tools/clang/docs/Modules.html

I also found this setting in Xcode, so set that for C++/Obj-C++ itnerop. That seems to change the errors that I get about ObjC. Will have more time to revisit this later.

SWIFT_OBJC_INTEROP_MODE = objcxx

Thanks for all the extra info.

I don’t have a solution to that specific problem yet, but I wanted to post what I’ve achieved so far, primarily to establish a baseline.

Note I was primarily working from Mixing Swift and C++. That was referenced upthread, but I’m including a link here primarily for the benefit of Future Quinn™.

Here’s what I did today:

  1. Using Xcode 16.1, I created a new Swift project from the macOS > Command Line Tool template.

  2. I chose File > New > File and selected the macOS > C++ File template, naming the file QContact.

  3. Xcode asked whether I wanted to create a bridging header. I agreed to that.

  4. I replaced the QContact.hpp and QContact.cpp file content with the code at the end of this post. As you can see, I’m an expert C++ programmer (-:

  5. In the target editor, I changed the C++ and Objective-C Interoperability build setting to C++ / Objective-C++.

  6. I added this line to the bridging header:

    #include "QContact.hpp"
    
  7. I replaced main.swift with this code:

    func main() {
        let quinn = QContact("Quinn “The Eskimo!”")
        print(quinn.fullName())
    }
    
    main()
    
  8. I chose Product > Run. It built successfully and printed:

    Mr Gumby
    

OK, so that’s the absolute minimum working.


For my next trick I tried moving the C++ code out into a Swift package. Why a Swift package? Well, I’ll come back to that below.

Here’s what I did:

  1. I chose File > New > Package and selected the Multiplatform > Library template. I chose a Testing System of None, just to simplify things. I set the name to QContacts.

  2. I deleted Sources/QContacts/QContacts.swift.

  3. I created a Sources/QContacts/include directory.

  4. I copied QContact.hpp to that.

  5. And QContact.cpp to Sources/QContacts.

  6. In Sources/QContacts/include, I created a new module map using File > New, choosing the macOS > Module Map template.

  7. I replaced the contents with this:

    module QContacts {
        header "QContact.h"
    
        export *
    }
    
  8. I selected my Mac as the run destination and chose Product > Build. It built successfully.

  9. I closed the package.

Now I imported the package into the tool project:

  1. In the tool project, I remove the include in my bridging header.

  2. And deleted this project’s copy of QContact.hpp and QContact.cpp.

  3. I chose File > Add Package Dependencies, clicked the Add Local button, and selected the QContacts package.

  4. In the resulting sheet I left all the options at their default and clicked Add Package.

  5. I added import QContacts to the top of main.swift.

  6. I chose Build > Run. It built successfully and printed the same message as before.

Neat-o!


Now there’s a reason I select a Swift package here. Swift Package Manager has good support for the traditional Unix-y ‘static library plus a bunch o’ headers’ module. That’s less well supported in Xcode. Xcode is much happier building frameworks. I suspect it’s possible to get Xcode to do what you want, but I didn’t have time to explore that today.

OTOH, if you’re happy building a framework then there’s actually sample code for that. See the links under the Language Interoperability with C++ topic.

Remember that, with mergeable libraries, a framework build structure no longer means that you have to link to the code dynamically. You can create a framework and then merge the framework [1] into your main executable.

Alternatively, you might find that a Swift package is something you enjoy working with.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well, everything except a tiny stub.


#ifndef QContact_hpp
#define QContact_hpp

#include <string>

class QContact {

  public:
    QContact(std::string fullName);
    ~QContact();

    std::string fullName() const;

  private:
    std::string _fullName;
};

#endif /* QContact_hpp */
#include "QContact.hpp"

QContact::QContact(std::string fullName) {
    this->_fullName = fullName;
}

QContact::~QContact() {}

std::string QContact::fullName() const {
    return this->_fullName;
}

Thanks Quinn! I'll get back to the interop soon. The bridging header feels like cheating, but it's interesting that Xcode allowed you to include a C++ header there. Mine only contain ObjC and C. But with that new Interoperability setting, that does seem to change things.

How to build clang modules to work with Swift/C++ interop
 
 
Q