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.
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:
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 apackage
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 calledoutputDirectory
.
For the list of all available JNAerator options, visit the wiki page on GitHub called Command Line Options And Environment Variables.
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 .
JNaerator will create these two Java files:
outputDirectory/xyz/froud/jmccul/MeasurementComputingUniversalLibrary.java
outputDirectory/xyz/froud/jmccul/DaqDeviceDescriptor.java
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.
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
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 tostruct*
.... The tagging interfacesStructure.ByReference
andStructure.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:
- use the
Structure
constructor which accepts aPointer
parameter, or - call the static
Structure.newInstance()
method.
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 andread()
after the call. These calls are normally handled automatically by theFunction
object when it encounters aStructure
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 */