How should I extract complicated info from struct?

103 Views Asked by At

I have a complicated struct in C++. Now I need take the value by JNA. I have tried some ways, but all failed. I'm not sure where the problem is.

Here is my structure in Java, it is generated by JNACreator(https://code.google.com/archive/p/jnaerator/):

typedef struct SqWholeImageInfo 
{
    char *fileName;
    SqPicHead *phead;
    SqPersonInfo *personInfo;
    SqExtraInfo *extra;
    SqImageInfo **macrograph;
    SqImageInfo *thumbnail;
    SqPicInfo **sliceLayerInfo;
    SqSliceInfo **sliceInfo;
}SqWholeImageInfo;
package com.hys.mapper;

import com.sun.jna.Pointer;
import com.sun.jna.Structure;

import java.util.Arrays;
import java.util.List;

public class SqWholeImageInfo extends Structure {
    public Pointer fileName;
    public com.hys.mapper.SqPicHead.ByReference phead;
    public com.hys.mapper.SqPersonInfo.ByReference personInfo;
    public com.hys.mapper.SqExtraInfo.ByReference extra;
    public com.hys.mapper.SqImageInfo.ByReference[] macrograph;
    public com.hys.mapper.SqImageInfo.ByReference thumbnail;
    public com.hys.mapper.SqPicInfo.ByReference[] sliceLayerInfo;
    public com.hys.mapper.SqSliceInfo.ByReference[] sliceInfo;
    public SqSdpcInfo() {
        super();
    }
    protected List<String> getFieldOrder() {
        return Arrays.asList("fileName", "phead", "personInfo", "extra", "macrograph", "thumbnail", "sliceLayerInfo", "sliceInfo");
    }
    public SqWholeImageInfo(Pointer fileName, com.hys.mapper.SqPicHead.ByReference phead, com.hys.mapper.SqPersonInfo.ByReference personInfo, com.hys.mapper.SqExtraInfo.ByReference extra, com.hys.mapper.SqImageInfo.ByReference macrograph[], com.hys.mapper.SqImageInfo.ByReference thumbnail, com.hys.mapper.SqPicInfo.ByReference sliceLayerInfo[], com.hys.mapper.SqSliceInfo.ByReference sliceInfo[]) {
        super();
        this.fileName = fileName;
        this.phead = phead;
        this.personInfo = personInfo;
        this.extra = extra;
        if ((macrograph.length != this.macrograph.length))
            throw new IllegalArgumentException("Wrong array size !");
        this.macrograph = macrograph;
        this.thumbnail = thumbnail;
        if ((sliceLayerInfo.length != this.sliceLayerInfo.length))
            throw new IllegalArgumentException("Wrong array size !");
        this.sliceLayerInfo = sliceLayerInfo;
        if ((sliceInfo.length != this.sliceInfo.length))
            throw new IllegalArgumentException("Wrong array size !");
        this.sliceInfo = sliceInfo;
    }
    public static class ByReference extends SqWholeImageInfo implements Structure.ByReference {

    };
    public static class ByValue extends SqWholeImageInfo implements Structure.ByValue {

    };
}

The interface is:

// in c
DECLSPEC GLOBAL(int) openfile(char *sdpcPath, SqWholeImageInfo **info);

// call in c
char *filePath = "path/to/file";
SqWholeImageInfo *si = NULL;
int ret = openfile(filePath, &si);
// that's why it needs a pointer to structure
for (int i = 0; i < si->picHead->hierarchy; i++)
{
    PrintPicInfo(si->sliceLayerInfo[i]); 
}
// in java
public int openfile(String sdpcPath, SqWholeImageInfo.ByReference info);

As you see, this structure have structure arrays field in it. I first try to call it directly:

String filePath = "path/to/file";
SqWholeImageInfo.ByReference info = new SqWholeImageInfo.ByReference();
int status = SdpcService.INSTANCE.openfile(filePath, info);
System.out.println("file:" + info.fileName.getString(0));

Then I got an error:

Array fields must be initialized

I first guess it needs SqWholeImageInfo array. I changed the interface to

// interface
public int openfile(String sdpcPath, SqWholeImageInfo.ByReference[] info);
// call
String filePath = "path/to/file";
SqWholeImageInfo.ByReference[] info = new SqWholeImageInfo.ByReference[1];
info[0] = new SqWholeImageInfo.ByReference();
int status = SdpcService.INSTANCE.openfile(filePath, info);
System.out.println("file:" + info[0].fileName.getString(0));

Then I got the same error, and I guess it is caused by the structure array in SqWholeImageInfo. So I changed the type of com.hys.mapper.SqImageInfo.ByReference[] to PointerByReference like this:

...
    public Pointer fileName;
    public com.hys.mapper.SqPicHead.ByReference phead;
    public com.hys.mapper.SqPersonInfo.ByReference personInfo;
    public com.hys.mapper.SqExtraInfo.ByReference extra;
    public PointerByReference macrograph;
    public com.hys.mapper.SqImageInfo.ByReference thumbnail;
    public PointerByReference sliceLayerInfo;
    public PointerByReference sliceInfo; // now here is no more array.
...

I got the return value 0 in status (success!), but the filename has the wrong value. Then I found this:

enter image description here

// got file:native@0x1f4b1b0b050
// System.out.println("file:" + info.fileName);
// got error value it should be 'path/to/file'
System.out.println("file:" + info.fileName.getString(0));
// And another error: Cannot read field "srcWidth" because "info.phead" is null this can be fixed by using structure array of info
System.out.println("width:" + info.phead.srcWidth);
System.out.println("height:" + info.phead.srcHeight);

I don't know why. Can't the pointer type be used as the type of a structure array? If it works, how should I convert pointer to structure?

Or is there something wrong in my operation?

Or is there any way to initialize an array of variable length?

I checked other questions:


I tried some simple examples and it seems that I have some misunderstandings about structure nesting, but I still don't know where the problem is. Here are all the code of C++ and Java

//header file
// framework.h
#pragma once
#define WIN32_LEAN_AND_MEAN    
#include <windows.h>
#define DECLSPEC extern "C" _declspec(dllexport)
typedef struct StructA
{
  int x;
  int y;
  char* str1;
  unsigned char* seq1[128];
}StructA;

typedef struct StructB
{
  float a;
  double b;
  char** str_arr;
  int* ps;
}StructB;

typedef struct StructC
{
  StructA* sa;
  StructB** sbs;
}StructC;

DECLSPEC int TestFunc1(StructA* sa);
DECLSPEC int TestFunc2(StructA* sa, StructC** scs);

// source file
// dllmain.cpp
#include "pch.h"

#include <stdio.h>
#include "malloc.h"
#include "string.h"
using namespace std;

int TestFunc1(StructA* sa)
{
  sa->x = 10;
  sa->y = 12;
  sa->str1 = (char*)malloc(sizeof(char) * 6);
  memset(sa->str1, 0, sizeof(char) * 6);
  strcpy(sa->str1, "hello");

  unsigned char testSeq[] = "abc";
  sa->seq1[0] = testSeq;

  return 0;
}

int TestFunc2(StructA* sa, StructC** scs) {
  sa->x = 101;
  sa->y = 121;
  sa->str1 = (char*)malloc(sizeof(char) * 6);
  memset(sa->str1, 0, sizeof(char) * 6);
  strcpy(sa->str1, "hello");

  unsigned char testSeq[] = "abc";
  sa->seq1[0] = testSeq;

  if (*scs == NULL) {
    printf("init sc\n");
    *scs = (StructC*)malloc(sizeof(StructC));
    (*scs)->sa = (StructA*)malloc(sizeof(StructA));
    (*scs)->sa->x = 0;  // Initialize x to some default value
    (*scs)->sa->y = 0;  // Initialize y to some default value
    (*scs)->sa->str1 = (char*)malloc(sizeof(char) * 10);  // Allocate memory for str1
    memset((*scs)->sa->str1, 0, sizeof(char) * 10);
    strcpy((*scs)->sa->str1, "");  // Initialize str1 to an empty string
    (*scs)->sbs = (StructB**)malloc(sizeof(StructB*) * 4);
    for (int i = 0; i < 4; ++i) {
      (*scs)->sbs[i] = (StructB*)malloc(sizeof(StructB));
      (*scs)->sbs[i]->a = 0.0;  // Initialize a to some default value
      (*scs)->sbs[i]->b = 0.0;  // Initialize b to some default value
      (*scs)->sbs[i]->str_arr = NULL;  // Initialize str_arr to NULL
      (*scs)->sbs[i]->ps = NULL;  // Initialize ps to NULL
    }

    printf("After malloc - sa: %p, x: %d, y: %d, str1: %p\n",
      (*scs)->sa, (*scs)->sa->x, (*scs)->sa->y, (*scs)->sa->str1);
  }
  else {
    printf("no init sc\n");
  }

  // Set values for StructA members
  (*scs)->sa->x = 110;
  (*scs)->sa->y = 120;
  (*scs)->sa->str1 = (char*)malloc(sizeof(char) * 10);
  memset((*scs)->sa->str1, 0, sizeof(char) * 10);
  strcpy((*scs)->sa->str1, "hello119");

  printf("After setting values - sa: %p, x: %d, y: %d, str1: %p\n",
    (*scs)->sa, (*scs)->sa->x, (*scs)->sa->y, (*scs)->sa->str1);

  // Set values for StructB members
  (*scs)->sbs[0]->a = 1.5;
  (*scs)->sbs[0]->b = 1.8;
  int ia[] = { 1, 2, 3 };
  (*scs)->sbs[0]->ps = ia;

  return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

The Java code

package com.hys;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.PointerByReference;

import java.util.Arrays;
import java.util.List;

public class TestdllService {
    public interface MyLibrary extends Library {
        MyLibrary INSTANCE = Native.load("D:\\WS_VS\\testdll\\x64\\Release\\testdll.dll", MyLibrary.class);

        int TestFunc1(StructA sa);
        int TestFunc2(StructA sa, StructC.ByReference scs);
    }

    public static class StructA extends Structure {
        public int x;
        public int y;
        public String str1;
        public Pointer[] seq1 = new Pointer[128];

        // Specify the order of fields explicitly
        protected List<String> getFieldOrder() {
            return Arrays.asList("x", "y", "str1", "seq1");
        }

        public StructA() {
            super();
            // Initialize fields to default values
            x = 0;
            y = 0;
            str1 = "";
            Arrays.fill(seq1, Pointer.NULL);
        }

        public static class ByReference extends StructA implements Structure.ByReference {
        }

        public static class ByValue extends StructA implements Structure.ByValue {
        }
    }

    public static class StructB extends Structure {
        public float a;
        public double b;
        public Pointer str_arr; // Assuming this is equivalent to char**
        public Pointer ps;

        public static class ByReference extends StructB implements Structure.ByReference {
            protected List<String> getFieldOrder() {
                return Arrays.asList("a", "b", "str_arr", "ps");
            }
        }

        public static class ByValue extends StructB implements Structure.ByValue {
        }
    }

    public static class StructC extends Structure {
        public StructA.ByReference sa;
        public StructB.ByReference sbs;

        // Specify the order of fields explicitly
        protected List<String> getFieldOrder() {
            return Arrays.asList("sa", "sbs");
        }

        public StructC() {
            super();
        }

        public StructC(Pointer p) {
            super(p);
            read();
        }

        public void initSbsArray(int size) {
            sbs = new StructB.ByReference();
            sbs.toArray(size);
        }

        public static class ByReference extends StructC implements Structure.ByReference {
            public ByReference() {
                super(Pointer.NULL);
            }

            public ByReference(Pointer p) {
                super(p);
            }
        }

        public static class ByValue extends StructC implements Structure.ByValue {
        }
    }

    public static void main(String[] args) {
        MyLibrary myLibrary = MyLibrary.INSTANCE;

        StructA sa = new StructA();
//        int ret = myLibrary.TestFunc1(sa);
        StructC.ByReference scs = new StructC.ByReference(Pointer.NULL);
        int dynamicSize = 4;
        scs.initSbsArray(dynamicSize);

        int ret = myLibrary.TestFunc2(sa, scs);
        System.out.println("Result: " + ret);
        // Access the modified struct or handle the result as needed
        if (scs != null && scs.getPointer() != null) {
            // Read the values from the modified struct
            scs.read(); // This populates the Java fields

            // Access the values
            System.out.println("Modified sa.x: " + scs.sa.x);
            System.out.println("Modified sa.y: " + scs.sa.y);
            System.out.println("Modified sa.str1: " + scs.sa.str1);
        } else {
            System.out.println("Pointer is null, no modification occurred.");
        }


    }
}

The TestFunc1 works well. But the TestFunc2 have wrong value.

// output of TestFunc2
Result: 0
Modified sa.x: -1586624800 // it should be 110
Modified sa.y: 483 // it should be 120
Modified sa.str1: P`r��  // it should be hello119
init sc
After malloc - sa: 000001E3A16E06E0, x: 0, y: 0, str1: 000001E3A171EEC0
After setting values - sa: 000001E3A16E06E0, x: 110, y: 120, str1: 000001E3A171EE60
1

There are 1 best solutions below

1
Daniel Widdis On

Quoting from your comment:

From the API, the JNACreator gives right result. the SqImageInfo**, SqPicInfo** and SqSliceInfo** are all struct pointer arrays in SqWholeImageInfo. And the length depends on the int SqPicHead.hierarchy, so I need first get the info in SqPicHead *phead then I can get the length of SqPicInfo **sliceLayerInfo.

This is the key bit of information to use for the mapping:

  1. The array mapping is correct, but still needs to be initialized.
  2. You don't know when you initialize it what size mapping you need, so you can initialize them at the start with a size of 1.
  3. If you're calling native and expecting it to populate this array, you can override read() to grab the appropriate size and re-allocate the array size before reading the full structure.

I don't have the full details of SqPicHead but given your information a structure like this will probably work (or at least point you in the right direction):

@FieldOrder ({"fileName", "phead", "personInfo", "extra", "macrograph", "thumbnail", "sliceLayerInfo", "sliceInfo"})
public class SqWholeImageInfo extends Structure {
    public String fileName;
    public SqPicHead.ByReference phead;
    public SqPersonInfo.ByReference personInfo;
    public SqExtraInfo.ByReference extra;
    public SqImageInfo.ByReference[] macrograph = new SqImageInfo.ByReference[1];
    public SqImageInfo.ByReference thumbnail;
    public SqPicInfo.ByReference[] sliceLayerInfo = new SqPicInfo.ByReference[1];
    public SqSliceInfo.ByReference[] sliceInfo = new SqSliceInfo.ByReference[1];
    public SqSdpcInfo() {
        super();
    }

    @Override
    public void read() {
        readField("phead");
        macrograph = (new SqImageInfo.ByReference()).toArray(phead.hierarchy);
        sliceLayerInfo = (new SqPicInfo.ByReference()).toArray(phead.hierarchy);
        sliceInfo = (new SqSliceInfo.ByReference()).toArray(phead.hierarchy);
        super.read();
    }

    public static class ByReference extends SqWholeImageInfo implements Structure.ByReference {};
}

I've made a few other changes:

  • The @FieldOrder annotation is alot cleaner and does the same thing as your method override
  • I changed the char * mapping to a String which is a better JNA mapping
  • I omitted the constructor with arguments as I don't think you need it or want it
  • I omitted the ByValue version of the structure as you don't need it