1

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?

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Animesh Sahu
  • 7,445
  • 2
  • 21
  • 49
  • JNA uses reflection to get the field names from the structure, so they must be declared `public` – Daniel Widdis Jun 07 '20 at 04:51
  • @DanielWiddis by default all the fields are public in kotlin, marking with public (just tried) doesn't have any effect :( – Animesh Sahu Jun 07 '20 at 04:53
  • @DanielWiddis, I tried to decompile the SYSTEM_BATTERY_STATE into java, and it looks like properties are private but their getters and setters are public, is there a way to instruct Structs to call setters/getters or they "require" to be used with public (which I don't think kotlin allows)? – Animesh Sahu Jun 07 '20 at 05:03
  • Pretty sure the public access is required because of the use of Reflection. But it looks like you can do this in Kotlin. See [this example](https://github.com/Jire/Kotmem/blob/master/src/main/kotlin/org/jire/kotmem/win32/LPMODULEINFO.kt) ... – Daniel Widdis Jun 07 '20 at 05:04

1 Answers1

4

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"
        )
}
Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63