C library in iOS and Android
Table of contents:
Call C library from Ruby, Python, Rust, iOS and Android app
In my ad venture I will first use my logo written in ASCII and saved on GitHub. I add to it a file that contains two methods, logo and logo_black which both return a string with a logo, but one is colorised for terminal output.
My logo - use your own
char* logo_black() {
char* string = "your ASCII logo here";
return string;
}
Compile on macOS
CC = gcc
CFLAGS = -Wall
all: logo
logo:
$(CC) $(CFLAGS) logo-lib.c logo.c -o logo
$(CC) $(CFLAGS) -o liblogo.dylib -shared -fPIC logo-lib.c
Copy the *.dylib library out of your folder to some nice place where you can find it.
C to Ruby
First, I will use Ruby to call the C library. I will use the ffi gem to do so.
require 'ffi'
module Logo
extend FFI::Library
ffi_lib 'liblogo.dylib'
attach_function :logo_black, [], :string
end
puts Logo.logo_black
So it's easy for Ruby to call C libraries.
C to Python
Python is also easy to call C libraries. I will use the ctypes library to do so.
import ctypes
lib = ctypes.CDLL('./liblogo.dylib')
lib.logo_black.restype = ctypes.c_char_p
result = lib.logo_black()
print(result.decode('utf-8'))
It requires a bit more code than Ruby, but it's still easy.
C to Node.js
Node.js is also easy to call C libraries. I will use the ffi library to do so.
var ffi = require('ffi'); // or require('ffi-napi')
var lib = ffi.Library('./liblogo.dylib', {
'logo_black': ['string', []]
});
console.log(lib.logo_black());
Fortunately for me it does not work. Neither ffi nor ffi-napi work. They simply do not install on my macOS. I will try to use Rust instead.
C to Rust
I will use the rust-ffi library to call the C library. I will use the libc library to do so.
# Cargo.toml
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
//rootOfProject/build.rs
extern crate cc;
fn main() {
cc::Build::new()
.file("path/to/your_c_file.c")
.compile("your_c_library");
}
// src/main.rs
extern crate libc;
use libc::c_char;
use std::ffi::CStr;
#[link(name = "your_c_library")]
extern "C" {
fn logo_black() -> *const c_char;
}
fn main() {
unsafe {
let result = logo_black();
let c_str = CStr::from_ptr(result);
let str_slice = c_str.to_str().unwrap();
let str_buf = str_slice.to_owned(); // if you want to convert it to a String
println!("{}", str_buf);
}
}
As you can see, it's a bit more complicated than Ruby, Python but works instead of Node.js.
Good point here is to make yourself a wrapper around what you want to call from Rust.
C to Swift
I will use Swift to call the C library. I will use the import Foundation library to do so.
import Foundation
func callDynamicLibraryFunction(functionName: String) -> String? {
// Replace with the path to your dynamic library
let libraryPath = "liblogo.dylib"
// Replace with the name of your function
let functionName = functionName
// Open the dynamic library
guard let handle = dlopen(libraryPath, RTLD_NOW) else {
print("Unable to open library: \(String(cString: dlerror()))")
return nil
}
// Get the function from the library
guard let function = dlsym(handle, functionName) else {
print("Unable to find function: \(String(cString: dlerror()))")
dlclose(handle)
return nil
}
// Define the function type
typealias CFunction = @convention(c) () -> UnsafePointer<CChar>
// Cast the function to the correct type
let functionTyped = unsafeBitCast(function, to: CFunction.self)
// Call the function and get the result
let result = functionTyped()
// Check if the result is a null pointer
if result == nil {
print("Function returned a null pointer")
dlclose(handle)
return nil
}
// Try to create a Swift string from the result
guard let stringResult = String(validatingUTF8: result) else {
print("Function returned an invalid string")
dlclose(handle)
return nil
}
// Close the library
dlclose(handle)
// Return the result as a Swift string
return stringResult
}
if let result = callDynamicLibraryFunction(functionName: "logo") {
print("\(result)")
} else {
print("Failed to call function")
}
C to C# (CSharp)
I will use the PInvoke to call the C library. I will use the DllImport attribute to do so.
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("../../liblogo.dylib")]
// dynamic char* logo_black() with size fixed with malloc and free;
// public static extern string logo_black();
public static extern IntPtr logo_black();
static void Main()
{
// string logoBlackString = logo_black();
string? logoBlackString = Marshal.PtrToStringAnsi(logo_black());
Console.WriteLine(logoBlackString);
}
}
If you want to use string instead of IntPtr you need to use malloc and free in your C library.
char* string = malloc(1024 * sizeof(char)); // Allocate memory dynamically
strcpy(string,
"Your ASCII logo here");
return string;
free(string); // Free memory, just in case :)
So far so good. I can call my C library from Ruby, Python, Rust, and Swift. I will try to call it from Android now.
C++ to Android
I will create new Android project in Android Studio, but I will use Native C++ option to create a new project. I will use the JNI to call the C library.
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
Remember that C++ is superset of C, so you can use C libraries in C++.
To run your stringFromJNI method, you need to call it from your MainActivity class.
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib") // this is the name of your library
// (it gets lib prefix and .so suffix)
}
}
external fun stringFromJNI(): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sampleText.text = stringFromJNI()
}
}
C to iOS
In Swift and Xcode, the easiest way to import your library to your project is to use the Bridging-Header.h file. You can add it to your project by adding a new file to your project and selecting Header File from the Source section or C file and tick Header File, then when prompted agree to create Bridging header file.
// Bridging-Header.h
#include "logo.h"
Don't forget to add your source code to your project.
// logo.h
char* logo_black();
and in logo.c
char* logo_black() {
char* string = "your ASCII logo here";
return string;
}
Then you can use your library in Swift.
// ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text(String(cString: logo_black()))
Spacer()
}
.padding()
}
}
Conclusion
You can see that it's easy to call C libraries from Ruby, Python, Rust, Swift, C#, iOS and Android. It's a bit more complicated to call it from Node.js, but it's possible.
I hope you find this article useful. If you have any questions, feel free to ask me, you can leave a comment or contact me on Twitter/X.
Comments:
Make first impression!