Search code examples
javapointersstructcallbackjna

JNA callback function with pointer to structure argument


I am busy with a project in which I have to do native calls to a proprietary C library. I came across JNA, which seems to be tried and tested with a number of successful projects.

I am having trouble passing a structure (or pointer to) through to a callback function. I have tried many different scenarios before, and basically, any structure member that requires memory allocation, like a String (char *), for instance, is null when I retrieve it.

I have tried to illustrate the problem with the following example:

C code:

typedef struct {
    int number;
    char *string;
} TEST_STRUCT;

typedef union {
    int number;
    TEST_STRUCT test_struct;
} TEST_UNION;

typedef void (*TEST_CB)(TEST_UNION*);

void test(TEST_CB test_cb)
{
    TEST_STRUCT *test_struct = malloc(sizeof *test_struct);
    test_struct->number = 5;
    test_struct->string = "Hello";

    TEST_UNION *test_union = malloc(sizeof *test_union);
    test_union->number = 10;
    test_union->test_struct = *test_struct;

    test_cb(test_union);

    free(test_struct);
    free(test_union);
}

Java-code:

public interface TestLib extends Library
{
  class TestStruct extends Structure
  {
      public int number;
      public String string;

      public TestStruct() {
          super();
      }
      protected List<? > getFieldOrder() {
          return Arrays.asList("number", "string");
      }
      public TestStruct(int number, String string) {
          super();
          this.number = number;
          this.string = string;
      }
      public TestStruct(Pointer peer) {
          super(peer);
      }
      public static class ByReference extends MBTStatus implements Structure.ByReference {}
      public static class ByValue extends MBTStatus implements Structure.ByValue {}
   }


class TestUnion extends Union {
    public int number;
    public TestStruct testStruct;

    public TestUnion() {
        super();
    }
    public TestUnion(int number, TestStruct testStruct) {
        super();
        this.number = number;
        this.testStruct = testStruct;
    }
    public TestUnion(Pointer pointer) {
        super(pointer);
    }
    public static class ByReference extends TestUnion implements com.sun.jna.Structure.ByReference {}
    public static class ByValue extends TestUnion implements com.sun.jna.Structure.ByValue {}
}


   interface TestCallback extends Callback
   {
       public void callback(TestUnion testUnion);
   }

   void test(TestCallback testCallback);
}

The main Java class:

public class TestMain
{
    static
    {
        System.loadLibrary("test");
    }

    public static void main (String [] args)
    {
        TestLib.INSTANCE.test(
                new TestLib.TestCallback()
                {
                      public void callback(TestLib.TestUnion testUnion)
                     {
                         System.out.println(testUnion.testStruct.string == null ? "The string value is null" : "The string value is: " + testUnion.testStruct.string);
                     }
                }
        );
    }
}

The string value is then null:

The string value is null

I am a complete noob when it comes to JNA, so I have lots to learn. I'm not sure if the mapping of the structure is correct, which might be the cause of the null value.

Any help will be greatly appreciated!


EDIT: I made the question a bit more interesting:

So the argument to a callback function is a union, instead of a struct. The struct is now part of the union. When I do it this way, the value of the struct string variable seems to be null as well.


Solution

  • I just found the answer to the updated question myself. This example ultimatley shows how to do it. As a union only takes up the memory of its largest member, its type has to be set to that member. The Union.read() function must then be called to read the "selected" variable. This is done as follows:

    testUnion.setType(TestLib.TestStruct.class);
    testUnion.read();
    

    The testStruct variable can then be accessed. The correct callback function is then:

    public void callback(TestLib.TestUnion testUnion)
      {
        testUnion.setType(TestLib.TestStruct.class);      
        testUnion.read();
        System.out.println(testUnion.testStruct.string == null ? "The string value is null" : "The string value is: " + testUnion.testStruct.string);
      }