I referenced another stackoverflow article "How do I map a pointer to an array of structures in JNA" to come up with the following code to enumerate Windows service dependencies.
Structure and function declarations:
static class SERVICE_STATUS extends Structure {
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
public SERVICE_STATUS(){}
}
static class ENUM_SERVICE_STATUS extends Structure {
public ENUM_SERVICE_STATUS(){ }
public WString lpServiceName;
public WString lpDisplayName;
SERVICE_STATUS serviceStatus;
}
boolean EnumDependentServicesW(Pointer hService, int serviceState, ENUM_SERVICE_STATUS serviceStatuses, int size, IntByReference bytesNeeded, IntByReference servicesReturned);
If there is only one service dependency, the following code works:
IntByReference bytesNeeded = new IntByReference();
IntByReference numberOfServices = new IntByReference();
Advapi32.ENUM_SERVICE_STATUS serviceStatus = new Advapi32.ENUM_SERVICE_STATUS();
Advapi32.ENUM_SERVICE_STATUS[] serviceStatuses = (Advapi32.ENUM_SERVICE_STATUS[]) serviceStatus.toArray(1);
if (!advapi32.EnumDependentServicesW(serviceHandle, Advapi32.SERVICE_ACTIVE, null, 0, bytesNeeded, numberOfServices)) {
if (advapi32.EnumDependentServicesW (serviceHandle, Advapi32.SERVICE_ACTIVE, serviceStatuses[0], bytesNeeded.getValue(), bytesNeeded, numberOfServices)) {
for(int i = numberOfServices.getValue() - 1; i >= 0; i--){
logger.debug("Service Name: " + serviceStatuses[i].lpServiceName.toString());
}
}
If there are 2 service dependencies, I get a NullPointerException for lpServiceName in the logger.debug call:
IntByReference bytesNeeded = new IntByReference();
IntByReference numberOfServices = new IntByReference();
Advapi32.ENUM_SERVICE_STATUS serviceStatus = new Advapi32.ENUM_SERVICE_STATUS();
Advapi32.ENUM_SERVICE_STATUS[] serviceStatuses = (Advapi32.ENUM_SERVICE_STATUS[]) serviceStatus.toArray(2);
if (!advapi32.EnumDependentServicesW(serviceHandle, Advapi32.SERVICE_ACTIVE, null, 0, bytesNeeded, numberOfServices)) {
if (advapi32.EnumDependentServicesW (serviceHandle, Advapi32.SERVICE_ACTIVE, serviceStatuses[0], bytesNeeded.getValue(), bytesNeeded, numberOfServices)) {
for(int i = numberOfServices.getValue() - 1; i >= 0; i--){
logger.debug("Service Name: " + serviceStatuses[i].lpServiceName.toString());
}
}
The numberOfServices value for the code above is 2, as expected. I'm trying to pass the structure array instead of a pointer because I want JNA to do the memory synching. How should I be passing/using the array of structures?
According to the docs for EnumDependentServices,
lpServices [out, optional]
A pointer to an array of
ENUM_SERVICE_STATUS structures that receives the name and service
status information for each dependent service in the database. The
buffer must be large enough to hold the structures, plus the strings
to which their members point.
You're pretty much ignoring the required buffer size as reported by bytesNeeded. You should use the bytesNeeded value to create a Memory instance of the requisite size, then use that Memory instance to create a new ENUM_SERVICE_STATUS instance, instead of creating the structure independently of the required buffer size.
Related
My native code is
typedef struct driver_config {
unsigned int dllVersion;
unsigned int channelCount;
unsigned int reserved[10];
ChannelConfig channel[64];
} DriverConfig;
In Java my class looks like this
public class DriverConfig extends Structure {
public int dllVersion;
public int channelCount;
public int[] reserved= new int[10];
ChannelConfig[] channel = new ChannelConfig[64];
public DriverConfig() {
super();
init();
}
private void init() {
for (int i = 0; i < channel.length; i++) {
channel[i]= new ChannelConfig();
}
}
#Override
protected List<String> getFieldOrder() {
return Arrays.asList(new String[] { "dllVersion", "channelCount", "reserved" });
}
//toString()...
}
The method declaration is
int getDriverConfig(DriverConfig driverConfig);
I tried to access the method like this
DriverConfig driverConfig = new DriverConfig();
status = dll.INSTANCE.getDriverConfig(driverConfig);
System.out.println("DriverConfig Status: " + status);
System.out.println(driverConfig.toString());
If channel.length is replaced with less then 50 the array is initialized correctly but with channel.length it did not work. It even did not show any error just nothing.
Your getFieldOrder() array does not include the last element (channel) of your structure. I see in your comments that you attempted to do this but received an error because you have not declared it public. All elements of your structure must be listed in the FieldOrder and also declared public so they can be found with reflection.
Also, with JNA 5.x (which you should be using) the #FieldOrder annotation is preferred.
You haven't identified the mapping for ChannelConfig, but your question title and this API link matching your structure indicate that it is a nested structure array. Structure arrays must be allocated using contiguous memory, either by directly allocating the native memory (new Memory()) which requires knowing the structure size, or by using Structure.toArray(). Allocating in a loop as you have done will end up with memory for each new structure allocated at possibly/probably non-contiguous locations in native memory. Given that you state that it appears to work for some values, you might be getting lucky with contiguous allocations, but your behavior is certainly undefined.
Your structure mapping should therefore be:
#FieldOrder ({"dllVersion", "channelCount", "reserved", "channel"})
public class DriverConfig extends Structure {
public int dllVersion;
public int channelCount;
public int[] reserved= new int[10];
public ChannelConfig[] channel = (ChannelConfig[]) new ChannelConfig().toArray(64);
}
I have a problem with calling this method IShellFolder::GetUIObject I don't know how to create Pointer to Array of Pointers as the 3rd argument of this function. In the documentation the header of this function is:
HRESULT GetUIObjectOf(
HWND hwndOwner,
UINT cidl,
PCUITEMID_CHILD_ARRAY apidl,
REFIID riid,
UINT *rgfReserved,
void **ppv
);
This is my code:
String directory = "c:\\Users";
String file = "c:\\Users\\Duchon\\Downloads\\Baumüller Brno, s.r.o.PS43668.prpkg";
try {
PointerByReference psfDesktopPTR = new PointerByReference();
WinNT.HRESULT hResult = Shell32.INSTANCE.SHGetDesktopFolder(psfDesktopPTR);
if (COMUtils.SUCCEEDED(hResult)) {
IntByReference pcheaten = new IntByReference();
PointerByReference ppidl = new PointerByReference();
IntByReference pdwAttributes = new IntByReference();
MyIShellFolder psfDesktop = MyIShellFolder.Converter.PointerToIShellFolder(psfDesktopPTR);
hResult = psfDesktop.ParseDisplayName(null, null, new WString(file), pcheaten, ppidl, pdwAttributes);
PointerByReference iContextMenuPtr = new PointerByReference();
if (COMUtils.SUCCEEDED(hResult)) {
Pointer[] ppidls = new Pointer[1];
ppidls[0] = ppidl.getValue();
hResult = psfDesktop.GetUIObjectOf(null, 1, ppidl.getValue(), new Guid.REFIID(IContextMenu.IID_IContextMenu), new IntByReference(), iContextMenuPtr);
if (COMUtils.SUCCEEDED(hResult)) {
// QueryIContextMenu ...
}
}
}
}
catch (Exception e) {
e.printStackTrace(System.err);
}
But I get invalid memory access exception. I need a solution for array of files, not only for one. Thank you very much.
When you get an Invalid Memory Access error, it's a clue that you need to properly allocate Native memory. Your code above only declares a Java-side Pointer array.
Arrays in C use contiguous memory. This means you must allocate a single block of native memory large enough for the array; it is not enough to collect a bunch of individual allocations (which is what declaring a single Pointer variable in Java does).
You have two primary options for allocating this block of native memory:
Option 1. Use JNA's Memory class to explicitly allocate the size of memory you will need. If you allocating an array of Pointers, you will allocate like this: Memory m = new Memory(numberOfElements * Native.POINTER_SIZE); When you get the returned value into this memory you will use offsets to pull the appropriate pointer from the array, e.g., for the 0-indexed ith pointer, do Pointer p = m.getPointer(i * Native.POINTER_SIZE);
Option 2. Create a Structure of the appropriate size (in this case, containing a single element which is a Pointer) and use Structure.toArray() to allocate the Structure array. So you could define:
#FieldOrder ({"childId"})
class PCUITEMID_CHILD extends Structure {
public Pointer childId;
}
And then allocate the array
PCUITEM_CHILD[] pcuItemIdArray = new PCUITEMID_CHILD().toArray(numberOfElements);
Then you can pass this array variable, and access its result using traditional array syntax.
Pointer p = pcuItemIdArray[0].childId;
I have the following C code which I am calling from JNA. When I call it I am getting 0 as the return value while I'm getting the real value when I test it in C.
RFIDLIB_API uint32_t get(
ipj_key key /*[i]*/,
uint32_t bank_index /*[in]*/,
uint32_t value_index /*[out]*/,
uint32_t *value /*[out]*/)
{
return ipj_get(&iri_device,key,bank_index,value_index,value);
}
Below is how I have defined this in my JNA library.
public int get(ipj_key key, int bank_index, int value_index, IntByReference value);
Below is the ip_key structure
public class ipj_key extends Structure {
public int ipj_key;
#Override
protected List getFieldOrder() {
return Arrays.asList("ipj_key");
}
}
And below is how I am calling it in my main method.
public rfidlib rlib;
public static void main(String[] args) {
MainScannerTest mainClass = new MainScannerTest();
mainClass.rlib = (rfidlib) Native.loadLibrary("rfidlib", rfidlib.class);
ipj_key key = new ipj_key();
key.ipj_key = 0xD;
IntByReference value = new IntByReference();
mainClass.rlib.get(key, 0, 0, value);
System.out.println("Antenna power is : "+ value.getValue());
}
What am I doing wrong here? Why am I getting 0 as the return value? Please advice.
My C lib has a int* as an OUT parameter. I was also trying to use it with an IntByReference, without any success.
EDIT 2017-03-16: indeed it worked with IntByReference. There might me something wrong in your C code.
An alternative would be using a Pointer instead of IntByreference
(I made it work with IntByRef and also with Pointer in my case (64bit Win7, JNA 4.3.0 and Java 64 bits jdk 1.8.0_71))
With your code snippet, this would look like the following.
First change your JNA dll interface defintion (use Pointer instead of IntByReference):
public int get(ipj_key key, int bank_index, int value_index, /*change is here*/ Pointer value);
Then change of course the variable before call:
Pointer value = new Memory(Integer.size); // Here we assume that your java platform is 32 bits (Integer.size=32)
//this should match your uint32_t. No idea if there are consequences when mixing 32/64 bits java/OS/dll ...
Then, you can do the following:
System.out.println("Antenna power is : "+ value.getInt(0));
// The offset is set to 0, since there is no reason to read the int from another offset value.
Here is my updated code after implementing suggestions. But still problems persist.
typedef struct S1{
char temp1[100];
char temp2[100];
}S1
...
int manipulateTemp(S1 s1Arr[] );
JNA interface looks like this
public interface Add extends Library
{
Add INSTANCE = (Add) Native.loadLibrary("add", Add.class);
public static class S1 extends Structure {
public byte[] temp1 = new byte[100];
public byte[] temp2 = new byte[100];
public static class ByReference extends S1 implements Structure.ByReference {
};
};
int manipulateTemp( S1[]);
}
//
public static byte[] toByteArray(char[] a ,Charset c){
CharBuffer cBuffer = CharBuffer.wrap(a);
ByteBuffer bBuffer = c.encode(cBuffer);
return bBuffer.array;
}
//in main method
Add lib = Add.INSTANCE;
Add.S1.ByReference s1Ref = new Add.S1.ByReference();
Add.S1[] s1Arr = (Add.S1[])s1Ref.toArray(10);
s1Ref.clear();
//initialize array
for(int i =0;i<s1Arr.lenth ;i++){
byte[] data = toByteArray("myString1".toCharArray,Charset.defaultCharSet
System.arrarycopy(data,0, s1Arr[i].temp1,0,data.length);
data = toByteArray("myString2".toCharArray,Charset.defaultCharSet
System.arrarycopy(data,0, s1Arr[i].temp2,0,data.length);
}
// calling native function
lib.manipulateTemp(s1Arr[]);
After execution
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Function.invokeInt(Native Method)
at com.sun.jna.Function.invoke(Function.java:344)
at com.sun.jna.Function.invoke(Function.java:276)
at com.sun.jna.Library$Handler.invoke(Library.java:216)
at com.sun.proxy.$Proxy0.manipulateTemp((Unknown Source)
at LoanTest.newTestCalc.main(newTestCalc.java:288)
I even checked memory dump, structures are seems to be allocated stored correctly.Structure size is also correct = 200 bytes
Any clues about this error?
You need to copy values into the existing temp field, not overwrite it. When you overwrite it, you're actually changing its size, which JNA uses to determine the structure size. Following is how you should initialize your structure data:
class S1 extends Structure {
public byte[] temp = new byte[100];
...
}
S1 s = new S1();
S1[] array = (S1[])s.toArray(ARRAY_SIZE);
System.setProperty("jna.encoding", "utf-8"); // You probably want utf-8; utf-16 has 16-bit code units, so unless your native code is actually expecting a utf-16 encoding broken down into byte units, use utf-8
byte[] data = Native.toByteArray("myString"); // includes NUL terminator
System.arraycopy(data, 0, array[0].temp, 0, data.length);
// Repeat as needed for other members of the array
lib.manipulateTemp(array);
Note that the declarations manipulateTemp(S1 s) or manipulateTemp(S1[] array) will both work, although the latter is more accurate and conveys your intent explicitly.
I have the following implementation with Javolution:
public class RunScan extends Struct
{
public final Signed32 numOfClusters = new Signed32();
public final ClusterData[] clusters;
public final Signed32 numOfRecons = new Signed32();
public final ReconData[] recons ;
public RunScan (int numOfClusters, int numOfRecons)
{
this.numOfClusters.set(numOfClusters);
this.numOfRecons.set(numOfRecons);
clusters = array(new ClusterData[numOfClusters]);
recons = array(new ReconData[numOfRecons]);
}
}
public class ClusterData extends Struct
{
public final UTF8String scanType = new UTF8String(CommInterfaceFieldConstants.SCAN_TYPE_SIZE);
public final UTF8String patientId = new UTF8String(CommInterfaceFieldConstants.PATIENT_ID_SIZE);
.
.
.
}
public class ReconData extends Struct
{
public final UTF8String patientId = new UTF8String(CommInterfaceFieldConstants.PATIENT_ID_SIZE);
public final UTF8String scanSeriesId = new UTF8String(CommInterfaceFieldConstants.SCAN_SERIES_ID_SIZE);
.
.
.
}
In our communication class, before we put data onto socket, we need to get the bytes[] of the RunScan object but we get BufferUnderflowException in the line with "//<<<<<<<":
private byte[] getCmdBytes(Struct scCmd)
{
ByteBuffer cmdBuffer = scCmd.getByteBuffer();
int cmdSize = scCmd.size();
byte[] cmdBytes = new byte[cmdSize];
if (cmdBuffer.hasArray())
{
int offset = cmdBuffer.arrayOffset() + scCmd.getByteBufferPosition();
System.arraycopy(cmdBuffer.array(), offset, cmdBytes, 0, cmdSize);
}
else
{
String msg = "\n\ncmdBufferRemaining=" + cmdBuffer.remaining() + ", cmdBytesSize=" + cmdBytes.length + "\n\n";
System.out.println(msg);
cmdBuffer.position(scCmd.getByteBufferPosition());
cmdBuffer.get(cmdBytes); //<<<<<<<<<< underFlowException
}
return cmdBytes;
}
This method works in other cases. The exception happens because this line,
ByteBuffer cmdBuffer = scCmd.getByteBuffer();
only returns a 8 bytes (from the remaining() method) ByteBuffer of the RunScan object which are those two Signed32 fields, I think. But this line,
int cmdSize = scCmd.size();
returns a right length of the RunScan object which includes the size of those two arrays.
If I create those two array at the time I declare them (not "new" them in the constructor) with hard coded length, it works fine without any exception.
Anybody can help me figure out what's wrong with our implementation?
I ran into a similar situation with my code. Generally, with the current Struct object, you cannot have a variable length array defined in the same struct as the member that contains the number of elements in the array.
Try something like this:
public class RunScanHeader extends Struct
{
public final Signed32 numOfClusters = new Signed32();
public final Signed32 numOfRecons = new Signed32();
}
public class RunScanBody extends Struct
{
public final ClusterData[] clusters;
public final ReconData[] recons ;
public RunScan (int numOfClusters, int numOfRecons)
{
clusters = array(new ClusterData[numOfClusters]);
recons = array(new ReconData[numOfRecons]);
}
}
You'll then need a two phase approach to read and write, first read/write the header data, then read/write the body data.
Sorry I don't have more details at this time, if you can't solve this, let me know and I'll dig back through my code.
The initialization order is important has it defines the position of each field. Either your initialization is done when the field is declared (most common case). Or if you do it in the constructor you have to remember that the constructor is called after the member initialization. Here is an example with initialization done in the constructor:
public class RunScan extends Struct {
public final Signed32 numOfClusters;
public final ClusterData[] clusters;
public final Signed32 numOfRecons;
public final ReconData[] recons ;
public RunScan (int numOfClusters, int numOfRecons) {
// Initialization done in the constructor for all members
// Order is important, it should match the declarative order to ensure proper positioning.
this.numOfClusters = new Signed32();
this.clusters = array(new ClusterData[numOfClusters]);
this.numOfRecons = new Signed32();
this.recons = array(new ReconData[numOfRecons]);
// Only after all the members have been initialized the set method can be used.
this.numOfClusters.set(numOfClusters);
this.numOfRecons.set(numOfRecons);
}
}
get() will move the position of the ByteBuffer.
scCmd.getByteBuffer().slice().get(dest) might solve your issue with moving the position and unintended side effects.
scCmd.getByteBuffer().duplicate().get(dest) might also solve your issue if slice() produces the wrong picture of the origin buffer.
Additionally, it appears as though scCmd.getByteBuffer() creates a redundant reference and you are calling the source and child reference in the same method.
If scCmd.getByteBuffer() is already passing you a slice(), your redundant access to these methods is certainly going to do something other than what you planned.