diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown new file mode 100644 index 00000000..32aeb65a --- /dev/null +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -0,0 +1,258 @@ +--- +layout: post +comments: false +title: "Calling Unmodified ConanCenter Packages from Swift via C++ Interoperability" +description: "Swift can call supported C++ APIs without a hand-written wrapper. We use Conan to supply an ordinary LunaSVG package and connect its headers, binary, and build settings to Swift." +meta_title: "Calling Unmodified ConanCenter Packages from Swift via C++ Interoperability - Conan Blog" +categories: [cpp, conan, swift, macos, xcode] +--- + +If you've ever wanted to call a C++ library directly from Swift, you've +probably ended up writing a C facade or an Objective-C++ wrapper first. Swift +has been able to import C and Objective-C APIs since its early releases, but +its C importer couldn't represent C++ features such as namespaces, overloaded +functions, templates, constructors, destructors, and standard-library types, +so a wrapper was the only way in. + +[Swift 5.9](https://www.swift.org/blog/swift-5.9-released/) changed that by +introducing direct C++ interoperability: Swift can now import C++ headers and +call supported C++ APIs with no wrapper layer in between. + +To try this with an existing package, we use +[LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG rendering +library packaged in ConanCenter. The application creates an SVG scene in +Swift and asks LunaSVG to render it to a PNG. + +Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its +transitive dependencies, and the information needed to compile and link the +application. A small Clang module map makes LunaSVG's headers importable from +Swift. + +The complete project is available in the [Conan examples +repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop): + +```text +cxx_interop/ +├── conanfile.py +├── demo.xcodeproj/ +├── main.swift +├── ci_test_example.py +└── README.md +``` + +## Starting with the C++ API + +Before looking at Swift, it helps to see how the LunaSVG API would normally be +used from C++. Given `svg` and `css` as `std::string` values holding an SVG +document and a stylesheet, and `output` as the destination PNG path, rendering +and writing the file looks like this: + +```cpp +auto document = lunasvg::Document::loadFromData(svg); +document->applyStyleSheet(css); + +auto bitmap = document->renderToBitmap(); +bitmap.writeToPng(output); +``` + +Although short, this fragment already touches several C++ features: a +namespace, a static method, a `std::unique_ptr`, member functions, +and a `Bitmap` returned by value. + +## Making the C++ API Visible to Swift + +Before Swift can call this API, it needs to know which C++ header to import and +the module name it should use. Swift gets this information through a Clang +module map. + +```text +module LunaSVGMod { + header "/path/to/include/lunasvg/lunasvg.h" + export * +} +``` + +This gives the LunaSVG header a module name, `LunaSVGMod`. Swift must then be +compiled with C++ interoperability enabled and the module-map option is +forwarded to the Clang importer used by the Swift compiler: + +```text +-cxx-interoperability-mode=default +-Xcc -fmodule-map-file=/path/to/lunasvg.modulemap +``` + +`-Xcc` passes the following option to the Clang instance embedded in the Swift +compiler. With these options, Swift can import `LunaSVGMod` and use the +supported declarations from the header. The Xcode configuration shown later +adds the same options using the module map generated by Conan. + +> **Note**: Despite the similar terminology, this is a [Clang +> module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. +> Swift does not currently import C++20 modules. + +## Calling the Same API from Swift + +With the C++ standard library and `LunaSVGMod` imported, `main.swift` calls the +same API to render the demo scene: + +```swift +import CxxStdlib +import LunaSVGMod + +let svg = "..." +let css = ".sky{fill:#8ECBEB} ..." + +let document = lunasvg.Document.loadFromData(std.string(svg)) +document.pointee.applyStyleSheet(std.string(css)) + +let bitmap = document.pointee.renderToBitmap() +_ = bitmap.writeToPng(std.string("summer.png")) +``` + +`svg` and `css` are ordinary Swift strings. + +The mapping is visible in the code: + +- The C++ namespace `lunasvg` remains visible in Swift. +- `Document::loadFromData` becomes a static method. +- `document` is the `std::unique_ptr` that `loadFromData` returns, the + smart pointer that owns the C++ object. Swift reaches the object it owns + through `pointee`, the same role `->` plays in the C++ version above. +- `renderToBitmap` returns a C++ `Bitmap` by value. +- `writeToPng` remains an ordinary member-function call. + +The `std.string(...)` conversions are explicit because Swift does not +automatically bridge a dynamic Swift `String` to `std::string`. Constructing +the C++ string allocates and copies the string data. + +Running this produces the actual LunaSVG output, `summer.png`: + +
+ summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied +
+ +## Putting It All Together + +Conan feeds Xcode through two generators, +[`XcodeDeps`](https://docs.conan.io/2/reference/tools/apple/xcodedeps.html) +and +[`XcodeToolchain`](https://docs.conan.io/2/reference/tools/apple/xcodetoolchain.html), +which turn the dependency graph into a set of `.xcconfig` files an Xcode +project can use as its build configuration. + +The generators provide the dependency and C++ toolchain settings, but they do +not add the Swift-specific interoperability options required by this target. +The `-cxx-interoperability-mode` flag and the module map still have to reach +`swiftc`, through `OTHER_SWIFT_FLAGS`, the build setting Xcode passes straight +to the Swift compiler. `XcodeToolchain` exposes `build_settings`, a plain dict +of build settings to add to the `.xcconfig` file it generates. The consumer +recipe's `generate()` sets it alongside the module map from earlier, while +`layout()` collects everything the generators write into a `generators` folder: + +{% raw %} +```python +def layout(self): + self.folders.generators = "generators" + +def generate(self): + XcodeDeps(self).generate() + + include_dir = self.dependencies["lunasvg"].cpp_info.includedir + header = f"{include_dir}/lunasvg/lunasvg.h" + + modulemap_path = os.path.join(self.generators_folder, "lunasvg.modulemap") + modulemap = textwrap.dedent(f'''\ + module LunaSVGMod {{ + header "{header}" + export * + }} + ''') + save(self, modulemap_path, modulemap) + + cppstd = cppstd_flag(self) + + tc = XcodeToolchain(self) + tc.build_settings["OTHER_SWIFT_FLAGS"] = ( + f'$(inherited) -cxx-interoperability-mode=default ' + f'-Xcc {cppstd} -Xcc -fmodule-map-file="{modulemap_path}"' + ) + tc.generate() +``` +{% endraw %} + +`cppstd_flag` turns the profile's `compiler.cppstd` into the matching `-std=` +flag, so the headers are parsed with the same standard as the rest of the +build. `$(inherited)` keeps any Swift flags the project already defines. +`build_settings` needs Conan 2.32 or newer. + +The Xcode project uses `generators/conan_config.xcconfig` as the Base +Configuration for its Release configuration, which makes everything Conan +generates — include paths, linker options, and the Swift flags above — +available to the target. + +Install the dependency, then open the project: + +```bash +conan install . -s build_type=Release --build=missing +open demo.xcodeproj +``` + +From there it is a normal Xcode project: press Run, and Swift calls into +LunaSVG. The same build also works from the command line: + +```bash +xcodebuild -project demo.xcodeproj -scheme demo -configuration Release \ + -derivedDataPath build build +./build/Build/Products/Release/demo +``` + +Running the executable writes `summer.png` to the working directory. + +Together, Conan, Xcode, and Swift's C++ interoperability make an unmodified +ConanCenter package directly callable from Swift, without a wrapper library. +Direct access, however, does not remove the usual binary compatibility +requirements of calling C++ code. + +## The Limits of Direct Interoperability + +The module map exposes LunaSVG's declarations, but Swift still calls a compiled +C++ library through the platform C++ ABI. The headers and binary therefore need +to agree on the target, compiler ABI, standard library, dependencies, and any +option that changes public declarations. Conan's package model is useful here: +it selects or builds the native artifact that sits behind the imported API. + +Direct interop also does not turn a C++ API into a Swift-safe API. The most +important points to keep in mind are: + +- C++ ownership and lifetime rules still apply. The `std::unique_ptr` in this + example must remain alive while Swift accesses `pointee`, and [raw pointers + or views](https://www.swift.org/documentation/cxx-interop/#working-with-c-references-and-view-types-in-swift) + require the same care they would in C++. +- Swift cannot catch [C++ exceptions](https://www.swift.org/documentation/cxx-interop/status/#c-exceptions). + A C++ exception that crosses the boundary terminates the program. +- Swift supports a growing subset of C++, not every template or standard-library + type. The [status page](https://www.swift.org/documentation/cxx-interop/status/) + documents the current limitations. + +For libraries with unsupported constructs, exception-heavy APIs, or difficult +lifetime rules, a small wrapper can still provide a cleaner boundary. The +difference is that it is now a design choice rather than a requirement for every +C++ library. + +> **Note**: SwiftPM can also distribute C++ libraries for use through Swift's +> C++ interoperability and is a convenient choice when a suitable package +> already exists. Here, Conan consumes LunaSVG and its transitive dependencies +> from ConanCenter without requiring the C++ dependency graph to be repackaged +> for SwiftPM. The Conan recipes reuse the libraries' upstream build systems +> rather than rewriting their configuration and platform logic in +> `Package.swift`. + +Try the [complete +example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop) +and consult the official [Swift C++ interoperability +guide](https://www.swift.org/documentation/cxx-interop/) for the complete +mapping and safety rules. + +Happy coding! + +*This post was written with AI assistance and reviewed by humans.* diff --git a/assets/post_images/2026-09-02/summer.png b/assets/post_images/2026-09-02/summer.png new file mode 100644 index 00000000..73273aaf Binary files /dev/null and b/assets/post_images/2026-09-02/summer.png differ diff --git a/css/style.css b/css/style.css index 0c5373ad..6f3cfa0a 100644 --- a/css/style.css +++ b/css/style.css @@ -420,6 +420,7 @@ body .month-content h2 { background: #eef7ff; padding: 22px; margin-bottom: 2em; + overflow-x: auto; } .container.single-post-page-container .spli-footer { padding-top: 20px;