lynnswap/abibridge
Resolve mangled Swift and C++ symbols by source-level name, and call supported native functions and methods with ordinary Swift types and values.
Requirements
- iOS 18.4+, macOS 15.4+, visionOS 2.4+, watchOS 11.4+, or tvOS 18.4+
- arm64, arm64e, or arm64_32
- Xcode on macOS with Swift 6.3+
In Xcode, add https://github.com/lynnswap/ABIBridge as a package dependency and select the ABIBridge product. Add ABIBridgeSwiftUI if you need to display native SwiftUI views.
What you can do
Use resolve for async lookup or resolveAndWait for synchronous lookup. Keep the returned handle for repeated calls, and invoke it on the thread required by the native API.
Resolve a C++ function without writing its mangled name
A C++ function declared as int Example::Math::add(int, int) has a compiler-generated symbol such as _ZN7Example4Math3addEii. A direct lookup requires that mangled name:
import Darwin
let address = dlsym(libraryHandle, "_ZN7Example4Math3addEii")With ABIBridge, use the declaration name from the source:
import ABIBridge
let runtime = ABIRuntime.shared
let symbol = try runtime.resolveAndWait(
.cxx(.symbol("Example::Math::add(int, int)"))
)See symbol lookup for scopes and optional preparation.
Call C++ by its declaration
To call that function, provide its signature as a Swift function type. Specifying a framework acquires it when needed:
let add = try runtime.resolveAndWait(
.cxx(.function("Example::Math::add(int, int)", as: ((Int32, Int32) -> Int32).self)),
in: .framework(named: "Example")
)
let sum = try unsafe add.unsafeInvoke(20, 22)Use .c(.function(...)) for C declarations. Save a reusable request with NativeRequest.c(...) or NativeRequest.cxx(...).
For C++ object methods, bind receiver storage with cxxObject and resolve .cxx(.method(...)) or, for a selected virtual slot, .cxx(.virtualMethod(...)). See C++ object calls for signatures, table ownership, and unsafe virtual resolution.
Call Swift by its source-level name
For an already-loaded module defining func decorate(_ value: String) -> String:
let decorate = try runtime.resolveAndWait(.swift(.function(
"Example.decorate(_:)", as: ((String) -> String).self
)))
let message = try unsafe decorate.unsafeInvoke("Hello")Save a reusable request with NativeRequest.swift(.function(...)). Native async functions use await during invocation; see Swift function calls.
Generic declarations use the same callable handles with explicit type arguments:
let echo = try runtime.resolveAndWait(.swift(.function(
"Example.echo(_:)", as: ((String) -> String).self,
genericArguments: [.type(String.self)]
)))
let message = try unsafe echo.unsafeInvoke("Hello")See Swift generic calls for constraints, packs, and generic members. Swift closure values covers closures in tuples; argument conventions covers replacing values through NativeSwiftInout.
Call Swift type members
Resolve a native type, then select its initializers, methods, or properties. For a renderer with func title() -> String:
@MainActor
func readTitle(from renderer: AnyObject) async throws -> String {
let type = try await ABIRuntime.shared.resolve(.swift(.type("Example.Renderer")))
let title = try await type.resolve(.swift(.method("title()", as: (() -> String).self)))
return try unsafe title.unsafeInvoke(on: renderer)
}The same request resolves on runtime.object(renderer) to bind the receiver, which stays in the caller's isolation domain. See Swift types and members for initializers, static methods, and properties.
Call an existing Objective-C instance
For a renderer exposing setImage:animated:, pass ordinary Swift values:
import UIKit
let setImage = try runtime.object(renderer).resolveAndWait(.objc(.method(
"setImage:animated:",
as: ((UIImage?, Bool) -> Void).self
)))
try unsafe setImage.unsafeInvoke(image, true)
try unsafe setImage.unsafeInvoke(nil, false)Use .objc(.imp(...)) to capture a fixed implementation. See Objective-C calls for message dispatch and receiver ownership.
Display a native SwiftUI view
For a native factory returning some View, add the optional ABIBridgeSwiftUI product and use its owned wrapper on MainActor:
import ABIBridgeSwiftUI
let makePanel = try runtime.resolveAndWait(.swift(.function(
"Example.makePanel(_:)", as: ((String) -> NativeSwiftValue).self
)))
let panel = try NativeSwiftView(unsafe makePanel.unsafeInvoke("Hello"))Place panel in your SwiftUI hierarchy or a standard hosting controller. It retains the hidden view and its implementation images, even when the provider module is not importable.
Documentation
More examples and API contracts are available in DocC:
- C and C++ functions and C++ object methods
- Swift types and members and custom value adapters
- SwiftUI hosting, concrete values, and opaque views
- Symbol lookup and C/C++/Objective-C++ interfaces
- Hooks and replacements
- Migrating from earlier APIs
- Memory reads and pointer discovery
For build and test instructions, see CONTRIBUTING.md.
Acknowledgements
Symbol resolution uses MachOKit to read Mach-O images and dyld shared caches. Objective-C type decoding uses ObjCTypeDecodeKit. Thank you to p-x9 and the contributors to MachOKit and swift-objc-dump.
The C ABI backend uses libffi, packaged for SwiftPM by ZDLibffi. ABIBridge uses a fork with arm64e authentication fixes. Thank you to their authors and contributors.
License
MIT License. Copyright (c) 2026 Kazuki Nakashima.
Package Metadata
Repository: lynnswap/abibridge
Default branch: main
README: README.md