Call Fortran from Kotlin Native
This article demonstrates how to change the toolchain name using Gradle KTS and invoke gfortran compiler instead of g++ compiler and produce a DLL and reuse a Fortran subroutine or function inside a Kotlin Native code
There is a sub project named libfort1 which has Gradle KTS file, it has been improved to trigger a different exe command line using Gradle Action APIs, which allows the developer to modify the executable as well as parameters sent to the executable
Make sure you install Fortran inside MSYS2 before you proceed with the execution
$ pacman -S mingw-w64-x86_64-gcc-libgfortran mingw-w64-x86_64-gcc-fortran
libfort1 build.gradle.kts file
plugins {
`cpp-library`
}
class CompiterArgumentsAction : Action<MutableList<String>> {
override fun execute(args: MutableList<String>) {
args.clear()
args.add("-v")
args.add("-c")
args.add("-fPIC")
args.add("-shared")
args.add("-o")
args.add("${project.name}.dll")
args.add("-Wl,--out-implib,${project.name}.dll.a")
args.add("-Wl,--output-def,${project.name}.def")
args.add("-Wl,--export-all-symbols")
args.add("-Wl,--enable-auto-import")
}
}
class PlatformToolChainAction : Action<GccPlatformToolChain> {
override fun execute(toolChain: GccPlatformToolChain) {
toolChain.getCppCompiler().setExecutable("gfortran")
toolChain.getCppCompiler().withArguments(CompiterArgumentsAction())
toolChain.getcCompiler().setExecutable("gfortran")
toolChain.getcCompiler().withArguments(CompiterArgumentsAction())
toolChain.getLinker().setExecutable("gfortran")
}
}
fun buildFile(path: String) = layout.buildDirectory.file(path)
library {
targetMachines.add(machines.windows.x86_64)
linkage.set(listOf(Linkage.SHARED))
toolChains.configureEach {
when (this) {
is GccCompatibleToolChain -> {
this.eachPlatform(PlatformToolChainAction() as Action<in GccPlatformToolChain>)
}
else -> {}
}
}
}
tasks.withType(CppCompile::class.java).configureEach {
compilerArgs.addAll(toolChain.map { toolChain ->
when (toolChain) {
is Gcc, is Clang -> listOf()
else -> listOf()
}
})
source.from(fileTree("${project.rootDir}/${project.name}/src/main/fortran").matching {
include("*.f")
})
}
tasks.withType(LinkSharedLibrary::class.java).configureEach {
doFirst {
copy {
from(fileTree("${project.rootDir}/${project.name}/build/obj/main/debug").matching {
include("${project.name}.d*")
include("${project.name}.d*.*")
})
into("${project.rootDir}/${project.name}")
}
}
linkerArgs.addAll(toolChain.map { toolChain ->
when (toolChain) {
is Gcc, is Clang -> listOf(
"-v",
"-shared",
"-o",
"${project.name}.dll",
"-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(fileTree("${project.rootDir}/${project.name}").matching {
include("${project.name}.d*")
include("${project.name}.d*.*")
})
into("${project.rootDir}")
}
}
}
tasks.withType(Delete::class) {
delete(fileTree("${project.rootDir}/${project.name}").matching {
include("${project.name}.d*")
include("${project.name}.d*.*")
})
}
Fortran Code
subroutine test1() bind(c)
use ISO_C_BINDING
implicit none
cGCC$ ATTRIBUTES STDCALL, DLLEXPORT :: test1
cDEC$ ATTRIBUTES STDCALL, DLLEXPORT :: test1
PRINT *, 'I am a fortran subroutine'
return
end subroutine test1
In the above code the two lines decides the DLL signature being exported to the libfort1.dll
cGCC$ ATTRIBUTES STDCALL, DLLEXPORT :: test1
cDEC$ ATTRIBUTES STDCALL, DLLEXPORT :: test1
Here in cGCC, "c" is the first column comment of any fortran code, you can also use exclamation symbol "!" at 7 th column, both will work
GitHub: https://github.com/dickensas/kotlin-gradle-templates/tree/master/kotlin-fortran
Kotline Code
package plot
import kotlinx.cinterop.*
import libfort1.*
fun main()
{
test1()
}
kotlin cinterop libfort1.def
package = libfort1
libraryPaths = C:/msys64/mingw64/lib
linkerOpts = -LC:/msys64/mingw64/lib -lfort1 -v
compilerOpts = -LC:/msys64/mingw64/lib -lfort1 -v
---
extern void __stdcall test1();
here the extern void __stdcall test1(); decides the import of the Fortran symbol into Kotlin or C
When you run gradlew runDebugExecutableLibgnuplot within MSYS2 terminal you get
Output
> Task :runDebugExecutableLibgnuplot
I am a fortran subroutine
Finished!