JNA behaving different then my C# counterpart, why?

130 views Asked by At

I'm developing something for a piece of hardware and I got a C library to communicate with the hardware. I have methods that send a signal to the hardware (like light bulb turn on) and those work fine on both C# and Java using JNA.

The machine also has a pressable button and when that button is pressed it will log a signal which can be retrieved with a method called A.

The way this was intended to work is to create a new thread which keeps calling this method until it returns 1 in which case it will have information regarding the button press.

I've got this working in C# with the following code:

    while (true)
    {
        byte[] ccbdata = new byte[255];
        short TagCommand = -1, nMsg_type = -1;
        int ccblen = 0, nGwId = 0, nNode = 0;
        int ret = CWrapper.Wrapper.A(ref nGwId, ref nNode, ref TagCommand, ref nMsg_type, ref ccbdata[0], ref ccblen);
        if (ret > 0)
        {
           // do stuff
        }
        Thread.Sleep(100);
    }

Where the method is imported in C# like:

[DllImport("Clibrary.dll")]
public static extern int C(ref int Gateway_ID, ref int Node_Addr, ref short Subcmd, ref short msg_type, ref byte data, ref int data_cnt);

I want this code also to work on Java. Unfortunately when I run this with java it never returns 1 unlike the C# implementation.

I was wondering if I am doing something wrong as I'm not experienced using JNA. In Java I import the method like this:

int C(LongByReference gatewayID, LongByReference tag_addr, LongByReference Subcmd, LongByReference msg_type,
                  ByteByReference data, LongByReference data_cnt);

And I try to run the code like this:

    while(run) {
        gatewayID.setValue(0);
        tag_addr.setValue(0);
        subcmd.setValue(-1);
        msg_type.setValue(-1);
        byte b = 0;
        data.setValue(b);
        data_cnt.setValue(0);

        int ref = CWrapper.INSTANCE.C(gatewayID, tag_addr, subcmd, msg_type, data, data_cnt);
        if (ref > 0) {
            System.out.println("hit");
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

In the documentation I have about this method it is defined in C like:

C(ByRef gatewayID As Integer, ByRef tag_addr As Integer, ByRef subcmd As Integer, ByRef msg_type As Integer, ByRef data As Byte, ByRef data_cnt As Integer)

Does anyone know why the C# example works but not the Java one?

--EDIT 25-08-2021 12:04

The documentation says the parameters provided to the method are used. So I imagine if they are null somehow the method won't return anything. Perhaps the initialization is wrong for using the ByReference object?

--EDIT 26-08-2021

I've gotten the C++ signature which is:

typedef int (__stdcall *pC)(int& Gateway_ID, int& Node_Addr, short& Subcmd, 
                  short& Msg_Type, unsigned char* Data, short& Data_Cnt);
2

There are 2 answers

12
Cwift On

In java, long is 8 bytes, your c# code use ref int which is 4 bytes.
Have you tried use IntByReference?

0
Matthias Bläsing On

With the given C signature:

typedef int (__stdcall *pC)(int& Gateway_ID, int& Node_Addr, short& Subcmd, short& Msg_Type, unsigned char* Data, short& Data_Cnt);

I would bind it like this:

interface CLibrary extends StdCallLibrary {
    int C(
        IntByReference Gateway_ID,
        IntByReference Node_Addr,
        ShortByReference Subcmd,
        ShortByReference Msg_Type,
        byte[] Data,
        ShortByReference Data_Cnt);
}

int on C can be expected to be 32bit where it matters so this maps cleanly to a java int and as all arguments are passed by reference IntByReference is your friend. Same goes for short and ShortByReference (just different bitness).

This leaves the question what happens with Data and Data_Cnt. I guess, that Data is a buffer where the function places data (calling C is a pull operation). That buffer is 255 bytes long (I assume, that is somewhere documented as a requirement of the library). Now when the pull operation succeeds, I would expect that the data is written to the buffer backing Data and the amount of data received is written to Data_Cnt. Now the calling side (Java) can read the amount of data transfered (Data_Cnt.getValue()) and read that much from the byte[]. Java arrays are passed by reference - JNI creates a copy of the array, passes it to the function, the function runs and the data is the copied back into the array.

And here is the reason why you should always create a minimal runnable sample:

You skipped essential parts of the binding. The C signature has a __stdcall marker. And this changes the calling convention. Deriving from StdCallLibrary should fix that.