Skip to content

Commit d0ffa21

Browse files
authored
feat: Add C++ to Swift to Android integration example (#24)
1 parent 0da6cb6 commit d0ffa21

File tree

17 files changed

+652
-3
lines changed

17 files changed

+652
-3
lines changed

.gitignore

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
*.iml
2-
.gradle
2+
**/.gradle/
33
/local.properties
44
/.idea
55
.DS_Store
6-
/build
6+
build/
77
/captures
88
.externalNativeBuild
9-
.cxx
9+
**/.cxx/
1010
local.properties
1111
jniLibs
12+
13+
# C++ build artifacts
14+
**/prebuilt/
15+
16+
# Swift build artifacts
17+
**/.build/
18+
**/Package.resolved

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ This approach is ideal for production Android applications where you want to wri
2929
business logic, algorithms, or libraries in Swift, while maintaining a standard
3030
Kotlin/Java frontend.
3131

32+
## C++ Integration Example
33+
34+
The **[hello-cpp-swift](hello-cpp-swift/)** example demonstrates how to integrate
35+
C++ libraries into Android applications through Swift. This example packages a C++
36+
library as an artifactbundle, imports it as a Swift binary target, and exposes it
37+
to Android using swift-java for automatic JNI generation.
38+
39+
This pattern is useful when you have existing C++ code and want to leverage Swift's
40+
type safety and swift-java's automatic bridging to create Android-compatible libraries.
41+
3242
## Other Examples
3343

3444
For those who want to explore alternative integration patterns or understand

hello-cpp-swift/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# C++ to Swift to Android Integration
2+
3+
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.
4+
5+
## Overview
6+
7+
The project is structured into three main parts:
8+
9+
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.
10+
11+
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.
12+
13+
3. **`app`**: A standard Android application written in Kotlin that calls the Swift-wrapped C++ functions and displays the results.
14+
15+
## Prerequisites
16+
17+
Before you can build and run this project, you need to have the following installed:
18+
19+
* **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.
20+
* **Android NDK**: Required to build the C++ library. Set the `ANDROID_NDK_HOME` environment variable to your NDK installation path.
21+
22+
## Setup and Configuration
23+
24+
### 1. Build the C++ Library
25+
26+
Before building the Android app, you need to build the C++ library:
27+
28+
```bash
29+
cd hello-cpp-swift/cpp-lib
30+
./build-android-static.sh
31+
```
32+
33+
This will create the `prebuilt/HelloWorldCpp.artifactbundle` directory containing the compiled C++ static libraries for all Android architectures (arm64-v8a, armeabi-v7a, x86_64).
34+
35+
## Running the example
36+
37+
1. Open the `swift-android-examples` project in Android Studio.
38+
39+
2. Select the `hello-cpp-swift:app` Gradle target.
40+
41+
3. Run the app on an Android emulator or a physical device.
42+
43+
4. The app will display the results of C++ calculations (10 + 5 and 10 × 5) called through Swift.
44+
45+
## Building from command line
46+
47+
```bash
48+
# From the project root directory
49+
./gradlew :hello-cpp-swift:app:assembleDebug
50+
51+
# Install on device/emulator
52+
./gradlew :hello-cpp-swift:app:installDebug
53+
```
54+
55+
## How it works
56+
57+
1. **C++ Layer** (`cpp-lib/src/calculator.cpp`):
58+
```cpp
59+
int add(int a, int b) {
60+
return a + b;
61+
}
62+
```
63+
64+
2. **Swift Layer** (`swift-lib/Sources/HelloCppSwift/Calculator.swift`):
65+
```swift
66+
import HelloWorldCpp
67+
68+
public func addNumbers(_ a: Int32, _ b: Int32) -> Int32 {
69+
return add(a, b)
70+
}
71+
```
72+
73+
3. **Android/Kotlin Layer** (`app/MainActivity.kt`):
74+
```kotlin
75+
val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5)
76+
```
77+
78+
The Swift code is automatically wrapped with JNI bindings using the `swift-java` JExtract plugin, making it callable from Kotlin/Java code.
79+
80+
## Rebuilding the C++ Library
81+
82+
If you make changes to the C++ code, you need to rebuild the artifactbundle:
83+
84+
```bash
85+
cd hello-cpp-swift/cpp-lib
86+
./build-android-static.sh
87+
```
88+
89+
Then rebuild the Android app to pick up the changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
}
5+
6+
android {
7+
namespace = "com.example.hellocppswift"
8+
compileSdk = 36
9+
10+
defaultConfig {
11+
applicationId = "com.example.hellocppswift"
12+
minSdk = 28
13+
targetSdk = 36
14+
versionCode = 1
15+
versionName = "1.0"
16+
}
17+
18+
buildTypes {
19+
release {
20+
isMinifyEnabled = false
21+
proguardFiles(
22+
getDefaultProguardFile("proguard-android-optimize.txt"),
23+
"proguard-rules.pro"
24+
)
25+
}
26+
}
27+
28+
compileOptions {
29+
sourceCompatibility = JavaVersion.VERSION_11
30+
targetCompatibility = JavaVersion.VERSION_11
31+
}
32+
}
33+
34+
kotlin {
35+
compilerOptions {
36+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
37+
}
38+
}
39+
40+
dependencies {
41+
implementation(libs.androidx.core.ktx)
42+
implementation(libs.androidx.appcompat)
43+
implementation(libs.material)
44+
implementation(libs.androidx.constraintlayout)
45+
implementation("org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT")
46+
implementation(project(":hello-cpp-swift:swift-lib"))
47+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application
5+
android:allowBackup="true"
6+
android:label="@string/app_name"
7+
android:supportsRtl="true"
8+
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
9+
<activity
10+
android:name=".MainActivity"
11+
android:exported="true">
12+
<intent-filter>
13+
<action android:name="android.intent.action.MAIN" />
14+
<category android:name="android.intent.category.LAUNCHER" />
15+
</intent-filter>
16+
</activity>
17+
</application>
18+
19+
</manifest>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.example.hellocppswift
2+
3+
import android.os.Bundle
4+
import android.view.Gravity
5+
import android.widget.FrameLayout
6+
import android.widget.TextView
7+
import androidx.appcompat.app.AppCompatActivity
8+
9+
class MainActivity : AppCompatActivity() {
10+
override fun onCreate(savedInstanceState: Bundle?) {
11+
super.onCreate(savedInstanceState)
12+
13+
val textView = TextView(this).apply {
14+
textSize = 24f
15+
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
16+
setPadding(32, 32, 32, 32)
17+
gravity = Gravity.CENTER
18+
}
19+
20+
val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5)
21+
val product = com.example.hellocppswift.HelloCppSwift.multiplyNumbers(10, 5)
22+
23+
textView.text = "C++ via Swift Calculations:\n\n10 + 5 = $sum\n10 × 5 = $product"
24+
25+
val container = FrameLayout(this).apply {
26+
setPadding(0, 200, 0, 0)
27+
addView(textView)
28+
}
29+
30+
setContentView(container)
31+
}
32+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="app_name">Hello C++ Swift</string>
4+
</resources>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
project(HelloWorldCpp VERSION 1.0.0 LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
set(SOURCES
8+
src/calculator.cpp
9+
)
10+
11+
set(HEADERS
12+
include/calculator.h
13+
)
14+
15+
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
16+
17+
target_include_directories(${PROJECT_NAME}
18+
PUBLIC
19+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
20+
$<INSTALL_INTERFACE:include>
21+
)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6+
cd "$SCRIPT_DIR"
7+
8+
if [ -z "$ANDROID_NDK_HOME" ]; then
9+
echo "Error: ANDROID_NDK_HOME environment variable is not set"
10+
echo "Please set it to your Android NDK installation path"
11+
exit 1
12+
fi
13+
14+
if [ ! -d "$ANDROID_NDK_HOME" ]; then
15+
echo "Error: ANDROID_NDK_HOME points to non-existent directory: $ANDROID_NDK_HOME"
16+
exit 1
17+
fi
18+
19+
ANDROID_TOOLCHAIN="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake"
20+
21+
if [ ! -f "$ANDROID_TOOLCHAIN" ]; then
22+
echo "Error: Android toolchain file not found at: $ANDROID_TOOLCHAIN"
23+
exit 1
24+
fi
25+
26+
ANDROID_API=28
27+
28+
ABIS=(
29+
"arm64-v8a:android-aarch64"
30+
"armeabi-v7a:android-armv7"
31+
"x86_64:android-x86_64"
32+
)
33+
34+
echo "Building HelloWorldCpp static libraries for Android..."
35+
36+
for ABI_ENTRY in "${ABIS[@]}"; do
37+
ABI="${ABI_ENTRY%%:*}"
38+
echo "Building for $ABI..."
39+
40+
BUILD_DIR="build/android-static/$ABI"
41+
mkdir -p "$BUILD_DIR"
42+
43+
cmake -S . -B "$BUILD_DIR" \
44+
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_TOOLCHAIN" \
45+
-DANDROID_ABI="$ABI" \
46+
-DANDROID_PLATFORM="android-$ANDROID_API" \
47+
-DCMAKE_BUILD_TYPE=Release \
48+
-DCMAKE_CXX_STANDARD=17
49+
50+
cmake --build "$BUILD_DIR" --config Release
51+
52+
echo "✓ Built $ABI"
53+
done
54+
55+
echo ""
56+
echo "Creating artifactbundle..."
57+
58+
ARTIFACTBUNDLE_DIR="prebuilt/HelloWorldCpp.artifactbundle"
59+
60+
for ABI_ENTRY in "${ABIS[@]}"; do
61+
ABI="${ABI_ENTRY%%:*}"
62+
BUNDLE_DIR="${ABI_ENTRY##*:}"
63+
64+
mkdir -p "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers"
65+
66+
cp "build/android-static/$ABI/libHelloWorldCpp.a" "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/"
67+
68+
cp include/calculator.h "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/"
69+
cp include/module.modulemap "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/"
70+
71+
echo "✓ Created artifactbundle for $BUNDLE_DIR"
72+
done
73+
74+
cat > "$ARTIFACTBUNDLE_DIR/info.json" << 'EOF'
75+
{
76+
"schemaVersion": "1.0",
77+
"artifacts": {
78+
"HelloWorldCpp": {
79+
"version": "1.0.0",
80+
"type": "staticLibrary",
81+
"variants": [
82+
{
83+
"path": "android-aarch64/libHelloWorldCpp.a",
84+
"supportedTriples": ["aarch64-unknown-linux-android"],
85+
"staticLibraryMetadata": {
86+
"headerPaths": ["android-aarch64/Headers"],
87+
"moduleMapPath": "android-aarch64/Headers/module.modulemap"
88+
}
89+
},
90+
{
91+
"path": "android-armv7/libHelloWorldCpp.a",
92+
"supportedTriples": ["armv7-unknown-linux-android"],
93+
"staticLibraryMetadata": {
94+
"headerPaths": ["android-armv7/Headers"],
95+
"moduleMapPath": "android-armv7/Headers/module.modulemap"
96+
}
97+
},
98+
{
99+
"path": "android-x86_64/libHelloWorldCpp.a",
100+
"supportedTriples": ["x86_64-unknown-linux-android"],
101+
"staticLibraryMetadata": {
102+
"headerPaths": ["android-x86_64/Headers"],
103+
"moduleMapPath": "android-x86_64/Headers/module.modulemap"
104+
}
105+
}
106+
]
107+
}
108+
}
109+
}
110+
EOF
111+
112+
echo "✓ Generated info.json"
113+
echo ""
114+
echo "All Android static libraries built successfully!"
115+
echo "Artifactbundle created at: $ARTIFACTBUNDLE_DIR"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef CALCULATOR_H
2+
#define CALCULATOR_H
3+
4+
#ifdef __cplusplus
5+
extern "C" {
6+
#endif
7+
8+
int add(int a, int b);
9+
int multiply(int a, int b);
10+
11+
#ifdef __cplusplus
12+
}
13+
#endif
14+
15+
#endif

0 commit comments

Comments
 (0)