Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
13 changes: 10 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
*.iml
.gradle
**/.gradle/
/local.properties
/.idea
.DS_Store
/build
build/
/captures
.externalNativeBuild
.cxx
**/.cxx/
local.properties
jniLibs

# C++ build artifacts
**/prebuilt/

# Swift build artifacts
**/.build/
**/Package.resolved
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ This approach is ideal for production Android applications where you want to wri
business logic, algorithms, or libraries in Swift, while maintaining a standard
Kotlin/Java frontend.

## C++ Integration Example

The **[hello-cpp-swift](hello-cpp-swift/)** example demonstrates how to integrate
C++ libraries into Android applications through Swift. This example packages a C++
library as an artifactbundle, imports it as a Swift binary target, and exposes it
to Android using swift-java for automatic JNI generation.

This pattern is useful when you have existing C++ code and want to leverage Swift's
type safety and swift-java's automatic bridging to create Android-compatible libraries.

## Other Examples

For those who want to explore alternative integration patterns or understand
Expand Down
89 changes: 89 additions & 0 deletions hello-cpp-swift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# C++ to Swift to Android Integration

This example demonstrates how to call C++ code from Android through Swift. The app uses a C++ library that provides basic calculator functions (add and multiply), wraps them in Swift, and calls them from an Android Kotlin app.

## Overview

The project is structured into three main parts:

1. **`cpp-lib`**: A C++ library with basic calculator functions (`add` and `multiply`). This is built using CMake and packaged as an artifactbundle for consumption by Swift.

2. **`swift-lib`**: A Swift package that wraps the C++ functions and exposes them to Android using [swift-java](https://github.com/swiftlang/swift-java). The Swift code calls the C++ functions and provides JNI bindings automatically.

3. **`app`**: A standard Android application written in Kotlin that calls the Swift-wrapped C++ functions and displays the results.

## Prerequisites

Before you can build and run this project, you need to have the following installed:

* **Basic setup**: Follow the Prerequisites and Setup instructions in [hello-swift-java/README.md](../hello-swift-java/README.md) to install JDK, Swiftly, Swift SDK for Android, and publish the swift-java packages locally.
* **Android NDK**: Required to build the C++ library. Set the `ANDROID_NDK_HOME` environment variable to your NDK installation path.

## Setup and Configuration

### 1. Build the C++ Library

Before building the Android app, you need to build the C++ library:

```bash
cd hello-cpp-swift/cpp-lib
./build-android-static.sh
```

This will create the `prebuilt/HelloWorldCpp.artifactbundle` directory containing the compiled C++ static libraries for all Android architectures (arm64-v8a, armeabi-v7a, x86_64).

## Running the example

1. Open the `swift-android-examples` project in Android Studio.

2. Select the `hello-cpp-swift:app` Gradle target.

3. Run the app on an Android emulator or a physical device.

4. The app will display the results of C++ calculations (10 + 5 and 10 × 5) called through Swift.

## Building from command line

```bash
# From the project root directory
./gradlew :hello-cpp-swift:app:assembleDebug

# Install on device/emulator
./gradlew :hello-cpp-swift:app:installDebug
```

## How it works

1. **C++ Layer** (`cpp-lib/src/calculator.cpp`):
```cpp
int add(int a, int b) {
return a + b;
}
```

2. **Swift Layer** (`swift-lib/Sources/HelloCppSwift/Calculator.swift`):
```swift
import HelloWorldCpp

public func addNumbers(_ a: Int32, _ b: Int32) -> Int32 {
return add(a, b)
}
```

3. **Android/Kotlin Layer** (`app/MainActivity.kt`):
```kotlin
val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5)
```

The Swift code is automatically wrapped with JNI bindings using the `swift-java` JExtract plugin, making it callable from Kotlin/Java code.

## Rebuilding the C++ Library

If you make changes to the C++ code, you need to rebuild the artifactbundle:

```bash
cd hello-cpp-swift/cpp-lib
./build-android-static.sh
```

Then rebuild the Android app to pick up the changes.
47 changes: 47 additions & 0 deletions hello-cpp-swift/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.example.hellocppswift"
compileSdk = 36

defaultConfig {
applicationId = "com.example.hellocppswift"
minSdk = 28
targetSdk = 36
versionCode = 1
versionName = "1.0"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation("org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT")
implementation(project(":hello-cpp-swift:swift-lib"))
}
19 changes: 19 additions & 0 deletions hello-cpp-swift/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.hellocppswift

import android.os.Bundle
import android.view.Gravity
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val textView = TextView(this).apply {
textSize = 24f
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
setPadding(32, 32, 32, 32)
gravity = Gravity.CENTER
}

val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5)
val product = com.example.hellocppswift.HelloCppSwift.multiplyNumbers(10, 5)

textView.text = "C++ via Swift Calculations:\n\n10 + 5 = $sum\n10 × 5 = $product"

val container = FrameLayout(this).apply {
setPadding(0, 200, 0, 0)
addView(textView)
}

setContentView(container)
}
}
4 changes: 4 additions & 0 deletions hello-cpp-swift/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hello C++ Swift</string>
</resources>
21 changes: 21 additions & 0 deletions hello-cpp-swift/cpp-lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.18)
project(HelloWorldCpp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(SOURCES
src/calculator.cpp
)

set(HEADERS
include/calculator.h
)

add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})

target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
115 changes: 115 additions & 0 deletions hello-cpp-swift/cpp-lib/build-android-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/bin/bash

set -e

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"

if [ -z "$ANDROID_NDK_HOME" ]; then
echo "Error: ANDROID_NDK_HOME environment variable is not set"
echo "Please set it to your Android NDK installation path"
exit 1
fi

if [ ! -d "$ANDROID_NDK_HOME" ]; then
echo "Error: ANDROID_NDK_HOME points to non-existent directory: $ANDROID_NDK_HOME"
exit 1
fi

ANDROID_TOOLCHAIN="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake"

if [ ! -f "$ANDROID_TOOLCHAIN" ]; then
echo "Error: Android toolchain file not found at: $ANDROID_TOOLCHAIN"
exit 1
fi

ANDROID_API=28

ABIS=(
"arm64-v8a:android-aarch64"
"armeabi-v7a:android-armv7"
"x86_64:android-x86_64"
)

echo "Building HelloWorldCpp static libraries for Android..."

for ABI_ENTRY in "${ABIS[@]}"; do
ABI="${ABI_ENTRY%%:*}"
echo "Building for $ABI..."

BUILD_DIR="build/android-static/$ABI"
mkdir -p "$BUILD_DIR"

cmake -S . -B "$BUILD_DIR" \
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_TOOLCHAIN" \
-DANDROID_ABI="$ABI" \
-DANDROID_PLATFORM="android-$ANDROID_API" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17

cmake --build "$BUILD_DIR" --config Release

echo "✓ Built $ABI"
done

echo ""
echo "Creating artifactbundle..."

ARTIFACTBUNDLE_DIR="prebuilt/HelloWorldCpp.artifactbundle"

for ABI_ENTRY in "${ABIS[@]}"; do
ABI="${ABI_ENTRY%%:*}"
BUNDLE_DIR="${ABI_ENTRY##*:}"

mkdir -p "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers"

cp "build/android-static/$ABI/libHelloWorldCpp.a" "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/"

cp include/calculator.h "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/"
cp include/module.modulemap "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/"

echo "✓ Created artifactbundle for $BUNDLE_DIR"
done

cat > "$ARTIFACTBUNDLE_DIR/info.json" << 'EOF'
{
"schemaVersion": "1.0",
"artifacts": {
"HelloWorldCpp": {
"version": "1.0.0",
"type": "staticLibrary",
"variants": [
{
"path": "android-aarch64/libHelloWorldCpp.a",
"supportedTriples": ["aarch64-unknown-linux-android"],
"staticLibraryMetadata": {
"headerPaths": ["android-aarch64/Headers"],
"moduleMapPath": "android-aarch64/Headers/module.modulemap"
}
},
{
"path": "android-armv7/libHelloWorldCpp.a",
"supportedTriples": ["armv7-unknown-linux-android"],
"staticLibraryMetadata": {
"headerPaths": ["android-armv7/Headers"],
"moduleMapPath": "android-armv7/Headers/module.modulemap"
}
},
{
"path": "android-x86_64/libHelloWorldCpp.a",
"supportedTriples": ["x86_64-unknown-linux-android"],
"staticLibraryMetadata": {
"headerPaths": ["android-x86_64/Headers"],
"moduleMapPath": "android-x86_64/Headers/module.modulemap"
}
}
]
}
}
}
EOF

echo "✓ Generated info.json"
echo ""
echo "All Android static libraries built successfully!"
echo "Artifactbundle created at: $ARTIFACTBUNDLE_DIR"
15 changes: 15 additions & 0 deletions hello-cpp-swift/cpp-lib/include/calculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef CALCULATOR_H
#define CALCULATOR_H

#ifdef __cplusplus
extern "C" {
Copy link
Author

@NakaokaRei NakaokaRei Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now although I used extern "C" to wrap C++ function since the build while enabling C++ interop feature is failed, once following PR is merged, Im going to remove this and to enable C++ interop in other PR!

swiftlang/swift-java#463

#endif

int add(int a, int b);
int multiply(int a, int b);

#ifdef __cplusplus
}
#endif

#endif
4 changes: 4 additions & 0 deletions hello-cpp-swift/cpp-lib/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module HelloWorldCpp {
header "calculator.h"
export *
}
Loading