Search code examples
dllcallbackjna

Accessing a C/C++ structure of callbacks through a DLL's exported function using JNA


I have a vendor supplied .DLL and an online API that I am using to interact with a piece of radio hardware; I am using JNA to access the exported functions through Java (because I don't know C/C++). I can call basic methods and use some API structures successfully, but I am having trouble with the callback structure. I've followed the TutorTutor guide here and also tried Mr. Wall's authoritative guide here, but I haven't been able to formulate the Java side syntax for callbacks set in a structure correctly.

I need to use this exported function:

BOOL __stdcall SetCallbacks(INT32 hDevice,
                            CONST G39DDC_CALLBACKS *Callbacks, DWORD_PTR UserData);

This function references the C/C++ Structure:

typedef struct{
    G39DDC_IF_CALLBACK               IFCallback;
    //more omitted
} G39DDC_CALLBACKS;

...which according to the API has these Members (Note this is not an exported function):

VOID __stdcall IFCallback(CONST SHORT *Buffer, UINT32 NumberOfSamples,
                          UINT32 CenterFrequency, WORD Amplitude,
                          UINT32 ADCSampleRate, DWORD_PTR UserData);
//more omitted

I have a G39DDCAPI.java where I have loaded the DLL library and reproduced the API exported functions in Java, with the help of JNA. Simple calls to that work well.

I also have a G39DDC_CALLBACKS.java where I have implemented the above C/C++ structure in a format works for other API structures. This callback structure is where I am unsure of the syntax:

import java.util.Arrays;
import java.util.List;
import java.nio.ShortBuffer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.BaseTSD.DWORD_PTR;
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;

public class G39DDC_CALLBACKS extends Structure {
    public G39DDC_IF_CALLBACK               IFCallback;
    //more omitted

    protected List getFieldOrder() {
        return Arrays.asList(new String[] {
            "IFCallback","DDC1StreamCallback" //more omitted
        });
    }

    public static interface G39DDC_IF_CALLBACK extends StdCallCallback{
         public void invoke(ShortBuffer _Buffer,int NumberOfSamples,
                            int CenterFrequency, short  Amplitude,
                            int ADCSampleRate, DWORD_PTR UserData);
    }
}

Edit: I made my arguments more type safe as Technomage suggested. I am still getting a null pointer exception with several attempts to call the callback. Since I'm not sure of my syntax regarding the callback structure above, I can't pinpoint my problem in the main below. Right now the relevant section looks like this:

int NumberOfSamples=65536;//This is usually 65536.
ShortBuffer _Buffer = ShortBuffer.allocate(NumberOfSamples);
int CenterFrequency=10000000;//Specifies center frequency (in Hz) of the useful band
                             //in received 50 MHz wide snapshot. 
short Amplitude=0;//The possible value is 0 to 32767.
int ADCSampleRate=100;//Specifies sample rate of the ADC in Hz.
DWORD_PTR UserData = null;

G39DDC_CALLBACKS callbackStruct= new G39DDC_CALLBACKS();
lib.SetCallbacks(hDevice,callbackStruct,UserData);
     //hDevice is a handle for the hardware device used-- works in other uses
     //lib is a reference to the library in G39DDCAPI.java-- works in other uses
     //The UserData is a big unknown-- I don't know what to do with this variable
          //as a DWORD_PTR
callbackStruct.IFCallback.invoke(_Buffer, NumberOfSamples, CenterFrequency,
                                 Amplitude, ADCSampleRate,  UserData);

EDIT NO 2:

I have one callback working somewhat, but I don't have control over the buffers. More frustratingly, a single call to invoke the method will result in several runs of the custom callback, usually with multiple output files (results vary drastically from run to run). I don't know if it is because I am not allocating memory correctly on the Java side, because I cannot free the memory on the C/C++ side, or because I have no cue on which to tell Java to access the buffer, etc. Relevant code looks like:

//before this, main method sets library, starts DDCs, initializes some variables...

//API call to start IF
System.out.print("Starting IF...             "+lib.StartIF(hDevice, Period)+"\n")
G39DDC_CALLBACKS  callbackStructure = new G39DDC_CALLBACKS();
callbackStructure.IFCallback = new G39DDC_IF_CALLBACK(){

    @Override 
    public void invoke(Pointer _Buffer,  int NumberOfSamples, int CenterFrequency,
            short Amplitude, int ADCSampleRate,   DWORD_PTR UserData  )  {

    //notification
        System.out.println("Invoked IFCallback!!");

        try {
    //ready file and writers
            File filePath = new File("/users/user/G39DDC_Scans/");
            if (!filePath.exists()){
                System.out.println("Making new directory...");
                filePath.mkdir();
            }

            String filename="Scan_"+System.currentTimeMillis();
            File fille= new File("/users/user/G39DDC_Scans/"+filename+".txt");
            if (!fille.exists()) {
                System.out.println("Making new file...");
                fille.createNewFile();
            }

            FileWriter fw = new FileWriter(fille.getAbsoluteFile());
    //callback body
            short[] deBuff=new short[NumberOfSamples];
            int offset=0;
            int arraySize=NumberOfSamples;

            deBuff=_Buffer.getShortArray(offset,arraySize); 
            for (int i=0; i<NumberOfSamples; i++){
                String str=deBuff[i]+",";
                fw.write(str);
            }
                fw.close();
        } catch (IOException e1) {
            System.out.println("IOException: "+e1);
        }
    }
};

lib.SetCallbacks(hDevice, callbackStructure,UserData);
System.out.println("Main, before callback invocation");

callbackStructure.IFCallback.invoke(s_Pointer, NumberOfSamples, CenterFrequency, Amplitude, ADCSampleRate, UserData);
System.out.println("Main, after callback invocation");

//suddenly having trouble stopping DDCs or powering off device; assume it has to do with dll using the functions above
    //System.out.println("StopIF:   " + lib.StopIF(hDevice));//API function returns boolean value
    //System.out.println("StopDDC2: " + lib.StopDDC2( hDevice, Channel));
    //System.out.println("StopDDC1: " + lib.StopDDC1( hDevice, Channel ));
    //System.out.println("test_finishDevice: " + test_finishDevice( hDevice, lib));

System.out.println("Program Exit");

//END MAIN METHOD

Solution

  • You need to extend StdCallCallback, for one, otherwise you'll likely crash when the native code tries to call the Java code.

    Any place you see a Windows type with _PTR, you should use a PointerType - the platform package with JNA includes definitions for DWORD_PTR and friends.

    Finally, you can't have a primitive array argument in your G39DDC_IF_CALLBACK. You'll need to use Pointer or an NIO buffer; Pointer.getShortArray() may then be used to extract the short[] by providing the desired length of the array.

    EDIT

    Yes, you need to initialize your callback field in the callbacks structure before passing it into your native function, otherwise you're just passing a NULL pointer, which will cause complaints on the Java or native side or both.

    This is what it takes to create a callback, using an anonymous instance of the declared callback function interface:

    myStruct.callbackField = new MyCallback() {
        public void invoke(int arg) {
            // do your stuff here
        }
    };