1

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);
denbrau
  • 11
  • 4
  • off-topic comment: you have `ref byte data` ... but you are using it as it would expects `ref byte[]` - you shouldn't do this – Selvin Aug 24 '21 at 09:52

2 Answers2

2

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

Cwift
  • 300
  • 1
  • 6
  • Thanks for the suggestion, I've swapped the types to IntByReference but it doesn't work unfortunately. – denbrau Aug 24 '21 at 09:55
  • Do you have exactly the C function declaration? Maybe the parameter `data` needs a byte array instead of a `ByteByReference`.@denbrau – Cwift Aug 24 '21 at 10:12
  • I've edited the question to as to include the C function declaration. Wouldn't I need a byreference class? – denbrau Aug 24 '21 at 10:24
  • Your c# code use a byte array, maybe you can try pass a byte array in java – Cwift Aug 24 '21 at 10:28
  • You also have some `ref short` so you'd need `ShortByReference` for those. And just pass the byte array driectly rather than a ByteByReference. JNA treats arrays properly here. – Daniel Widdis Aug 24 '21 at 19:22
  • @DanielWiddis, I tried your suggestions. I expanded the question abit. I feel like the method can't read the values that I provide somehow. The correct values are set. Does that provide you with useful information? – denbrau Aug 25 '21 at 10:08
  • @denbrau What's your java interface now?, I think it should be `int C(IntByReference gatewayID, IntByReference tag_addr, IntByReference Subcmd, IntByReference msg_type, byte[] data, IntByReference data_cnt);`. But your c# use ref short on `subcmd ` – Cwift Aug 25 '21 at 12:25
  • @Cwift I've tried that one and I've tried: int C(IntByReference gatewayID, IntByReference tag_addr, ShortByReference Subcmd, ShortByReference msg_type, byte[] data, IntByReference data_cnt); – denbrau Aug 25 '21 at 12:28
  • @denbrau `int C(IntByReference gatewayID, IntByReference tag_addr, ShortByReference Subcmd, ShortByReference msg_type, byte[] data, IntByReference data_cnt);` should work. What are you getting as the return value? – Daniel Widdis Aug 25 '21 at 19:23
  • @DanielWiddis Hi Daniel, I'm getting back a 0 which indicated that nothing happened. The messages are picked up somehow (Based on some experiments). But no feedback is given. The only explanation I can think of is that the settings I pass are not read by the C library. – denbrau Aug 26 '21 at 07:35
  • @DanielWiddis I've added the raw C signature. – denbrau Aug 26 '21 at 09:04
  • Your Java code still seems to be passing a single byte for `data` rather than passing a buffer of 255 bytes. A `byte[255]` should work, or a `Pointer` mapped as `new Memory(255)` would work. – Daniel Widdis Aug 26 '21 at 16:07
0

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.