1

I am using custom types in the callback method and always get an empty value. What could be the problem?

C code in dll(i have .h file for this dll):

typedef union
{
char    caStruct[16384];

struct
    {
    // Header
    int iCode;
    int iID;
    int iResult;
    int iInfo;                  // Bits 0-3 = 1: data structure TVehicleData, 2: TVehicleDataXL, 8: tCalResults
    int iNum;                       // number of data sets
    int iMask;                  // mask with participating sensors, if available
    int iaReserve[10];

    // "Payload"
    char caData[];
    };
} tResponse;

//callback function
DLL_PROC int    __stdcall vwacom_ResultCallback( void (__stdcall *Results)( tResponse Response ) ); // 0

My java code:

Structure transformation:

@Structure.FieldOrder({"uResp"})
public class TResponse extends Structure {
    public static class ByReference extends TResponse implements Structure.ByReference { }
    public static class ByValue extends TResponse implements Structure.ByValue { }
    
    public UnionResp uResp;
    
    public static class UnionResp extends Union{
        public static class ByReference extends TResponseU implements Union.ByReference { }
        public static class ByValue extends TResponseU implements Union.ByValue { }
    
        public String caStruct; //char[0x4000]
        public StructResp sResp;
    }

    @Structure.FieldOrder({"iID","iCode","iResult","iInfo","iNum","iMask","iaReserve","caData"})
    public static class StructResp extends Structure{
        public static class ByReference extends TResponseS implements Structure.ByReference { }
        public static class ByValue extends TResponseS implements Structure.ByValue { }

        // Header
        public int  iID;
        public int  iCode;
        public int  iResult;
        public int  iInfo;                  // Bits 0-3 = 1: data structure TVehicleData, 2: TVehicleDataXL, 8: tCalResults
        public int  iNum;                       // number of data sets
        public int  iMask;                  // mask with participating sensors, if available
        public int[] iaReserve = new int[10];
        // "Payload"
        public String caData; //char[]
    }
}

dll interface:

public interface JNAVIsiWheAi extends Library {
    
    JNAVIsiWheAi INSTANCE = (JNAVIsiWheAi) Native.load("C:\\CWM\\JNAVIsiWheAi.dll", JNAVIsiWheAi.class);
    
    //vwacom_ResultCallback( void (__stdcall *Results)( tResponse Response ) );
    interface Results extends Callback{
        void invoke(TResponse tRes);
    }

    int vwacom_ResultCallback(Results results);
}

Use callback:

public void resultCallback() {
    JNAVIsiWheAi.Results results = new JNAVIsiWheAi.Results() {
        @Override
        public void invoke(TResponse tRes) {
            /*just many sout imformation */
        }
    };

    jnavIsiWheAi.vwacom_ResultCallback(results);
}

I don't get errors, but when outputting values, they are always empty.

My example console output looks like this:

  • caStruct = null; iID=0; iCode=0; iResult=0; iInfo=0; iNum=0; iMask=0 iaReserve =[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; caData=null

1 Answer 1

0

There are a few issues here.

First, you're not allocating enough memory to fill the data. By mapping the char[16384] to String you're essentially just allocating enough space for a pointer to memory elsewhere. This should be a byte[] to allocate that much space to the Union. JNA has no size information to allocate more space to read the result.

(Related, the String mapping in your structure won't work, and could cause an invalid memory error when you try to read it. The correct thing to do for a byte array of unknown length would be to map a byte array of size 1, and either directly read the values from that field's offset, or resize the byte array using length information and re-read it. But you know a max size already because of the 16384 bytes minus the allocations for the 16 ints, so you could just allocate that size.)

Secondly, the values in a union do not auto-read because JNA does not know which of the union types is appropriate to read.

From the Union javadoc:

Upon reading from native memory, Structure, String, or WString fields will not be initialized unless they are the current field as identified by a call to setType(java.lang.Class<?>). The current field is always unset by default to avoid accidentally attempting to read a field that is not valid. In the case of a String, for instance, an invalid pointer may result in a memory fault when attempting to initialize the String.

For most unions, there is an element (often the first one) to read which tells you the type. In this particular mapping, it appears the union is only there to force a fixed-size structure and you'll always want the StructResp.

In your TResponse mapping, you should override read() and set the type appropriately. The general pattern is to call super.read() to read the Structure and get type info, set the type based on that, then read() the union.

@Override
public void read() {
    super.read(); // not necessary in this case because you're not reading type 
    uResp.setType(StructResp.class);
    uResp.read();
}

Note, however, that since you're always reading the structure element, you don't actually need to map the union at all.

Just simplify things and only map the Structure you want to populate, and allocate enough memory for the caData element based on the fixed size:

public static class StructResp extends Structure{ 
    // ... existing mappings ...
    public byte[] caData = new byte[0x4000 - 16 * Integer.BYTES];
}

Then you can simply extract the String with Native.toString(caData). (This may be Native.toWideString() if the data is in UTF-16.)

Since you're mapping a __stcall library, your interface should extend StdCallLibrary rather than Library.

And finally, the argument in the callback is not a pointer; Structures are treated ByReference by default as arguments, but you need to use the ByValue tag in the invoke() method.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you. I corrected my mistakes(StdCallLibrary, replaced string values with byte array). Also I overridden the methods read and write for TResponse class. Unfortunately, empty values are still coming.
@RamirLanner one more issue with callbacks is reachability. If the callback is the last time the JVM sees the variable it may determine it unreachable and GC it, reclaiming its memory. Make sure you refer to the results variable after the callback. In JVM 9+ a Reachability Fence is the best option for this. If that doesn't fix it, please edit your original question to show your changed code so I can review it again.
Problem is solved. I simplified the structure as you suggested. Now I have only StructResp. And in Invoke methood I changed the argument invoke(StructResp.ByValue).
Ah, should have noticed no pointer on TResponse in the argument list. That's it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.