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:
// 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:
- this is error of enum calling (I have used int already in other structure): JNA with complicated struct
- In my code, I don't know the real length: JNA how do I go from a C int* to a JNA struct
- this is a very similar question, but I don't understand why: JNA: How to access array of struct in struct?
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

Quoting from your comment:
This is the key bit of information to use for the mapping:
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
SqPicHeadbut given your information a structure like this will probably work (or at least point you in the right direction):I've made a few other changes:
@FieldOrderannotation is alot cleaner and does the same thing as your method overridechar *mapping to aStringwhich is a better JNA mappingByValueversion of the structure as you don't need it