Kotlin JNA getting declared field names to be empty list

Tags: , , ,



The code below is highly inspired by this answer and this answer, and I am trying to get it work with Kotlin language, rather than Java that was given in the answer there.

It is basically based on JNA (Java Native Access), which essentially pulls the SYSTEM_BATTERY_STATE from the Windows Native library (on C++) to get information about the battery on Laptops.

The code works as expected in Java version, but if I try to do the same with Kotlin, I get an exception saying that the declared field names is an empty list/array.

fun main() { getBatteryState() }

fun getBatteryState(): SYSTEM_BATTERY_STATE? {
    val batteryState = SYSTEM_BATTERY_STATE()
    val retrieveValue = PowrProf.CallNtPowerInformation(
        5,
        Pointer.NULL,
        0,
        batteryState,
        batteryState.size().toLong()
    )

    return if (retrieveValue == 0) batteryState else null
}

interface PowrProf : StdCallLibrary {
    @Suppress("FunctionName")
    fun CallNtPowerInformation(informationLevel: Int, inBuffer: Pointer?, inBufferLen: Long, outBuffer: SYSTEM_BATTERY_STATE?, outBufferLen: Long): Int

    companion object : PowrProf by Native.load("PowrProf", PowrProf::class.java)!!
}

class SYSTEM_BATTERY_STATE : Structure(ALIGN_MSVC), Structure.ByReference {
    var AcOnLine: Byte = 0
    var BatteryPresent: Byte = 0
    var Charging: Byte = 0
    var Discharging: Byte = 0

    var Spare1_0: Byte = 0
    var Spare1_1: Byte = 0
    var Spare1_2: Byte = 0
    var Spare1_3: Byte = 0

    var MaxCapacity = 0
    var RemainingCapacity = 0
    var Rate = 0

    var EstimatedTime = 0
    var DefaultAlert1 = 0
    var DefaultAlert2 = 0

    override fun getFieldOrder(): List<String> {
        return listOf(
            "AcOnLine", "BatteryPresent", "Charging", "Discharging",
            "Spare1_0", "Spare1_1", "Spare1_2", "Spare1_3",
            "MaxCapacity", "RemainingCapacity", "Rate",
            "EstimatedTime", "DefaultAlert1", "DefaultAlert2"
        )
    }
}
Exception in thread "main" java.lang.Error: Structure.getFieldOrder() on class com.animeshz.github.batteryinfo.SYSTEM_BATTERY_STATE returns names ([AcOnLine, BatteryPresent, Charging, DefaultAlert1, DefaultAlert2, Discharging, EstimatedTime, MaxCapacity, Rate, RemainingCapacity, Spare1_0, Spare1_1, Spare1_2, Spare1_3]) which do not match declared field names ([])
    at com.sun.jna.Structure.getFields(Structure.java:1089)
    at com.sun.jna.Structure.deriveLayout(Structure.java:1232)
    at com.sun.jna.Structure.calculateSize(Structure.java:1159)
    at com.sun.jna.Structure.calculateSize(Structure.java:1111)
    at com.sun.jna.Structure.allocateMemory(Structure.java:414)
    at com.sun.jna.Structure.<init>(Structure.java:205)
    at com.sun.jna.Structure.<init>(Structure.java:193)
    at com.sun.jna.Structure.<init>(Structure.java:180)
    at com.animeshz.github.batteryinfo.SYSTEM_BATTERY_STATE.<init>(SYSTEM_BATTERY_STATE.kt:9)
    at com.animeshz.github.batteryinfo.UtilKt.getBatteryState(util.kt:11)
    at com.animeshz.github.batteryinfo.UtilKt.main(util.kt:60)
    at com.animeshz.github.batteryinfo.UtilKt.main(util.kt)

Why does declared field names comes out to be empty array/list? And how could I troubleshoot this?

Answer

JNA’s Structure class uses reflection to find the field names, which means they must be declared as public fields/attributes. The @JvmField annotation in Kotlin removes getters and exposes those attributes as needed. I suspect this should work for you (untested):

class SYSTEM_BATTERY_STATE( 
        @JvmField var AcOnLine: Byte = 0,
        @JvmField var BatteryPresent: Byte = 0,
        @JvmField var Charging: Byte = 0,
        @JvmField var Discharging: Byte = 0,

        @JvmField var Spare1_0: Byte = 0,
        @JvmField var Spare1_1: Byte = 0,
        @JvmField var Spare1_2: Byte = 0,
        @JvmField var Spare1_3: Byte = 0,

        @JvmField var MaxCapacity = 0,
        @JvmField var RemainingCapacity = 0,
        @JvmField var Rate = 0,

        @JvmField var EstimatedTime = 0,
        @JvmField var DefaultAlert1 = 0,
        @JvmField var DefaultAlert2 = 0
    ) : Structure(ALIGN_MSVC), Structure.ByReference {

    override fun getFieldOrder() = listOf(
            "AcOnLine", "BatteryPresent", "Charging", "Discharging",
            "Spare1_0", "Spare1_1", "Spare1_2", "Spare1_3",
            "MaxCapacity", "RemainingCapacity", "Rate",
            "EstimatedTime", "DefaultAlert1", "DefaultAlert2"
        )
}


Source: stackoverflow