Kotlin JVM Easy JNI using SWIG and Gradle CPP

Submitted by Dickens A S on Sat, 08/22/2020 - 15:34

This article demonstrates easy way of generating C++ stub code including Java Generation for the corresponding C++ Classes using SWIG

The Gradle KTS project contains a sub module which contains the main source code, the header file and the SWIG file

The Gradle KTS is written in such way it will call the "swig" command line and generates the wrappers and copies the files to corresponding locations

The C++ compiler is auto chosen by Gradle cpp-library plugin, there is no need to write CMake or MakeFile or batch files for that

The code uses the latest Gradle 7.1.1 

GitHub: https://github.com/dickensas/kotlin-gradle-templates/tree/master/swig-jni

The Gradle SWIG Task

val swigTask: Exec by tasks.creating(Exec::class) {
	commandLine (
		"swig",
		"-c++",
		"-java",
		"-cppext",
		"cpp",
		"-addextern",
		"-module",
		"${project.name}",
		"src\\main\\swig\\${project.name}.i"
	)
}

The Gradle CPP Compiler Arguments

tasks.withType(CppCompile::class.java).configureEach {
    dependsOn(swigTask)
    doFirst {
    	copy {
    		from("${project.rootDir}/${project.name}/src/main/swig")
    		into("${project.rootDir}/${project.name}/src/main/cpp")
    		exclude("*.java")
    		exclude("*.i")
    	}
    }
	compilerArgs.addAll(toolChain.map { toolChain ->
        when (toolChain) {
            is Gcc, is Clang -> listOf(
            		"-c",
            		"-v",
            		"-fpic",
            		"-I/usr/java/include",
            		"-I$javaHome/include",
            		"-I$javaHome/include/win32"
            )
            else -> listOf()
        }
    })
}

Gradle CPP Linker Arguments

tasks.withType(LinkSharedLibrary::class.java).configureEach {
	linkerArgs.addAll(toolChain.map { toolChain ->
		when (toolChain) {
            is Gcc, is Clang -> listOf(
            	"-v",
            	"-Wl,--out-implib=${project.name}.dll.a", 
            	"-Wl,--export-all-symbols",
            	"-Wl,--enable-auto-import",
            	"-Wl,--output-def=${project.name}.def"
            )
            else -> listOf()
        }
    })
    doLast {
    	copy {
    		from("${project.rootDir}/${project.name}/build/lib/main/debug")
    		into("${project.rootDir}")
    		exclude("*.java")
    		exclude("*.i")
    		exclude("*.obj")
    	}
    	
    	copy {
    		from("${project.rootDir}/${project.name}/src/main/swig")
    		into("${project.rootDir}/src/main/java")
    		exclude("*.i")
    		exclude("*.cpp")
    	}
    	
    	copy {
    		from("${project.rootDir}/${project.name}/${project.name}.dll.a")
    		into("${project.rootDir}")
    	}
    	
    	copy {
    		from("${project.rootDir}/${project.name}/${project.name}.def")
    		into("${project.rootDir}")
    	}
    }
}

this code is written generic based on the folder name of the sub project is assumed as the DLL file name

These gradle tasks plays the important role, otherwise you need to manually copy the files

The Kotlin Code

class App {
	val greeting: String
        get() {
            return Greeter().greeting()
        }
        
	companion object {
        init {
            System.loadLibrary("cpplib")
        }
    }
}

fun main(args: Array<String>) {
    println(App().greeting)
}

The C++ code

#include <iostream>
#include <stdlib.h>
#include "cpplib.h"

std::string jniexample::Greeter::greeting() {
    return std::string("Hello, World! From C++");
}

The SWIG Code

%include "std_string.i" 
%{
#include "../public/cpplib.h"
%}

%include "../public/cpplib.h"

Final Output

  > Task :run
  Hello, World! From C++

You need to install SWIG,
I used MSYS2 and installed swig by issuing command

pacman -S base-devel

JAVA_HOME environment variable need to be set 
From gradle this code will take the JAVA_HOME

val javaHome = System.getenv("JAVA_HOME")

Current target architecture is WIndows, it needs be changed for Linux or other targets

targetMachines.add(machines.windows.x86_64)

Finished!

 

Add new comment