Skip to content

Latest commit

 

History

History
202 lines (133 loc) · 10.4 KB

info-about-JNA-and-JNAerator.md

File metadata and controls

202 lines (133 loc) · 10.4 KB

Information about JNA and JNAerator

JNA stands for Java Native Access. JNA is a Java library which lets you use native shared libraries in Java programs. Native shared libraries are .dll, .so, and .dylib files.

JNAerator is a tool which generates Java bindings for JNA.

How to download JNAerator

It appears JNAerator was originally hosted on Google Code and has been partly migrated to GitHub.

The GitHib repository does not have any ready-to-use jar files. Instead, you can download them from the Google Code archive.

It appears the latest version available on Google Code is "jnaerator-0.12-SNAPSHOT-20130727.jar", even though it is marked deprecated.

Screenshot of the Google Code page:

Screenshot of the JNAerator download page on Google Code, with the newest jar file highlighted with a red box

How to run JNAerator

Run this command:

java -jar path/to/jnaerator-0.12-SNAPSHOT-20130727.jar -library MeasurementComputingUniversal -mode Directory -runtime JNA -package xyz.froud.jmccul -o outputDirectory "C:\Users\Public\Documents\Measurement Computing\DAQ\C\cbw.h"

Explanation of the JNAerator options used:

  • -library MeasurementComputingUniversal: sets the name of the generated file to "MeasurementComputingUniversalLibrary.java".
  • -mode Directory: outputs a directory of Java source code files, instead of compiling them into a jar.
  • -runtime JNA: use JNA instead of BridJ.
  • -package xyz.froud.jmccul: adds a package statement to the top of the generated Java files, and creates subdirectories for each part of the package name.
  • -o outputDirectory: put all the output files in a folder called outputDirectory.

For the list of all available JNAerator options, visit the wiki page on GitHub called Command Line Options And Environment Variables.

Output

Command-line output

The expected command-line output is something like this:

Auto-configuring parser...
Parsing native headers...
Normalizing parsed code...
Generating libraries...
Generating DaqDeviceDescriptor.java
Generating MeasurementComputingUniversalLibrary.java
#
# SUCCESS: JNAeration completed !
# Output mode is 'Directory(Bindings sources in simple file hierarchy)
#
# => 'C:\path\to\outputDirectory'
#

If you have Visual Studio and/or a Windows SDK installed, you may see more output like this:

Oct 24, 2022 6:54:15 PM com.ochafik.admin.visualstudio.VisualStudioUtils getProp
INFO: [environment] ProgramFiles(x86)=C:\Program Files (x86)
Oct 24, 2022 6:54:15 PM com.ochafik.admin.visualstudio.VisualStudioUtils getProp
INFO: [environment] VISUAL_STUDIO_HOME=C:\Program Files (x86)\Microsoft Visual Studio 9.0
Oct 24, 2022 6:54:15 PM com.ochafik.admin.visualstudio.VisualStudioUtils getProp
INFO: [environment] ProgramFiles(x86)=C:\Program Files (x86)
Oct 24, 2022 6:54:15 PM com.ochafik.admin.visualstudio.VisualStudioUtils getProp
INFO: [environment] WINDOWS_SDK_HOME=C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A
Oct 24, 2022 6:54:15 PM com.ochafik.admin.visualstudio.VisualStudioUtils getProp
INFO: [environment] VISUAL_STUDIO_INCLUDES=C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include;C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Include

You can ignore errors about JNAerator not being able to find windows.h or time.h:

C:\Users\Public\Documents\Measurement Computing\DAQ\C\cbw.h:14:0: error: File not found: windows.h in C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Include .
C:\Users\Public\Documents\Measurement Computing\DAQ\C\cbw.h:14:0: error: File not found: time.h in C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Include .

File output

JNaerator will create these two Java files:

  • outputDirectory/xyz/froud/jmccul/MeasurementComputingUniversalLibrary.java
  • outputDirectory/xyz/froud/jmccul/DaqDeviceDescriptor.java

Post-processing

Set name of DLL to load

Replace the first three lines of the MeasurementComputingUniversalLibrary interface body with:

public static final MeasurementComputingUniversalLibrary INSTANCE = Native.load(
    (com.sun.jna.Platform.is64Bit() ? "cbw64.dll" : "cbw32.dll"),
    MeasurementComputingUniversalLibrary.class
);

You can read the Javadoc for the Native.load() method.

No Javadoc for the Platform.is64Bit() method, but the source code is somewhat interesting.

Remove deprecated methods

For simple cases, JNAerator produces one Java method signature for each C function. But if a C function involves pointers, JNAerator produces two Java method signatures, one marked deprecated and one not.

Here's a simplified example. This is the function prototype for cbDIn():

int cbDIn(int BoardNum, int PortType, unsigned short *DataValue);

JNAerator outputs:

/**
 * @deprecated Use the safer methods cbDIn(int, int, java.nio.ShortBuffer) and cbDIn(int, int, com.sun.jna.ptr.ShortByReference) instead.
 */
@Deprecated 
int cbDIn(int BoardNum, int PortType, ShortByReference DataValue);

int cbDIn(int BoardNum, int PortType, ShortBuffer DataValue);

(Confusingly, the @deprecated Javadoc tag suggests two safer alternative methods, but the second suggestion is the deprecated method.)

I want to remove all the deprecated methods. According to Command Line Options And Environment Variables, this JNAerator option should work:

  • -skipDeprecated
    Don't generate members that would be tagged as @Deprecated

But the JNAerator jar I'm using doesn't recognize that option and will not run if I add it.

Instead, I replaced all matches of the crazy regular expression below with an empty string.

\/\*\*\s+\*[\w\s:<>\(\),\*\/\\\.@\{\#\}]+@deprecated\s+int \w+\([\w ,\.]+\);

Link to try the regular expression: https://regexr.com/6bokv

Add helper method for Structure.ByValue

For the DaqDeviceDescriptor struct, JNAerator generates a class with two empty subclasses:

public class DaqDeviceDescriptor extends com.sun.jna.Structure {

    // struct stuff here

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

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

The Javadoc for com.sun.jna.Structure says:

When used as a function parameter or return value, [the Structure class] corresponds to struct*.... The tagging interfaces Structure.ByReference and Structure.ByValue may be used to alter the default behavior.

The ByReference subclass is not used in JMCCUL and can be removed from DaqDeviceDescriptor.java.

The ByValue subclass is used twice, as parameters for the cbCreateDaqDevice() and cbGetBoardNumber() functions.

The original Universal Library signatures of those functions are:

int cbCreateDaqDevice(int BoardNum, DaqDeviceDescriptor deviceDescriptor);

int cbGetBoardNumber(DaqDeviceDescriptor DeviceDescriptor);

The DaqDeviceDescriptor parameters are not pointers so JNAerator generates:

int cbCreateDaqDevice(int BdNum, DaqDeviceDescriptor.ByValue DeviceDescriptor);

int cbGetBoardNumber(DaqDeviceDescriptor.ByValue DeviceDescriptor);

Everywhere else uses DaqDeviceDescriptor with no tagging interface, and I had a hard time figuring out how to convert to DaqDeviceDescriptor.ByReference. (If you try to cast it it'll throw a ClassCastException.) I found these two comments which say to first call getPointer() and then either:

Using the constructor would require adding constructors to DaqDeviceDescriptor and the ByValue subclass which is annoying, so I chose to use the newInstance() method instead.

The Javadoc for the getPointer() method warns:

if you use the structure's pointer as a function argument, you are responsible for calling write() prior to the call and read() after the call. These calls are normally handled automatically by the Function object when it encounters a Structure argument or return value.

And indeed if you don't call read() after calling Structure.newInstance(), then cbCreateDaqDevice() will fail with error code 306. cbGetErrMsg() says "Error number 306 has no text" but cbw.h reveals:

/* Internal errors returned by 32 bit library */
...
#define CREATE_BOARD_FAILURE    306   /* 32 bit - failed to create board */