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.

2024-02-08 02:08:25 +0100 by Neosb

Comments:

Make first impression!

Add comment

Back to blog