Combine / Call Fortran DLL inside Kotlin Native using Gradle KTS

Submitted by Dickens A S on Thu, 07/22/2021 - 10:54

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!

 

 

Fortran
GFortran
MSYS2
Kotlin Native
kotlin
DLL
Windows

Add new comment