Skip to content

Call Kt-Math from C

Kotlin/Native can compile a Kotlin library into a C shared library, with a header declaring its whole API: this is how Kt-Math can be used from C (or any language with a C foreign-function interface).

For Linux, macOS and Windows, the GitHub releases ship the library prebuilt, exporting Kt-Math's whole API: see Getting started in C. Build it yourself for other Kotlin/Native targets, or to export your own Kotlin code along with Kt-Math.

Build the shared library

In a Kotlin Multiplatform project depending on Kt-Math, declare a shared library for your native target, exporting Kt-Math's API:

kotlin {
    linuxX64 { // or macosArm64, mingwX64, ...
        binaries.sharedLib("ktmath") {
            export("io.github.gciatto:kt-math:VERSION")
        }
    }
    sourceSets {
        nativeMain {
            dependencies {
                api("io.github.gciatto:kt-math:VERSION")
            }
        }
    }
}

./gradlew linkKtmathReleaseSharedLinuxX64 writes libktmath.so (.dylib on macOS, .dll on Windows) and libktmath_api.h to build/bin/linuxX64/ktmathReleaseShared/.

Use it from C

The header exposes a libktmath_symbols() function, returning a structure that mirrors Kotlin's packages and classes:

  • every Kotlin object is a handle, a structure holding a pointer, to release with DisposeStablePointer;
  • every string returned by Kotlin is to release with DisposeString;
  • member functions take the receiver as first argument, companion objects are reached through _instance();
  • overloads are distinguished by trailing underscores, in declaration order;
  • a Kotlin exception aborts the process: prefer the parse*OrNull functions of BigFactory, which return a null handle on invalid input.

This program is the beginning of Kt-Math's own C usage test, compiled and run by CI against Kt-Math's shared library (the rest of src/nativeUsageTest/c/usage.c calls every other function of the C API):

// Every Kotlin object is a handle (a struct holding a pointer) to dispose of with DisposeStablePointer, every returned
// string is to dispose of with DisposeString; overloads get trailing underscores, in declaration order. Kotlin
// exceptions abort the process: the parse*OrNull functions return null handles instead.
#include <stdio.h>
#include <string.h>

#include "libktmath_api.h"

static libktmath_ExportedSymbols *lib;
static int failures = 0;

#define KT_MATH (lib->kotlin.root.org.gciatto.kt.math)
#define DISPOSE(handle) lib->DisposeStablePointer((handle).pinned)

// Checks that a string returned by Kotlin is the expected one, then frees it
static void expect(const char *what, const char *actual, const char *expected) {
    if (strcmp(actual, expected) != 0) {
        printf("FAILED %s: expected %s, got %s\n", what, expected, actual);
        failures++;
    } else {
        printf("ok %s = %s\n", what, actual);
    }
    lib->DisposeString(actual);
}

static void big_integers(libktmath_kref_org_gciatto_kt_math_BigFactory factory) {
    libktmath_kref_org_gciatto_kt_math_BigInteger x =
        KT_MATH.BigFactory.parseBigInteger(factory, "123456789012345678901234567890", 10);
    libktmath_kref_org_gciatto_kt_math_BigInteger y = KT_MATH.BigFactory.bigIntegerOf(factory, 42);
    libktmath_kref_org_gciatto_kt_math_BigInteger product = KT_MATH.BigInteger.times(x, y);
    expect("x * 42", KT_MATH.BigInteger.toString_(product), "5185185138518518513851851851380");
    libktmath_kref_org_gciatto_kt_math_BigInteger quotient = KT_MATH.BigInteger.div(x, y);
    expect("x / 42", KT_MATH.BigInteger.toString_(quotient), "2939447357436801878600823045");
    expect("x in hex", KT_MATH.BigInteger.toString(x, 16), "18ee90ff6c373e0ee4e3f0ad2");
    if (KT_MATH.BigInteger.compare(x, y) <= 0) {
        printf("FAILED compare(x, 42) > 0\n");
        failures++;
    }
    libktmath_kref_org_gciatto_kt_math_BigInteger invalid = KT_MATH.BigFactory.parseBigIntegerOrNull(factory, "12x", 10);
    if (invalid.pinned != NULL) {
        printf("FAILED parseBigIntegerOrNull(\"12x\") is null\n");
        failures++;
    }
    DISPOSE(quotient);
    DISPOSE(product);
    DISPOSE(y);
    DISPOSE(x);
}

static void big_decimals(libktmath_kref_org_gciatto_kt_math_BigFactory factory) {
    libktmath_kref_org_gciatto_kt_math_MathContext_Companion contexts = KT_MATH.MathContext.Companion._instance();
    libktmath_kref_org_gciatto_kt_math_MathContext unlimited = KT_MATH.MathContext.Companion.get_UNLIMITED(contexts);
    libktmath_kref_org_gciatto_kt_math_BigDecimal a = KT_MATH.BigFactory.parseBigDecimal(factory, "1.10", unlimited);
    libktmath_kref_org_gciatto_kt_math_BigDecimal b = KT_MATH.BigFactory.bigDecimalOf(factory, 3, unlimited);
    libktmath_kref_org_gciatto_kt_math_RoundingMode half_even = KT_MATH.RoundingMode.HALF_EVEN.get();
    libktmath_kref_org_gciatto_kt_math_MathContext ten_digits = KT_MATH.MathContext.MathContext(10, half_even);
    libktmath_kref_org_gciatto_kt_math_BigDecimal quotient = KT_MATH.BigDecimal.div_(a, b, ten_digits);
    expect("1.10 / 3", KT_MATH.BigDecimal.toPlainString(quotient), "0.3666666667");
    libktmath_kref_org_gciatto_kt_math_BigDecimal root = KT_MATH.BigDecimal.sqrt(b, ten_digits);
    expect("sqrt(3)", KT_MATH.BigDecimal.toPlainString(root), "1.732050808");
    libktmath_kref_org_gciatto_kt_math_BigDecimal log = KT_MATH.big.log(b, ten_digits); // big-math's functions
    expect("log(3)", KT_MATH.BigDecimal.toPlainString(log), "1.098612289");
    DISPOSE(log);
    DISPOSE(root);
    DISPOSE(quotient);
    DISPOSE(ten_digits);
    DISPOSE(half_even);
    DISPOSE(b);
    DISPOSE(a);
    DISPOSE(unlimited);
    DISPOSE(contexts);
}

static void big_rationals(libktmath_kref_org_gciatto_kt_math_BigFactory factory) {
    libktmath_kref_org_gciatto_kt_math_BigRational third = KT_MATH.BigFactory.parseBigRational(factory, "1/3");
    libktmath_kref_org_gciatto_kt_math_BigRational sixth = KT_MATH.BigFactory.bigRationalOf_____(factory, 1, 6);
    libktmath_kref_org_gciatto_kt_math_BigRational sum = KT_MATH.BigRational.plus(third, sixth);
    libktmath_kref_org_gciatto_kt_math_BigRational reduced = KT_MATH.BigRational.reduce(sum);
    expect("1/3 + 1/6", KT_MATH.BigRational.toRationalString(reduced), "1/2");
    libktmath_kref_org_gciatto_kt_math_BigRational invalid = KT_MATH.BigFactory.parseBigRationalOrNull(factory, "1/0");
    if (invalid.pinned != NULL) {
        printf("FAILED parseBigRationalOrNull(\"1/0\") is null\n");
        failures++;
    }
    DISPOSE(reduced);
    DISPOSE(sum);
    DISPOSE(sixth);
    DISPOSE(third);
}

int main(void) {
    lib = libktmath_symbols();
    libktmath_kref_org_gciatto_kt_math_BigFactory_Companion companion = KT_MATH.BigFactory.Companion._instance();
    libktmath_kref_org_gciatto_kt_math_BigFactory factory = KT_MATH.BigFactory.Companion.get_default(companion);
    big_integers(factory);
    big_decimals(factory);
    big_rationals(factory);
    return failures == 0 ? 0 : 1;
}

Functions taking a Kotlin IntArray, Array, Random or range can't be called from C, which can't create those; Kotlin arrays and sequences returned to C can only be passed on and disposed of.

Compile it against the library: cc usage.c -I<dir> -L<dir> -lktmath -Wl,-rpath,<dir>.