Skip to content
Draft
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
layout: post
comments: false
title: "Using an Unmodified C++ Package Directly from Swift"
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: "Using an Unmodified C++ Package Directly from Swift with Conan - 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<Document>`, 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 = "<svg>...</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<Document>` 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`:

<div style="display: flex; justify-content: center;">
<img src="/assets/post_images/2026-09-02/summer.png" alt="summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied" width="60%"/>
</div>

## 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 `extra_xcconfig`, 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.extra_xcconfig["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.
`extra_xcconfig` 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.*
Binary file added assets/post_images/2026-09-02/summer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down