(__stdcall *)(void*,int,double))
JNIEasy::findExportedMethodAddress(
"examples.manual.MyCPPClassOnJava.value(int,double)");
return _value(this,opcode,value);
}
};
// MyCPPClassOnJava.cpp
double (__stdcall * MyCPPClassOnJava::_value)(void*,int,double) = 0;
An example
of call in C++:
MyCPPClassOnJava* proxy = ...
proxy->valueField(NativeFieldMethod::SET,10);
double res = proxy->valueField(NativeFieldMethod::GET,0);
//
Must be 10
The NativeFieldMethod class (holding the GET, SET and GET_SET constants) is defined in JNIEasy.h, constants 0 (GET), 1 (SET) and 2 (GET_SET) can be used too.
Important:
a native field-method callback access the related Java field directly (using a
direct access or reflection), if the (instance) field is native the native
memory is not read or modified. A possible new value set with the native field-method
can be lost and updated with the native memory value when reading the Java
field normally, and the Java field value read with the native field-method is
not ensured to be synchronized with the native memory. In our example the Java
field is already native and can be accessed directly from Java (using get and
set methods). Is not recommended to use a Java field declared as native and
accessed using a field-method (the Java field can be read/updated without the
native memory synchronization)
The export
of a Java field as a method is especially useful with static fields (a static
field can not be declared as native directly) and non-native instance fields.
The
proxy/callback modeling is based on methods not classes; there is no limitation
to mix in the same Java class methods working as proxy and methods working as
callbacks.
Java native
capable classes, declared as C++ classes and structures, support inheritance;
they can inherit from another Java native capable class mapping the C++ tree.
C++ unions do not support inheritance, this is applied to Java unions too.
In
enhancement time JNIEasy automatically detects if a user defined Java native
capable class inherits from another native capable class, there is no
declaration in the XML descriptor. The framework ever enhances first the base
native capable class locating and loading the XML descriptor of the base class,
the XML file must be alongside the .class file. Is not allowed to insert in the
middle of a native capable inheritance tree a non-native capable class (use a
native capable class without native fields if you need something similar), but
is valid to inherit from a non-native capable class as the top most base class
(in fact all classes inherit from java.lang.Object).
There is a
limitation regarding C++ virtual methods (declared with the virtual keyword): an exported virtual
method (like sub) is called from Java ignoring the virtual rule because its method
address is used directly (is not obtained using the virtual method table). This
is a minor limitation because the
virtual behavior works in the Java side, the Java method called (supposed
not final) is the
most derived, and this method calls the related derived method in the C++ side.
There is a
limitation: C++ objects created as proxy of a Java object (using the Java
constructor) are not created with the normal C++ new sentence and no C++ constructor is
called, a C++ constructor initializes the virtual table of methods (if any),
wherefore C++ virtual methods (declared with “virtual” keyword) can not be called. This
is a minor limitation because the
virtual behavior works in the Java side and the most derived method is
called. Base and derived methods must be declared in the C++ proxy without the virtual keyword, regardless the C++ method
called the most derived Java method is called. Of course C++ virtual methods
works ok if the object is created with a new sentence invoking a normal C++
constructor.
Inner
classes are allowed as native capable classes, but only static inner classes.
The XML enhancer descriptor file must follow the same name rules as the .class
file. For instance, if the MyStructure class declares a static inner class named MyInnerStructure, the XML file name must be:
MyStructure$MyInnerStructure.jnieas.enh.xml
And the XML
content may be:
<?xml version="1.0"
encoding="UTF-8"?>
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports/>
<class name="MyStructure$MyInnerStructure" type="structure" />
</package>
</jniEasyEnhancer>
The
developer can export to the native side, single Java methods with native
capable signatures outside Java classes as C++ classes, structures and union.
These methods, of course, must be declared inside Java classes (a Java
imperative). A user defined Java class containing a native capable method may
be declared as a native capable direct callback; a native instance has an
address and represents a native method with a native signature callable from the
native side (a pointer-to-method); in fact if the native instance is freed,
explicitly or by the garbage collector, the native method can not be called
from native because the method address is not valid (and can be reused by
another native element). A native method instance must be seen as a native
method compiled “on the fly”. Two native instances of the same class (same
method) have different addresses, they must be considered like two different
methods.
A simple
example with a static method:
package examples.manual;
public class AddTwoNumbersCallback
{
public static long add(int a,int b)
{
return (long)a + (long)b;
}
}
The XML
enhancer file:
<?xml version="1.0" encoding="UTF-8"?>
<!-- AddTwoNumbersCallback.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="AddTwoNumbersCallback" type="callback" >
<method name="add" params="int,int" >
</method>
</class>
</package>
</jniEasyEnhancer>
The
signature in the C/C++ side may be:
__int64
(__stdcall *) (int,int)
A native
instance can be used in the scenarios where a method pointer with this
signature is required, in fact, a native instance itself is a native
pointer-to-method in Java much like a CMethod/CPPConstructor/CPPMethod/CFieldMethod mapping a DLL method, the Java method can be
called from Java with the native marshalling.
An example
of native call from Java:
NativeStaticMethodCallback cb =
(NativeStaticMethodCallback)new AddTwoNumbersCallback();
JNIEasy.get().getNativeManager().makeNative(cb);
long res = cb.callLong(new Object[]{new Integer(2),new Integer(3)});
System.out.println("Must be 5: " + res);
The method AddTwoNumbersCallback.add(in,int) is called using a native bridge.
This feature is useful to test any Java native callback from Java.
In the
native programming world is very common to use pointer-to-method parameters
used as callbacks to be called into the method invoked, the C method signature
is working like an “interface”, and concrete C methods can be used as
parameters. In the previous example the wrapped method was static, the user
defined callback class name works as the native C signature of the method, but
the type and concrete method are “welded”, the user defined callback can not be
used as parameter type of “implementation changeable” callback parameters of
native methods declared in Java, because the callback implementation of any
native callback instance is ever the same. To avoid this problem the wrapped
method may be non-static, but the signature may remain as static too, a
non-static method can be overwritten in an inherited class, the only limitation
is the non-static method can not be abstract (neither the class).
Developing
a version “to override”:
package examples.manual;
public class AddTwoNumbersCallbackToOverride
{
public AddTwoNumbersCallbackToOverride()
{
}
public long add(int a,int b)
{
throw new RuntimeException("Override
this method");
}
}
The XML
enhancer file:
<?xml version="1.0" encoding="UTF-8"?>
<!--
AddTwoNumbersCallbackToOverride.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="AddTwoNumbersCallbackToOverride" type="callback" >
<method name="add" params="int,int" >
</method>
</class>
</package>
</jniEasyEnhancer>
Note the AddTwoNumbersCallbackToOverride XML file is identical to the AddTwoNumbersCallback version, in both cases the add method is a static native method,
but the native instance is used to call the add instance method.
In our
example is not useful to create instances of AddTwoNumbersCallbackToOverride, because the add method does nothing (throws an
exception), but the class name can be used now as a “placeholder” of concrete
implementations, native Java methods now can use this class as parameter type
and expect objects with derived classes. Of course another approach is to use
fields to distinguish the method behavior in a per instance basis, but this
approach is not so elegant.
Derived
classes do not need to be enhanced, no XML enhancer descriptor is needed, and
instances of these classes are native capable because the base class is already
native capable.
Following
the example:
package examples.manual;
public class AddTwoNumbersCallbackConcrete extends
AddTwoNumbersCallbackToOverride
{
public AddTwoNumbersCallbackConcrete()
{
}
public long add(int a,int b)
{
return (long)a + (long)b;
}
}
Instances
of this class can be made native and passed as parameters to native methods
where a AddTwoNumbersCallbackToOverride object is expected.
A real
world example, the EnumWindows method of the Win32 API (Microsoft documentation):
“The
EnumWindows function enumerates all top-level windows on the screen by passing
the handle to each window, in turn, to an application-defined callback
function. EnumWindows continues until the last top-level window is enumerated
or the callback function returns FALSE”[13].
The C
signature is the following:
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc, // pointer
to callback function
LPARAM lParam //
application-defined value
);
where WNDENUMPROC is defined as:
typedef BOOL
(CALLBACK* WNDENUMPROC)(HWND, LPARAM);
and CALLBACK is defined as:
#define CALLBACK __stdcall
The Java user
defined callback “template”:
package examples.manual;
public class EnumWindowsProc
{
public EnumWindowsProc()
{
}
public int onCall(int hwnd,int lParam)
{
throw new RuntimeException("Override
this method");
}
}
The XML
enhancer file:
<?xml version="1.0" encoding="UTF-8"?>
<!-- EnumWindowsProc.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="EnumWindowsProc" type="callback" >
<method name="onCall" callConv="std_call"
params="int,int" >
</method>
</class>
</package>
</jniEasyEnhancer>
The
concrete callback (no need of a XML descriptor):
package examples.manual;
public class EnumWindowsProcConcrete extends EnumWindowsProc
{
public int windowCount;
public EnumWindowsProcConcrete()
{
}
public int onCall(int hwnd, int lParam)
{
windowCount++;
return 1;
// TRUE
}
public int getWindowCount()
{
return windowCount;
}
}
and finally
an example of use:
DynamicLibrary dll = JNIEasy.get().getDLLManager().get("User32");
CMethod method = dll.addCMethod("EnumWindows",int.class,
new Object[]{EnumWindowsProc.class,int.class},CallConv.STD_CALL);
EnumWindowsProc lpEnumFunc = new EnumWindowsProcConcrete();
int res = method.callInt(new Object[]{lpEnumFunc,new
java.lang.Integer(5)});
System.out.println(“Opened windows :” + lpEnumFunc.getWindowCount());
In previous
examples, the exported method was inside the native capable callback class,
this is not mandatory, the method can be outside the callback class and in the
case of static methods the container class does not need to be a native capable
class.
Example:
package examples.manual;
public class AddTwoNumbersCallbackOutside
{
public AddTwoNumbersCallbackOutside()
{
}
}
The XML
enhancer descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<!-- AddTwoNumbersCallbackOutside.jnieasy.enh.xml
-->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="AddTwoNumbersCallbackOutside" type="callback" >
<method name="addStatic" thisClass="MyCPPClassOnJava"
params="int,int">
</method>
</class>
</package>
</jniEasyEnhancer>
The
attribute thisClass="MyCPPClassOnJava" specifies
the class where the method (addStatic) must be searched.
A user
defined callback can make native an instance Java method seen as instance
method in the native side too. This method must be declared in a user defined
native capable class declared as C++ class, structure or union (“field based”
user defined native capable classes). This (native capable) method does not
need to be declared as native in the container class’s XML enhancer descriptor
because the native method view is managed by the callback class, anyway a
native instance of the container’s class is needed to call this method from
native (because is a instance/non-static method in Java and in native side).
The method is invoked from native using an instance of the container class as
the first parameter (using a C form layout of the instance method with a
container class pointer as the first parameter).
Example:
mapping the sub method of MyCPPClassOnJava
The Java
callback:
package examples.manual;
public class MyCPPClassOnJavaSubCallback
{
public MyCPPClassOnJavaSubCallback()
{
}
}
The XML
descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<!-- MyCPPClassOnJavaSubCallback.jnieasy.enh.xml
-->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="MyCPPClassOnJavaSubCallback" type="callback" >
<method name="sub" thisClass="MyCPPClassOnJava"
params="int,int" >
</method>
</class>
</package>
</jniEasyEnhancer>
The thisClass attribute specifies the container
class of sub.
One step
forward: declaring a constructor and destroy callbacks we have an example of a complete
lifecycle of a MyCPPClassOnJava native instance without using MyCPPClassOnJava
explicitly.
Mapping the
two integer constructor with a Java callback:
package examples.manual;
public class MyCPPClassOnJavaConstructorCallback
{
public MyCPPClassOnJavaConstructorCallback()
{
}
}
The XML
enhancer descriptor of the constructor:
<?xml version="1.0" encoding="UTF-8"?>
<!--
MyCPPClassOnJavaConstructorCallback.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="MyCPPClassOnJavaConstructorCallback"
type="callback" >
<constructor thisClass="MyCPPClassOnJava"
params="int,int">
</constructor>
</class>
</package>
</jniEasyEnhancer>
Mapping the
destroy method
with a Java callback:
package examples.manual;
public class MyCPPClassOnJavaDestroyCallback
{
public MyCPPClassOnJavaDestroyCallback()
{
}
}
The XML
enhancer descriptor of the destroy callback:
<?xml version="1.0" encoding="UTF-8"?>
<!--
MyCPPClassOnJavaDestroyCallback.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="MyCPPClassOnJavaDestroyCallback" type="callback" >
<method name="destroy" thisClass="MyCPPClassOnJava"
params="MyCPPClassOnJava">
</method>
</class>
</package>
</jniEasyEnhancer>
Putting it
all together:
NativeConstructorCallback constCb =
(NativeConstructorCallback)new MyCPPClassOnJavaConstructorCallback();
JNIEasy.get().getNativeManager().makeNative(constCb);
Object[] args = new Object[] { new Integer(2), new Integer(3) };
Object obj = constCb.call(args); //
Calls constructor and
// returns a native
MyCPPClassOnJava instance
NativeInstanceMethodCallback cb =
(NativeInstanceMethodCallback)new MyCPPClassOnJavaSubCallback();
JNIEasy.get().getNativeManager().makeNative(cb);
args = new Object[] { new Integer(2), new Integer(3) };
double res = cb.callDouble(obj,args); // Calls sub(int,int)
NativeStaticMethodCallback destCb =
(NativeStaticMethodCallback)new MyCPPClassOnJavaDestroyCallback();
JNIEasy.get().getNativeManager().makeNative(destCb);
destCb.callVoid(new Object[]{obj}); // Calls
destroy(MyCPPClassOnJava)
The exportMethod="true" can be used in the declaration of a
user defined native capable callback, if set, a native instance callback is
created, made native and exported when the callback class is loaded.
Example:
updating the AddTwoNumbersCallback XML file:
...
<class
name="AddTwoNumbersCallback"
type="callback"
>
<method
name="add"
exportMethod="true" >
...
In the
native side the callback pointer can be obtained using findExportedMethodAddress and the signature name:
"examples.manual.AddTwoNumbersCallback.add(int,int)"
Example:
JNIEasyLibrary dll = JNIEasy.get().getJNIEasyLib();
String sig = "examples.manual.AddTwoNumbersCallback.add(int,int)";
NativeStaticMethod method =
(NativeStaticMethod)dll.findExportedMethodAddress(sig);
long res = method.callLong(new Object[]{new Integer(1),new Integer(2)});
// add(1,2) call
System.out.println("Must
be 3: " + res);
The NativePointer interface is implemented by native
capable classes wrapping a native capable or “can be native” object reference
working as a native pointer. A NativePointer native instance holds a native pointer, wherefore a NativePointer native reference is like a C/C++
“pointer to pointer” (ex. int**).
The
framework offers a default implementation, therefore a NativePointer field can be declared as a native field
in a structure etc; the internal pointer (which type is declared in the XML
descriptor of the field) can be got /set
with the methods getValue()/setValue(Object).
The
developer can code user defined native capable class pointers holding a field
with a fixed pointer type. These user defined classes can be used instead of
the default implementation.
Example: a
pointer to an UNICODE String (wchar_t**)
package examples.manual;
public class PointerToString
{
protected String ptrToStr;
public PointerToString() //
mandatory
{
}
public PointerToString(String pointer)
{
this.ptrToStr = pointer;
}
public String getString()
{
return ptrToStr;
}
public void setString(String pointer)
{
this.ptrToStr = pointer;
}
}
The XML
enhancer file:
<?xml version="1.0"
encoding="UTF-8"?>
<!-- Archive
PointerToString.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports/>
<class name="PointerToString" type="pointer">
<field name="ptrToStr" encoding="unicode" />
</class>
</package>
</jniEasyEnhancer>
A PointerToString reference is
seen as a char** C type.
Java arrays
need native capable classes to manage its native state. All combinations of
Java arrays of enabled types are managed by the framework, including
multidimensional arrays. Most of them have a specific interface/default native capable
class with no need of casting.
There are
two families of arrays with a default implementation managing multiple types
(casting needed):
1. Multidimensional arrays (ex. int[][])
2. Arrays of native capable classes
(ex. MyStructure[] or
NativeString[])
The
developer can code user defined native capable class arrays holding a concrete
Java array field: a native capable or multidimensional array. These user
defined classes can be used instead of the default implementation.
Example: a two-dimensional
int array
package examples.manual;
import
com.innowhere.jnieasy.core.data.NativeObjectArray;
public class IntArray2Dim
{
private int[][] value;
public IntArray2Dim() //
mandatory
{
}
public IntArray2Dim(int[][] value)
{
this.value = value;
}
public int[][] getIntArray()
{
return value;
}
public void setIntArray(int[][] value)
{
this.value = value;
}
public int getInt(int[] dims)
{
return this.value[dims[0]][dims[1]];
}
public void setInt(int[] dims,int newValue)
{
int[][] aux = this.value;
aux[dims[0]][dims[1]] =
newValue;
this.value = aux;
}
public int getIntQuick(int[] dims)
{
NativeObjectArray thisArr
= (NativeObjectArray)this;
return
((Integer)thisArr.getElement(dims)).intValue();
}
public void setIntQuick(int[] dims,int newValue)
{
NativeObjectArray thisArr
= (NativeObjectArray)this;
thisArr.setElement(dims,new Integer(newValue));
}
}
The XML
enhancer file:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive IntArray2Dim.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports/>
<class name="IntArray2Dim" type="array">
<field name="value" />
</class>
</package>
</jniEasyEnhancer>
The field value is internally and automatically declared
“by value” (the native memory layout is the same as a structure with a single
array field by value), and can not be declared “by pointer”.
The native
capable array container can have any values of dimensions; a concrete instance
and field value imposes the max values of dimensions (and memory size). To declare
fixed values (lengths) of dimensions:
. . .
<field
name="value"
length="2">
<component length="3" />
</field>
. . .
The previous
example declares the custom class as a fixed size array, all instances have the
same maximum lengths (2 and 3).
The methods
getInt(int[])
and setInt(int[],int)
are examples of methods to get and set an element of the array ensuring
synchronization with the native memory, the enhancer can not control an element
access of an array.
return this.value[dims[0]][dims[1]];
Reads the
native memory and copies the array in this.value, then returns the concrete element read from
Java memory.
int[][] aux = this.value;
The aux = this.value; reads the native memory and copies
the array in this.value, the array reference points to the Java array.
aux[dims[0]][dims[1]] =
newValue;
Sets the
new element in the array on Java memory.
this.value = aux;
Updates the
this.value field
and the native memory of the whole array.
The methods
getIntQuick(int[])
and setIntQuick(int[],int) are the speeder versions of the previous methods, only the strictly necessary
native memory is read/written (the element read/updated). The multidimensional
array is a NativeObjectArray
after the enhancement process. Of course NativeObjectArray methods can be called outside the
class.
Example: an
array of structures (elements declared “by pointer”).
package examples.manual;
import
com.innowhere.jnieasy.core.data.NativeObjectArray;
public class MyStructureArray
{
private MyStructure[] value;
public MyStructureArray()
{
}
public MyStructureArray(MyStructure[] value)
{
this.value = value;
}
public MyStructure[] getStructArray()
{
return value;
}
public void setStructArray(MyStructure[] value)
{
this.value = value;
}
public MyStructure getStruct(int index)
{
return value[index];
}
public void setStruct(int index,MyStructure value)
{
MyStructure[] aux = this.value;
aux[index] = value;
this.value = aux;
}
public MyStructure getStructQuick(int index)
{
NativeObjectArray thisArr
= (NativeObjectArray)this;
return (MyStructure)thisArr.getElement(index);
}
public void setStructQuick(int index,MyStructure value)
{
NativeObjectArray thisArr
= (NativeObjectArray)this;
thisArr.setElement(index,value);
}
}
The XML
enhancer file:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyStructureArray.jnieasy.enh.xml
-->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports/>
<class name="MyStructureArray" type="array">
<field name="value" />
</class>
</package>
</jniEasyEnhancer>
Again the
field value is
internally declared “by value”, the native capable array container can have any
values of dimensions (a fixed array length can be specified too with the length attribute); array elements
(structures) are declared “by pointer” (the default native variable type), then
MyStructureArray
is an array of MyStructure pointers (MyStructure*[]).
We have
seen how a C++ class can be accessed from Java using JNIEasy, basically
exporting the required methods and adding standard or C call conventions. What
about a legacy C++ class? If we cannot touch the source code we can ever add
special C methods wrapping and exporting legacy methods. In C++ an instance
method is called passing as first parameter a pointer to the object instance,
an instance method is basically a C method with a hidden parameter (a pointer
to the object), a virtual table is used to resolve the address of the method.
We can simulate with C methods false C++ methods, of course excluding virtual
addressing.
Example:
Legacy C++
class MyLegacyClassOnDLL
(header):
#ifndef MyLegacyClassOnDLL_h
#define MyLegacyClassOnDLL_h
#include "JNIEasy.h"
// MyLegacyClassOnDLL.h
class MyLegacyClassOnDLL
{
protected:
double m_value;
public:
MyLegacyClassOnDLL(int a,int b);
virtual ~MyLegacyClassOnDLL();
static __int64 addStatic(int a,int b);
double getValue();
virtual double sub(int a,int b);
};
extern "C"
{
DLLEXPORT MyLegacyClassOnDLL* __stdcall MyLegacyClassOnDLL_create(
int a,int b);
DLLEXPORT void __stdcall
MyLegacyClassOnDLL_destroy(
MyLegacyClassOnDLL* obj);
DLLEXPORT __int64 __stdcall
MyLegacyClassOnDLL_addStatic(int a,int
b);
DLLEXPORT double __stdcall
MyLegacyClassOnDLL_getValue(
MyLegacyClassOnDLL* obj);
DLLEXPORT double __stdcall MyLegacyClassOnDLL_sub(
MyLegacyClassOnDLL* obj,int a,int b);
}
#endif
Implementation:
#include "MyLegacyClassOnDLL.h"
#include <stdio.h>
#include <stdarg.h>
MyLegacyClassOnDLL::MyLegacyClassOnDLL(int a,int b)
{
m_value = a + b;
}
MyLegacyClassOnDLL::~MyLegacyClassOnDLL()
{
}
__int64 MyLegacyClassOnDLL::addStatic(int a,int b)
{
return (__int64)a + (__int64)b;
}
double MyLegacyClassOnDLL::getValue()
{
return m_value;
}
double MyLegacyClassOnDLL::sub(int a,int b)
{
m_value = m_value - (a + b);
return m_value;
}
// Wrapper Methods
MyLegacyClassOnDLL* __stdcall MyLegacyClassOnDLL_create(int a,int b)
{
return new MyLegacyClassOnDLL(a , b); // may be an inherited class
}
void __stdcall MyLegacyClassOnDLL_destroy(MyLegacyClassOnDLL* obj)
{
delete obj;
}
__int64 __stdcall MyLegacyClassOnDLL_addStatic(int a,int b)
{
return MyLegacyClassOnDLL::addStatic(a,b);
}
double __stdcall MyLegacyClassOnDLL_getValue(MyLegacyClassOnDLL* obj)
{
return obj->getValue();
}
double __stdcall MyLegacyClassOnDLL_sub(MyLegacyClassOnDLL* obj,
int
a,int b)
{
return obj->sub(a,b);
}
C methods
are simple wrappers of the C++ methods exporting them to Java. Original class
fields can be directly mapped in Java.
The Java
class:
public class MyLegacyClassOnDLL
{
protected int virtualTable; // the C++ class has a virtual method
protected double value;
public MyLegacyClassOnDLL() //
mandatory (is not native)
{
}
public MyLegacyClassOnDLL(int a, int b)
{
throw new RuntimeException("Not
enhanced");
}
public static native void destroy(MyLegacyClassOnDLL obj);
public static native long addStatic(int a,int b);
public double getValue()
{
return value;
}
public native double sub(int a,int b);
}
The XML
file, MyLegacyClassOnDLL.jnieasy.enh.xml, for the enhancer (is added to the
enhancer XML descriptor with the list to enhance too):
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyLegacyClassOnDLL.jnieasy.enh.xml
-->
<jniEasyEnhancer version="1.1" ...>
<package name="examples.manual">
<imports/>
<class name="MyLegacyClassOnDLL" type="class"
libraryPath="MyLibrary" >
<constructor onLibrary="true"
nativeName="MSC:_MyLegacyClassOnDLL_create@8;gcc:MyLegacyClassOnDLL_create" params="int, int">
</constructor>
<method name="destroy" onLibrary="true"
nativeName="MSC:_MyLegacyClassOnDLL_destroy@4;gcc:MyLegacyClassOnDLL_destroy" params="MyLegacyClassOnDLL">
</method>
<method name="addStatic" onLibrary="true"
nativeName="MSC:_MyLegacyClassOnDLL_addStatic@8;gcc:MyLegacyClassOnDLL_addStatic" params="int,int">
</method>
<method name="sub" onLibrary="true"
nativeName="MSC:_MyLegacyClassOnDLL_sub@12;gcc:MyLegacyClassOnDLL_sub"
params="int,int">
</method>
</class>
</package>
</jniEasyEnhancer>
And finally
a use example:
byte res = MyLegacyClassOnDLL.addStatic(1,2);
System.out.println("Must be 3: " + res);
MyLegacyClassOnDLL obj = new MyLegacyClassOnDLL(1,2);
//
Calls create() C++ method. New object is already native
System.out.println("Must be 3: " + obj.getValue());
// Java
method getValue() is not native (field “value” is native)
double res2 = obj.sub(1,2);
System.out.println("Must be 0: " + res2);
MyLegacyClassOnDLL.destroy(obj);
// Calls
the C++ method destroy(), the memory is freed
User
defined classes must be enhanced to be converted in native capable classes. The
enhancer adds new code and modifies the byte code of the compiled class
providing the transparent behavior of the native memory-Java memory
synchronization.
Enhancement
can be done on filesystem or on runtime (on class loading).
If the
enhancement is done on filesystem, this task does not use the native memory
and/or load user dynamic link libraries (except the framework’s DLL).
An enhanced
.class in the filesystem does not need the XML enhancer descriptor file in
runtime and can be distributed without it.
The
enhancement process of a user defined class may need to create new (internal)
native capable classes, theses classes are saved alongside the user defined
.class; on runtime these classes are loaded automatically when necessary.
The
enhancement task is usually performed using the NativeEnhancerCmd class in the command line or Ant
task, or using the method:
NativeEnhancer.enhance(URL filePath,String outputDir)
In both
cases, the enhancement is made in “batch” mode using a “root” XML file listing
the XML descriptor files of classes to be enhanced. The name of this “root” XML
file is not fixed.
The
following example file (enhancer.xml) list all XML enhancer descriptor files of all user defined native
capable classes used in this manual, all XML files are in the same directory in
this example:
<?xml version="1.0" encoding="UTF-8"?>
<!-- enhancer.xml -->
<jniEasyEnhancer version="1.1">
<include file="AddTwoNumbersCallback.jnieasy.enh.xml" />
<include file="AddTwoNumbersCallbackOutside.jnieasy.enh.xml" />
<include file="AddTwoNumbersCallbackToOverride.jnieasy.enh.xml" />
<include file="Dimension.jnieasy.enh.xml" />
<include file="EnumWindowsProc.jnieasy.enh.xml" />
<include file="IntArray2Dim.jnieasy.enh.xml" />
<include file="MyCPPClassOnDLL.jnieasy.enh.xml" />
<include file="MyCPPClassOnJava.jnieasy.enh.xml"/>
<include file="MyCPPClassOnJavaConstructorCallback.jnieasy.enh.xml"/>
<include file="MyCPPClassOnJavaDestroyCallback.jnieasy.enh.xml"/>
<include file="MyCPPClassOnJavaSubCallback.jnieasy.enh.xml"/>
<include file="MyStructure.jnieasy.enh.xml"/>
<include file="MyStructureArray.jnieasy.enh.xml"/>
<include file="PointerToString.jnieasy.enh.xml"/>
<include file="inc/enhancer2.xml" />
</jniEasyEnhancer>
The file attribute of the include tag expects the relative file path,
or the URL of a XML enhancer descriptor class file or another “root” enhancer
file. The “inc/enhancer2.xml”
is an example of “including” another XML file in the “inc” subdirectory of the current folder
of the enhancer.xml
file.
The syntax
of the NativeEnhancerCmd
command is:
java
com.innowhere.jnieasy.core.NativeEnhancerCmd urlClassDescXML outputDir
Where urlClassDescXML is the URL of the XML root descriptor (or a
specific class enhancer file) and outputDir is the root directory to write the
enhanced classes (is highly recommended the same directory of the non enhanced .class files). But these is a very simplified command, the
java interpreter must locate the .class archives (a classpath must be specified) and the JNIEasy
DLL must be located (must be in the search path specified with the java.library.path property).
Is highly
recommended to use Ant to perform the enhancement tasks. The Ant file build.xml provided with JNIEasy is a complete
cross-platform example of Ant tasks, only a few variables of
/conf/conf.properties file must be modified to setup a development environment
of JNIEasy.
The XML
descriptor root lists the classes to enhance, but a user defined native capable
class being enhanced may need the native description of another user defined
native class, if this user defined native class is not already enhanced
(declared later in the list) the framework will search in the Java classpath to
locate the XML enhancer descriptor file with the file name according the name
rules and will enhance this class before (cyclic references are managed with no
problem), more info in the On
load enhancer section. If a class was already enhanced the enhancer
does nothing (the .class file must be removed or the source class recompiled to
re-enhance again a .class).
The content
of the top most root file enhancer.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<!-- enhancer.xml -->
<jniEasyEnhancer version="1.1">
<include file="manual/enhancer.xml" />
<include file="win32exam/win32/enhancer.xml" />
</jniEasyEnhancer>
The “on
load enhancer” is an optional feature useful to avoid the filesystem
enhancement of user defined native capable classes. The enhancement is
performed in runtime using a special framework’s ClassLoader when the class is used first time. If
you need a quick loading of your program this feature must not be used because
the enhancement is a bit slow task (use the filesystem enhancement on
development time).
The ClassLoader enhancer enhances the original
non-enhanced .class file in memory before submitting the bytecode to the JVM
using the ClassLoader.defineClass(String,byte[],int,int) method. To do the enhancement task the enhancer
ClassLoader
needs to locate the XML enhancer descriptor file of the class, the search is
performed with the method ClassLoader.getResource(String) using the name convention of XML
enhancer files, the XML file must be in the classpath; the most simple approach
is to put in the same folder the XML and the .class files.
Example of
“on load enhancer” activation:
NativeEnhancer enhancer = JNIEasy.get().getEnhancer();
ClassLoader myParentLoader = Thread.currentThread().getContextClassLoader();
String[] included = new String[] { "examples.manual.*"
};
String[] excluded = new String[] { StartInterface.class.getName() };
ClassLoader enhancerCL =
enhancer.enableOnLoad(included,excluded,myParentLoader);
Class clsStart =
Class.forName("examples.manual.Start",true, enhancerCL);
// Start class implements StartInterface
StartInterface start = (StartInterface)clsStart.newInstance();
start.run(); //
method using native capable classes not enhanced
See Enhancer.enableOnLoad(String[],String[],ClassLoader) for more info.
The Java
code generation utilities are especially useful to generate the Java proxy code
of methods (usually C methods) of DLLs exporting tons of methods. Usually a
program uses a small group of methods of a big DLL, for example the Windows User.dll, the enhancer approach can spend
very much time and memory initializing all proxy methods, used or not used in
the program. The Java code generated by the generation utilities follows the
principle: “the first use creates the proxy”, there is no creation and registry
of any internal proxy instance (using DynamicLibrary methods) until the method is first used, the
second time will use the previously created proxy. The typical approach is
group the C exported methods of a DLL in the same code generated Java class.
The
generated methods are not synchronized to achieve the maximum performance, but
they can work in a multithread environment because the creation of proxy
instances is synchronized by the framework (the framework ensures the creation
of a single proxy instance when two threads try in the same instant to create
the same proxy instance).
In spite of
the main objective is to generate C proxy methods, constructors and C++ proxy
methods can be generated too, the generated Java class can be enhanced in a
following step. This is another approach to create native capable classes with
methods working as proxy of C/C++ methods, these proxy methods with generated
code do not need to be enhanced (they work already as proxies), use this
technique if you are more comfortable with less code hidden by the enhancer.
The
generation task is usually performed using the NativeJavaCodeGeneratorCmd class in the command line or Ant
task, or using the method:
NativeJavaCodeGenerator.generate(String,String,String[],String[])
In both
cases, the generation is made in “batch” mode using a “root” XML file listing
the XML code generation descriptor files of classes to be generated. The name
of this “root” XML file is not fixed, the XML generation descriptor of a class
neither has a fixed name, but the following format is recommended:
SimpleClassName.jnieasy.jgen.xml
The XML
file syntax is very similar to the enhancer counterpart.
The
following example file (proxygen.xml) is an example of a XML root file:
<?xml version="1.0" encoding="UTF-8"?>
<!-- proxygen.xml -->
<jniEasyJavaCodeGen version="1.1">
<include file="MyCPPClassOnDLLGen.jnieasy.jgen.xml" />
<include file="inc/proxygen2.xml" />
</jniEasyJavaCodeGen>
An example
of a XML generation class descriptor file: it follows a similar syntax to the
enhancer counterpart (using MSVC name mangling, the same example in the
distribution is cross-compiler):
<?xml version="1.0" encoding="UTF-8"?>
<!-- MyCPPClassOnDLLGen.jnieasy.jgen.xml -->
<jniEasyJavaCodeGen version="1.1">
<fileGen name="MyCPPClassOnDLLGen"> <!-- generator adds
.java -->
<package name="examples.manual" >
<imports>
<import class="java.io.*" />
</imports>
<class name="MyCPPClassOnDLLGen" extends="Object"
implements="Serializable,Cloneable"
libraryPath="MyLibrary" >
<field class="int" name="virtualTable" />
<field class="double" name="value" />
<freeCode>
protected
int virtualTable;
protected
double value;
/* Empty
constructor */
MyCPPClassOnDLLGen()
{
}
</freeCode>
<constructor
nativeName="?create@MyCPPClassOnDLL@@SGPAV1@HH@Z">
<param class="int" name="a" />
<param class="int" name="b" />
</constructor>
<method name="destroy" methodType="C"
nativeName="?destroy@MyCPPClassOnDLL@@SGXPAV1@@Z">
<return class="void" />
<params>
<param class="MyCPPClassOnDLL" name="obj" />
</params>
</method>
<method name="addStatic" methodType="C"
nativeName="?addStatic@MyCPPClassOnDLL@@SG_JHH@Z">
<return class="long" />
<params>
<param class="int" name="a" />
<param class="int" name="b" />
</params>
</method>
<method name="sub" methodType="CPP"
nativeName="?sub@MyCPPClassOnDLL@@UAGNHH@Z">
<return class="double" />
<params>
<param class="int" name="a" />
<param class="int" name="b" />
</params>
</method>
<method
name="varargsEx"
methodType="C"
nativeName="?varargsEx@MyCPPClassOnDLL@@SAXPADZZ"
callConv="c_call">
<return class="void" />
<params>
<param class="byte[]" name="buffer" />
<param dec="Object..." name="args" />
</params>
</method>
</class>
</package>
</fileGen>
</jniEasyJavaCodeGen>
Generates a
class file, MyCPPClassOnDLLGen.java, very similar to the class
MyCPPClassOnDLL seen
before, with the exception of the methods now containing the necessary proxy
code to call the C/C++ matched method.
Note the name and class attributes of fields and methods (returns)
missing in the enhancer version (because the code generation constructs the
Java class from scratch). The extends and implements attributes are declared to illustrate its use. The import (tag) declaration is needed to
resolve the Serialize interface in the generated class.
If the Java
class references other user defined native capable class, this must be previously
enhanced or must be located the XML enhancer descriptor in the classpath
(because the on load enhancer is used if necessary)
The syntax
of the NativeJavaCodeGeneratorCmd command is:
java com.innowhere.jnieasy.core.NativeJavaCodeGeneratorCmd
[-enhanceOnLoad includeImportList
[-excludeImports excludeImportList]] xmlDescriptorPath outputDir
The
complete syntax is documented in the class NativeJavaCodeGeneratorCmd.
The
distribution’s build.xml contains Ant task examples to call the code generation tool.
The top
most root is proxygen.xml defined as:
<?xml version="1.0" encoding="UTF-8"?>
<!-- proxygen.xml -->
<jniEasyJavaCodeGen version="1.1">
<include file="manual/proxygen.xml" />
<include file="win32exam/proxygen.xml" />
</jniEasyJavaCodeGen>
The
generated class MyCPPClassOnDLL must be enhanced later to work as a proxy of
the MyCPPClassOnDLL C++
class, anyway, static methods can be called without enhancement (the main
objective of the code generator, because most of the “big” DLLs are C
libraries).
A partial
result of the generated code:
package examples.manual;
import java.io.*;
public class MyCPPClassOnDLLGen extends Object
implements Serializable,Cloneable
{
public static com.innowhere.jnieasy.core.JNIEasy
jnieasyRoot =
com.innowhere.jnieasy.core.JNIEasy.get();
public static
com.innowhere.jnieasy.core.mem.DynamicLibrary
jnieasyDLL =
jnieasyRoot.getDLLManager().get("MyLibrary");
public static com.innowhere.jnieasy.core.method.NativeBehavior[]
jniEasyMethodList
=
new
com.innowhere.jnieasy.core.method.NativeBehavior[ 4
];
...
public static long addStatic(int a,int b)
{
com.innowhere.jnieasy.core.method.NativeBehavior jnieasyMethod
= jnieasyMethodList[2];
if (jnieasyMethod == null)
{
String jnieasyFuncName
=
"?addStatic@MyCPPClassOnDLL@@SG_JHH@Z";
jnieasyMethod =
jnieasyMethodList[2] =
jnieasyDLL.addCMethod(jnieasyFuncName,
jnieasyRoot.getTypeManager().dec(long.class).decVarType(),
new java.lang.Object[] {jnieasyRoot.getTypeManager().dec(int.class).decVarType().decParameter(),
jnieasyRoot.getTypeManager().dec(int.class).decVarType().decParameter()},
com.innowhere.jnieasy.core.typedec.CallConv.STD_CALL
);
}
return
((com.innowhere.jnieasy.core.method.NativeStaticMethod)jnieasyMethod).
callLong(new java.lang.Object[]
{new java.lang.Integer(a),new java.lang.Integer(b)});
}
. . .
}
A simple
invocation example:
long res = MyCPPClassOnDLLGen.addStatic(1,2);
System.out.println("Must be 3: " + res);
The varargsEx method is interesting because
contains a “varargs” parameter, in this example the dec attribute is used to illustrate the
“Object...” declaration following the Java 5 notation; this expression declares the
parameter as “varargs” with type Object[]. This declaration is equivalent to declare the parameter with class=”Object[]” and varargs=”true” .
JNIEasy
offers two interfaces (with two default implementation classes) to manipulate
the native memory: NativeBuffer and NativeBufferIterator.
A NativeBuffer object represents a contiguous
native memory section and has a native address, using this object the native
memory can be read and modified directly without native objects. The buffer can
own the native memory or be attached, if the buffer owns the memory this is
freed automatically when the object goes out the scope (garbage collected), but
can be explicitly freed with the method NativeBuffer.free(). A buffer can be created with the
methods:
·
NativeManager.allocateBuffer(long size) to create a buffer allocating
memory.
·
NativeManager.attachBuffer(long address,long size)
to attach the new buffer to an already allocated memory section. If the size is -1 the buffer is considered unlimited
and the upper bound is not checked.
The NativeBuffer interface has get and set methods
to access native memory data, all of them use an offset parameter, this offset is the 0
based relative memory position in bytes where to read or write (to calculate
the absolute position the offset is added to the buffer’s address).
Example:
NativeBuffer buffer = JNIEasy.get().getNativeManager().allocateBuffer(1024);
long offset = 16;
double value = buffer.getDouble(offset);
value = value*2;
buffer.setDouble(offset,value);
Every
native instance holds a NativeBuffer (referenced by the NativeStateManager), the method NativeCapableUtil.getBuffer(Object) returns this object. Is not
recommended to free the buffer of a native instance with NativeBuffer.free(), free the native memory of a native
instance with NativeCapableUtil.free(Object).
The NativeBufferIterator interface is used to move along the
buffer’s native memory. Every reading or writing moves forward the internal
iterator position the read or written bytes. A buffer iterator is created with
the method NativeBuffer.newBufferIterator(), the iterator created is “attached” to the
parent buffer. Several iterators can read concurrently (mono or multithread)
the same buffer with no problem, concurrent multithread is possible but
obviously dangerous (NativeBuffer and NativeBufferIterator objects are
not thread synchronized).
Example:
NativeBufferIterator it = buffer.newBufferIterator();
for(int i = 0;
i < 10; i++)
it.setInt(i);
writes 10
contiguous integers (jint) in the buffer.
Main
objective of JNIEasy’s cross-platform options is simple: one Java code,
multiple operating systems, compilers and native layouts[14]. The
cross-platform in this context means cross-operating system, cross-compiler and
cross-native layouts.
To achieve
this objective JNIEasy offers several features:
·
Platform
dependent size of addresses
·
Multiple
native memory sizes and alignments of primitive types
·
A
C “inspired” macro based system to resolve sizes and names
Java
primitive data types have fixed sizes; it was a very successful rule, because a
pure Java program works exactly equal in all platforms. But this is a problem
making Java and C/C++ integration, because all pointer types like void* and long data type[15] are
platform dependent: in a 32 bit platform pointers and long size is 4 bytes (same as int), in a 64 bit environment pointers
have 8 bytes and long has 8 bytes too in a LP64 model[16] (fortunately
int maintains
the same size, 32 bits). In a 32 bit platform the C/C++ long/int/anytype* – Java int correspondence does not work fully
in a 64 bit platform; the portable correlation must be C/C++ jint and Java int or C/C++ jlong
and Java long, but is
very unusual to see C/C++ legacy code using JNI data types.
One
solution is to offer 32 bit and 64 bit versions of Java programs based in
JNIEasy, the 32 bit version will use the Java int and the 64 bit version the long data type will be used to map the
C/C++ long, in a LP64 platform, or to map an address/handler (usually handlers are
pointers to undocumented structures).
The other
solution is to use cross-platform addresses.
JNIEasy is
ready to the 64 bit platforms (x86 AMD64/EM64T architectures).
JNIEasy
uses the long type to
deal with native addresses, memory sizes and offsets, in a 32 bit platform the
addresses (and related memory sizes) are promoted to long normally (the four upper bytes are
ever 0 if the integer value is positive or FFFFFFFF if negative) and cast to 32
bits with a normal cast operation (the signed value is ever unaltered). In a 64
bit platform no cast will be made.
References (pointers) have a platform dependent variable size
automatically: a Java native reference in a Java user defined native capable
structure, or C++ class etc (e.g. a field with a native capable data type or
“can be native” reference) occupies 32 bits in a 32 bit platform and 64 bits in
a 64 bit platform.
Another
facility is to use the Java long data type as a cross-platform data type in a native perspective.
A
cross-platform Java long occupies in the native memory 32 bits in a 32 bit platform and 64 bits
in a 64 bit platform. A cross-platform long works as the C/C++ long and pointers types, using the cross-platform
long the 64 bits
and 32 bits Java versions are the same, only one API.
To declare
a cross-platform long must be used the XML attribute “address”, if set to “true” the long native size is platform dependent
(the size of addresses of the platform). Arrays, Long, NativeLong and NativeLongObject data types can contain a long cross-platform, declaring the long component as address with the
optional <component>
child element (see Primitive
wrapper types and Primitive
object types).
To declare
a cross-platform long using a programmatic manner use the NativeTypeManager.decAddress() method.
Examples:
public class MyStructure
{
. . .
protected long crossPlatLong;
protected long[]
crossPlatLongArray = new long[]{1,2};
protected Long crossPlatLongObject = new Long();
protected NativeLong crossPlatNativeLong;
protected NativeLongObject
crossPlatNativeLongObject;
public MyStructure()
{
. . .
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
this.crossPlatNativeLong = (NativeLong)typeMgr.decAddress().
decObjectWrapper(NativeLong.class).newValue();
this.crossPlatNativeLongObject = (NativeLongObject)typeMgr.decAddress().
decObjectWrapper(NativeLongObject.class).newValue();
}
...
}
XML
enhancer descriptor:
. . .
<field name="crossPlatLong"
address="true"
/>
<field name="crossPlatLongArray"
>
<component
address="true"
/>
</field>
<field name="crossPlatLongObject"
>
<component
address="true"
/>
</field>
<field name="crossPlatNativeLong"
>
<component
address="true"
/>
</field>
<field name="crossPlatNativeLongObject"
>
<component
address="true"
/>
</field>
.
. .
In a 32 bit
platform the size of the crossPlatLong attribute is 32 bits, the crossPlatLongArray reference points to an array
containing 32 bit integers and crossPlatLongObject, crossPlatNativeLong and crossPlatNativeLongObject references point to 32 bit integers.
Java primitive
types (boolean, byte, char, short, int, long, float, double) have a default native type, the
native type can be seen as the JNI type (jboolean, jbyte, jchar, jshort, jint, jlong, jfloat, jdouble). Java ensures fixed memory sizes to
the primitive types in the Java side, by default native counterparts in JNIEasy
have the same sizes (and alignments), but this can be changed on user demand.
Custom
sizes help the developer to program in Java with more fidelity to the native
side. Example: the native counterpart to the Java char is not the C char, it is short or wchar_t (with 2 bytes) and vice versa, the
C char counterpart
is the Java byte. The Java byte conventions are not the same as the Java char, if contained data is a character
the byte data type
is not the best match. Another example is Java boolean (1 byte) and BOOLEAN Win32’s macro (both types
conceptually match but the size does not).
JNIEasy
lets to change the default size and alignment of the native primitive type
layout of a Java primitive data type globally or with a per-element basis (usually
primitive fields or method parameters).
The methods
NativeTypeManager.setDefaultXSize(long size, long alignment), where X is Boolean, Char, Int etc, can be used to define the
default size and alignment of primitive types globally. To set the desired
default values before any native memory access is highly recommended. Usually
the alignment is the same as the memory size, the methods NativeTypeManager.setDefaultXSize(long
size) set the alignment
to the memory size passed as argument.
Example:
JNIEasy. get().getTypeManager().setDefaultCharSize(1,1);
sets the
default size and alignment of Java char in native as 1 (like the C char).
To know the
valid values see the javadoc documentation of setDefaultXSize(long,long) methods.
NativeBuffer and NativeBufferIterator methods are not affected by the primitive default
sizes, the native sizes are ever the Java sizes.
Any
primitive type element can be declared with a custom native memory size and/or
alignment with the XML attributes memSize and prefAlignSize.
Example:
public class MyStructure
{
...
protected char cChar;
...
}
and XML enhancer
descriptor:
. . .
<field name="cChar"
memSize="1"
/>
.
. .
Previous
XML code declares cChar with native memory size 1 byte and alignment 1 byte (if memory size is
specified and alignment is not, the alignment is set to the memory size).
“Verification”
with Java:
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
StructureDescriptor strucDesc =
(StructureDescriptor)typeMgr.getClassDescriptor(MyStructure.class);
NativeFieldDescriptor fieldDesc = strucDesc.getField("cChar");
TypeNative fieldType = fieldDesc.getVarType().getType();
System.out.println("Must be 1: " + fieldType.size());
System.out.println("Must
be 1: " + fieldType.preferredAlignSize());
Using a
programmatic approach the method NativeTypeManager.decPrimitive(Class
clasz,long size, long alignment) declares a primitive native type
with the memory size and alignment specified (using the method NativeTypeManager.decPrimitive(Class
clasz,long size) the
alignment is set as the specified size).
Size 0 is a
valid native memory size and alignment of primitive types. A primitive field
with native memory size 0 is a phantom
field; a phantom field has no native memory layout influence (alignment
must be set to 0 too). They are interesting when a specific platform declares a
field not declared in another platform using C macros. This feature is only
useful used in conjunction with the JNIEasy’s macro system.
The C macro
system is “a blessing” in the C/C++ world because it enables the developer to
face the incompatibilities of platforms, compilers, library versions …
Java has
not a C macro like system because the ClassLoader mechanism and the WORA principle (the JVM
works the same on every platform) are the alternatives. But in JNIEasy these
features are not sufficient because JNIEasy needs to deal with native elements,
a native element can be different between platforms. To face this problem
JNIEasy introduces an C “inspired” macro system: a name or size can vary
between platforms, the used name or size is resolved on runtime depending on
declared macros, defined or not, in runtime.
1.
DLL
names: a dynamic library name can change between platforms. Example: MSVCRT.DLL (lib name, MSVCRT) is the C runtime
library on Windows, libc.so.6 on Linux, libc.so.1 on Solaris and libc.dylib
on Mac OS X.
2.
Exported
symbols/names of methods and fields: the name mangling is different between
compilers, it affects to C++ exported elements and standard call C methods
(MSVC add a @N suffix).
3.
Size
(and alignment) of primitive data types: primitive data types (and derivatives)
can have different size between platforms (and compilers), for instance the long data type is 4 bytes in gcc Linux/Mac/Solaris
x86 and 8 bytes in x86_64, the LONG_PTR has variable size too in Windows-MSVC.
JNIEasy
provides a macro name/value registry with methods like NativeTypeManager.defineMacro(String,Object), NativeTypeManager.getMacro(String) etc. These macros are used to
resolve expressions where the selected value is obtained if a concrete macro
was declared. The approach is similar to the #ifdef MACRO or #if
defined(MACRO) C/C++ preprocessor instructions.
Three
methods can be used to parse macro based expressions:
1. NativeTypeManager.parseTextWithMacros(String textExpr)
Returns the text prefixed with a previously
defined macro (see the javadoc for a full syntax description).
Example:
"WIN32:MSVCRT;Linux:/lib/libc.so.6;Mac:/usr/lib/libc.dylib;SunOS:/lib/libc.so.1;OtherLibc"
The name "MSVCRT" is returned if the "WIN32" macro was defined, else "/lib/libc.so.6" if the "Linux" macro was defined, else “/usr/lib/libc.dylib" if the "Mac" macro was defined else "/lib/libc.so.1" if the "SunOS" macro was defined else "OtherLibc".
2. NativeTypeManager.parseNameWithMacros(String nameExpr)
Is used to resolve names prefixed with macros,
is based on parseTextWithMacros (basically trims the leading and trailing spaces).
3. NativeTypeManager.parseMemorySizeWithMacros(String
sizeExpr)
Returns the size prefixed with a previously
defined macro (see the javadoc for a full syntax description).
Example: "MSC:4;gcc:8;REGISTER_SIZE" the size returned is 4 if the "MSC" macro was defined, else is 8 if the
"gcc"
macro was defined, else the value of the "REGISTER_SIZE" macro is used (must be defined with
an integer value or an exception is thrown).
Several
JNIEasy methods already support directly this macro based syntax like DLLManager.get(String)/find(String)/remove(String), DynamicLibrary.getAddress(String)/addDLLBehavior(String,…)/
findBehaviorsByName(String)/findBehaviorByName(String,…)/ removeBehaviorsByName(String)/removeBehaviorByName(String,…), JNIEasyLibrary.exportBehavior(String,…), NativeTypeManager.decPrimitive(Class,String,String)/
decPrimitive(Class,String) / decString(Class,String)/ decStringBuffer(Class,String).
XML
attributes (enhancer and code generation) support this syntax too:
·
libraryPath : parsed with NativeTypeManager.parseNameWithMacros(String)
·
nativeName : parsed with NativeTypeManager.parseNameWithMacros(String)
·
encoding : parsed with NativeTypeManager.parseNameWithMacros(String)
·
memSize : parsed with NativeTypeManager.parseMemorySizeWithMacros(String)
·
prefAlignSize : parsed with NativeTypeManager.parseMemorySizeWithMacros(String)
·
alignSize : parsed with NativeTypeManager.parseMemorySizeWithMacros(String)
Example:
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
String osname = System.getProperty("os.name");
if (osname.startsWith("Windows"))
typeMgr.defineMacro("Windows");
else
if (osname.startsWith("Linux"))
typeMgr.defineMacro("Linux");
else
if (osname.startsWith("Mac
OS X"))
typeMgr.defineMacro("MacOSX");
else
if (osname.startsWith("SunOS"))
typeMgr.defineMacro("SunOS");
String dllName = "Windows:MSVCRT;Linux:/lib/libc.so.6;MacOSX:/usr/lib/libc.dylib;SunOS:/lib/libc.so.1";
DLLManager dllMgr = JNIEasy.get().getDLLManager();
DynamicLibrary dll = dllMgr.get(dllName);
CMethod method = dll.addCMethod("abs",int.class,
new Object[]{int.class},CallConv.C_CALL);
int res = method.callInt(new Object[]{ new Integer(-10)
});
System.out.println("Must
be true: " + (res == 10));
JNIEasy
supports, optionally, transactional manipulations of native memory in a similar fashion as JDO or
Hibernate. In native transactions the native memory must be seen as a data
repository; during a native transaction the original state of the native memory
is saved before any modification and is restored to the original values if the
transaction is aborted (rollback).
A native
transaction has boundaries (begin and end) and is thread isolated (transactions
are not shared by threads). The thread isolation is the only one isolation
offered by a native transaction, the same native memory fragment can be
modified concurrently by two threads with one or two live transactions (JNIEasy
avoids the concurrent modification but there is no thread lock between
transactions); every transaction keeps track of the memory modifications
performed by the associated thread only. If the user is going to modify the
same native instance concurrently in a transactional environment is recommended
to make a global lock during every transaction.
Native
transactions only work keeping the state of fields from native capable classes;
direct native memory manipulations with NativeBuffer and native modifications performed
in the native side are not monitored and are not transactional.
Rollback
behavior
a)
The
native instance was made native inside the transaction: the allocated memory is
freed.
b)
The
native instance was modified inside the transaction: the modified native memory
is returned to the previous state to the modification.
c)
The
native instance was freed (non-native) inside the transaction: the instance is
made native again and the values of the native memory are restored.
The
transaction object implementing the interface NativeTransaction, associated to the current
(calling) thread can be obtained with the method:
NativeManager.currentTransaction()
A
transaction begins with the method NativeTransaction.begin() and ends with the methods NativeTransaction.commit() and NativeTransaction.rollback().
Example:
NativeManager nativeMgr = JNIEasy.get().getNativeManager();
MyStructure structUpdatedInside = new MyStructure();
structUpdatedInside.setIntArray4(new int[]{1,2});
nativeMgr.makeNative(structUpdatedInside);
MyStructure structNewInside = new MyStructure();
structNewInside.setIntArray4(new int[]{1,2});
MyStructure structFreedInside = new MyStructure();
nativeMgr.makeNative(structFreedInside);
structFreedInside.setIntArray4(new int[]{1,2});
NativeTransaction txn = nativeMgr.currentTransaction();
txn.begin();
nativeMgr.makeNative(structNewInside);
structNewInside.setIntArray4(new int[]{3,4});
nativeMgr.free(structFreedInside);
structFreedInside.setIntArray4(new int[]{3,4});
structUpdatedInside.setIntArray4(new int[]{3,4});
txn.rollback();
boolean res;
int[] array;
res = NativeCapableUtil.isNative(structNewInside);
System.out.println("Must be false: " +
res);
array = structNewInside.getIntArray4();
System.out.println("Must be 1: " + array[0]);
res = NativeCapableUtil.isNative(structFreedInside);
System.out.println("Must be true: " + res);
array = structFreedInside.getIntArray4();
System.out.println("Must be 1: " + array[0]);
array = structUpdatedInside.getIntArray4();
System.out.println("Must be 1: " + array[0]);
JNIEasy
provides an event notification system monitoring the lifecycle of native
instances.
The user
can register listeners to receive these events; a listener is an instance of a
class implementing a listener interface and registered, if necessary, as
listener in JNIEasy.
There are
two types of listener interfaces:
The native
instance changing its native state receives the matched event according the
interface(s) implemented; the instance works itself as a listener and does not
need to register. The interface names have the following pattern: EventTypeCallback.
|
Interface |
Related NativeManager method |
Methods |
Notes |
|
AttachCallback |
attach(Object,NativeAddress,long) |
Pre Post |
Is called pre and
post the attachment. The “pre” method is not enhanced. |
|
AttachCopyCallback |
attachCopy(Object,Object) |
Pre Post |
Is called pre and
post copy to native instance a non-native instance (copy) |
|
DetachCallback |
detach(Object,int,boolean) |
Pre Post |
Is called pre and
post detachment the native instance. The “post” method is not enhanced. |
|
DetachCopyCallback |
detachCopy(Object) |
Pre Post |
Is called pre and
post when creating a detached clone of the native instance. |
|
FetchCallback |
fetch(Object,int) |
Post |
Is called post
fetching the instance from native memory. This method is not enhanced, useful
to synchronize non-native fields with native fields. |
|
MakeNativeCallback |
makeNative(Object) makeNative(Object,NativeAddress,long) |
Pre (two modes) Post |
Is called pre and
post made native (writing the native memory). The “pre” methods are not
enhanced, are useful to prepare native fields before to write to native
memory |
|
UnFetchCallback |
unFetch(Object,int) |
Pre |
Is called pre
“unfetching” the instance to native memory. This method is not enhanced,
useful to prepare native fields before to write to native memory. |
Example:
modifying MyStructure
public class MyStructure implements MakeNativeCallback
{
public void jnieasyPreMakeNative(NativeAddress nativeAddress,long offset)
{
System.out.println("PreMakeNative
(attached)");
System.out.println("Must
be false: " +
NativeCapableUtil.isNative(this));
}
public void jnieasyPreMakeNative()
{
System.out.println("PreMakeNative
(memory owner)");
System.out.println("Must
be false: " +
NativeCapableUtil.isNative(this));
}
public void jnieasyPostMakeNative()
{
System.out.println("PostMakeNative");
System.out.println("Must
be true: " +
NativeCapableUtil.isNative(this));
}
}
NativeManager natMgr =
JNIEasy.get().getNativeManager();
MyStructure obj = new
MyStructure();
natMgr.makeNative(obj);
// Calls jnieasyPreMakeNative()
// and
jnieasyPostMakeNative()
NativeBuffer address =
NativeCapableUtil.getBuffer(obj);
MyStructure obj2 = new
MyStructure();
natMgr.makeNative(obj2,address,0);
// Calls
jnieasyPreMakeNative(Address,long)
// and jnieasyPostMakeNative()
Instances
of these classes must be registered in JNIEasy to receive events. These are the
InstanceLifecycleListener based interfaces; listener methods will receive InstanceLifecycleEvent events. The listener interfaces are
symmetric to the native instance interfaces.
A class
working as a listener must implement only one InstanceLifecycleListener
interface.
|
Interface |
Related symmetric instance interface |
Event class |
|
AttachLifecycleListener |
AttachCallback |
AttachLifecycleEvent |
|
AttachCopyLifecycleListener |
AttachCopyCallback |
AttachCopyLifecycleEvent |
|
DetachLifecycleListener |
DetachCallback |
DetachLifecycleEvent |
|
DetachCopyLifecycleListener |
DetachCopyCallback |
DetachCopyLifecycleEvent |
|
FetchLifecycleListener |
FetchCallback |
FetchLifecycleEvent |
|
MakeNativeLifecycleListener |
MakeNativeCallback |
MakeNativeLifecycleEvent |
|
UnFetchLifecycleListener |
UnFetchCallback |
UnFetchLifecycleEvent |
Example:
package examples.manual;
import com.innowhere.jnieasy.core.util.NativeCapableUtil;
import
com.innowhere.jnieasy.core.listener.MakeNativeLifecycleEvent;
import
com.innowhere.jnieasy.core.listener.MakeNativeLifecycleListener;
public class MakeNativeListenerExample
implements MakeNativeLifecycleListener
{
public MakeNativeListenerExample()
{
}
public void preMakeNative(MakeNativeLifecycleEvent event)
{
if (event.isAttaching())
System.out.println("PreMakeNative
(attached)");
else
System.out.println("PreMakeNative
(memory owner)");
System.out.println("Must
be false: " +
NativeCapableUtil.isNative(event.getSource()));
}
public void postMakeNative(MakeNativeLifecycleEvent event)
{
System.out.println("PostMakeNative");
System.out.println("Must
be true: " +
NativeCapableUtil.isNative(event.getSource()));
}
}
When
register a listener, the classes “interested” must be specified:
MakeNativeListenerExample listener = new MakeNativeListenerExample(); JNIEasy.get().getNativeManager().addInstanceLifecycleListener(listener,
new Class[]{MyStructure.class});
the
listener will receive “make native” events of MyStructure objects. Now the previous
“makeNative” examples can be executed again to “fire” events.
NativeManager natMgr = JNIEasy.get().getNativeManager();
MyStructure obj = new MyStructure();
natMgr.makeNative(obj); // Calls
preMakeNative() and postMakeNative()
NativeBuffer address = NativeCapableUtil.getBuffer(obj);
MyStructure obj2 = new MyStructure();
natMgr.makeNative(obj2,address,0);
// Calls
preMakeNative() and postMakeNative()
JNIEasy has
been designed to run in a multithread environment.
Key parts
of the JNIEasy framework are multithread safe, sometimes the multithread
support is achieved using thread locking but only when necessary, for instance,
native method calls and Java callback calls are not locked to achieve the
maximum parallelism.
There are
parts or uses of the framework not synchronized, consciously, for instance the NativeBuffer methods: concurrent modification of
the same native memory is a risky task; the developer must synchronize
concurrent access to shared native memory.
The
operations of synchronization between Java fields and native memory in native
classes are synchronized in a per native instance basis; Java fields can be
read/modified concurrently with no problem from a framework’s point of view,
but this is not recommended (specially concurrent modification) from an application’s
point of view, developer is encouraged to synchronize concurrent access to
shared native instances and native instances sharing the same native memory
(several native instances can be attached to the same native memory part).
Native
memory access performed from native side is in no way synchronized.
In brief,
no developer synchronization code is necessary if concurrent threads do not
share the same native memory parts.
Native
capable classes (user and predefined) are “serializable aware”.
Every
native capable class has two internal non-static fields: jnieasyType and jnieasyNativeStateManager, both fields are declared as
transient, and not serialized by default, this ensures the “native state” is
not serialized. Any other field holds data and must be serialized.
All
predefined native classes implement Serializable and Cloneable interfaces, user defined arrays and pointers
implement these interfaces too by default, user defined Java structures, C++
classes and unions do not, the developer can do it explicitly.
The
serialized map of a user defined class not enhanced and enhanced is the same
but the serial UID is different, to avoid this, a serialVersionUID field must be added, for instance:
private static final long serialVersionUID = 1L;
this
ensures that a serialized enhanced class instance can be deserialized as a not
enhanced class instance and vice versa.
The
serialization compatibility between enhanced and not enhanced classes is very
important to pass objects in a distributed environment, a peer can use the
enhanced version of the class and the other peer can use the non-enhanced
version.
A native
instance can be serialized and distributed, of course the deserialized instance
is non-native, and can be again made native if the peer works with JNIEasy
(using the attach and makeNative methods). There is a problem:
serialization uses Java reflection, Java reflection is not controlled by the
enhancer, if an instance is going to be serialized and the serialized instance
must be a copy of the current native memory state, a deep “unfetch” must be
performed before the serialization process.
Another
option is to serialize non-native instances; of course a native instance can be
freed or detached, but to keep the native object tree untouched there are two NativeManager methods to use a detached cloned
object tree instead the “live” objects:
1) Object
detachCopy(Object obj)
Creates a “deep” detached clone of the native
object argument and tree (native fields). The native relations are cloned too (the
detached tree is independent from the original) including circular
relationships (direct and indirect).
2) void
attachCopy(Object nativeObj,Object detachedObj)
Copy the content of a non-native object to a
native object (both objects must have the same type). The non-native object is
kept as is, but member native capable objects are made native and member of the
native object.
Using these
methods an entire native tree can be cloned, serialized, distributed,
unserialized and copied to a symmetric native tree.
Example:
public class MyStructure
implements MakeNativeCallback,Serializable,Cloneable
{
...
}
MyStructure struct = new MyStructure();
JNIEasy.get().getNativeManager().makeNative(struct);
MyStructure struct2 = new MyStructure();
JNIEasy.get().getNativeManager().makeNative(struct2);
MyStructure structCopy =
(MyStructure)JNIEasy.get().getNativeManager().detachCopy(struct);
ByteArrayOutputStream repOut = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(repOut);
oos.writeObject(struct);
oos.close();
ByteArrayInputStream repIn =
new ByteArrayInputStream(repOut.toByteArray());
ObjectInputStream ois = new ObjectInputStream(repIn);
MyStructure structCopy2 = (MyStructure)ois.readObject();
ois.close();
System.out.println("Must be true: " +
(struct2.getIntArray3()
!= null));
structCopy2.setIntArray3(null);
JNIEasy.get().getNativeManager().attachCopy(struct2,structCopy2);
System.out.println("Must
be null: " + struct2.getIntArray3());
The XML
descriptor structure of enhancer and Java code generation are specified in the
XMLSchema format.
Due to
performance reasons, JNIEasy does not validate a XML file against its schema, wherefore
is not necessary to declare the XMLSchema in the XML header, but is recommended
in development time to avoid mistakes. Many tools like XML tools and Java IDEs
validate XML files against its XMLSchema.
The XMLSchema
specification has limitations, this chapter explains the precise information
missing in the XMLSchemas and a functional meaning of elements and attributes.
The XML
enhancer descriptor structure is specified in the XMLSchema archive:
JNIEasy.enh.xsd
A header
example:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyStructure.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1"
xmlns="http://www.innowhere.com/jnieasy/enhancer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.innowhere.com/jnieasy/enhancer
../../../schemas/JNIEasy.enh.xsd">
<package name="examples.manual">
<imports />
<class name="MyStructure" type="structure">
...
The root
element.
·
version attribute : is mandatory, current version is 1.1.
Used to
include other XML files. Useful to enhance classes from command line in “batch”
mode.
·
file attribute : specifies the file to include, can be specified as complete
URL or a relative file path. Is required.
Declares
the package of the contained classes (usually one class).
Attributes:
·
name: the format is the same as the Java package declaration. Is required.
Child
nodes: <imports>
and <class>.
Parent element
of <import>
declarations.
Declares a
class pattern to resolve relative class names used in the following <class> elements.
Attributes:
·
class: the format is the same as the Java import declaration. Is required
The "java.lang.*" import is implicitly declared
following the Java rule; for instance, the "String" class name will be resolved as "java.lang.String".
The package
name is used too as a prefix to resolve class names without package (simple
class names).
Declares a user
defined native capable class to be enhanced.
Attributes:
·
name: declares the “simple” name of the class. The format is the same as the
name of a Java class declaration. Is required.
·
type: declares the native element this Java class represents. Possible
values are: structure, class, union, array, pointer or callback. Is required.
·
allFields: tells to the enhancer if all fields must be or not must be enhanced,
if true the
enhancer tries to enhance all native capable non-transient member fields
although the field is not declared in the XML. Is optional, the default value
is true.
·
allMethods: tells to the enhancer if all methods must be or not must be enhanced,
if true the
enhancer tries to enhance all native capable methods although the method is not
declared in the XML. It is only applied to “multiple field containers” like
classes working as structures, C++ classes or unions. Is optional, the default
value is false.
·
libraryPath: specifies the name or path of the Dynamic Link Library to be used in
this class, if some method is declared as a proxy of a native method. It is
only applied to “multiple field container classes” (structures, C++ classes or
unions). The library path/name follows the conventions of System.loadLibrary(String) and can be cross-platform using macros, the full format
is explained in the javadoc of DLLManager.get(String).
·
alignSize: specifies the alignment size to be applied to this class. Is optional.
Allows a cross-platform syntax using macros.
Child
nodes: <field>, <constructor>, <method>, <fieldMethod>.
Declares a
field as native and describes its native representation.
Attributes:
·
name: the name of the field to be native. Is required.
·
enhance: specifies if the field must be native or keep as non-native. The main
use is to exclude (value false) a file to be enhanced. Is optional, the default value is true.
·
alignSize: specifies the alignment size to be applied to this field. Is optional
and only is applied to fields of “multiple field container classes”
(structures, C++ classes or unions). Allows a cross-platform syntax using
macros.
·
union: specifies the begin and end of a group of fields forming an anonymous
union. Values begin and end. Is optional and only is applied to fields of “multiple field container
classes” (structures, C++ classes or unions).
·
Native
variable type attributes: declares the native representation of the field type.
Seen later in “Native variable type attributes and
nodes”.
Child
nodes: nodes corresponding to the native variable type. Seen later.
Declares a
constructor as native and describes its native representation.
Attributes:
·
enhance: idem <field>. If false the <param> child nodes and its class attributes specify the concrete constructor.
·
useReflection: if true the constructor working as a callback is called using Java reflection,
else is called with a direct call. Is optional, by default is false. Only is processed this attribute
if onLibrary is not
defined or false (the
constructor works as a callback).
·
exportMethod: if true the constructor is exported and found using the C method findExportedMethodAddress(const
char*). Is optional, by
default is false.
·
onLibrary: if true the constructor is a proxy of a “native constructor”. Is optional, by
default is false.
·
nativeName: specifies the exported name in the DLL to link the constructor working
as a proxy. The name must be the exported name or a cross-platform name, the
full format is explained in the javadoc of the NativeTypeManager.parseNameWithMacros(String)
method. Is ignored if onLibrary is not defined or false else is required.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child
nodes: <param>
nodes specify the concrete constructor and native representation of parameters.
Seen later.
Declares a
method as native and describes its native representation.
Attributes:
·
name: the name of the method to be native. Is required.
·
enhance: idem <constructor>.
·
useReflection: idem <constructor>.
·
exportMethod: idem <constructor>.
·
onLibrary: idem <constructor>.
·
nativeName: idem <constructor>, but in this case is optional, if not defined
the native name used is the method name.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child
nodes: <return>
and <params>
nodes specify the concrete method and native representation of return type and parameters.
Seen later.
Declares a
field seen as a native method (a field-method) and describes its native
representation.
Attributes:
·
name: the name of the field to be native as a field-method. Is required.
·
enhance: idem <constructor>.
·
useReflection: idem <constructor>.
·
exportMethod: idem <constructor>.
·
onLibrary: idem <constructor>.
·
nativeName: idem <method>, but in this case the native name is the
exported name of the field in the DLL, if not defined the native name used is
the field name.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child node:
<fieldType>
node specify the native declaration of the return and “value” parameter of the
field-method. Seen later.
The method
based (behaviors) elements: <constructor>, <method>, <fieldMethod> and native signature declarations can
use the following attributes:
·
callConv: specifies the native call convention used to call this method from
native. Permited values are std_call and c_call. Is optional, by default is std_call.
·
thisClass: specifies the “container” class of the method. It is required in user
native capable callbacks to specify the “real” class containing the method if
different to the user native capable callback class. In native signatures
specifies the container class in constructors, instance methods and instance
fields.
·
decSignature: declares the native representation of the method signature with a
string expression, the format of the expression is explained in the javadoc of
the method NativeSignatureManager.decBehavior(String,String[]). This attribute is an alternative native
signature declaration to use the callConv, thisClass attributes and the child nodes <params>, <param>, <return> and <fieldType>, these can be omitted.
·
params:
declares the list of classes of the constructor (a comma separated list of
class names), method or field method to identify which one is declared. This
attribute is an alternative to <params>, the default native representation of every
declared Java data type is used. Elements <return> and <params> can be avoided, anyway <return> may be used to specify the native
representation of the return data type beyond the default.
Declares
the native variable type of a method return and can be used in methods (not
constructors, or field-methods). The attributes and child nodes are described
later in “Native variable type attributes and
nodes”. If
omitted the native variable type is the default of the Java type or the
specified in the decSignature attribute.
Is the node
container of <param> nodes describing the native signature of a method (constructors do not
need this container node). The <params> node follows the <return> node.
Optionally
the params attribute, with a comma separated
list of Java class names as value, may be used to avoid the <param> nodes. The default native
representation of every declared Java data type is used.
Declares
the native variable type of a method parameter and can be used in methods and
constructors (not field-methods). If omitted the native variable type is the
default of the Java type or the specified in the decSignature attribute.
·
varargs: specifies if the parameter is a variable number of arguments
parameter. The parameter data type must be a Java array (Object[], String[], NativeString[], char[] etc). A varargs attribute mimics in
Java the C “…” parameter convention. Without this attribute set to true, the
default convention of the array parameter type is to pass a pointer-to-array to
the method, with the varargs attribute set to true all array elements are passed as parameters to
the method with their “intrinsic/default” native type. In this context the
default native type of Java primitive wrappers like Integer, Character, Double etc, is int, char, double etc (in other contexts the default
native type of Integer is int* and so on).
Is optional, by default is false.
Other
attributes and child nodes are described later in “Native variable type attributes and
nodes”. The name attribute can be specified but is
ignored by the enhancer.
Declares
the native variable type of the field type of a field-method, this declaration
is used to the return and “value” parameter of the field-method. Can be used
only in field-methods. The attributes and child nodes are described later in “Native variable type attributes and
nodes”. If
omitted the native variable type is the default of the Java type or the
specified in the decSignature attribute.
The nodes: <field>, <return>, <param>, <fieldType>, <component> and <pointer> can specify the native variable
type description of the Java type involved (the type of the field, method return,
parameter, field in a field-method), the fields and child nodes describing the
native variable type are specified in the same way.
There are
attributes shared by all types and attributes and child nodes related to a
specific Java type or type group.
Common
attributes:
·
varConv: specifies if the native type is declared “by value” or “by pointer”.
Permited values are byValue, byPtr and byDefault. Is optional, the default value is
the default convention of the native type.
·
class: specifies the Java class of the native type, the value must be a class
name (ex. java.lang.String)
or a simple class name (String); if used a simple class name, the declared imports and the declared
package are used to resolve the class name. Is optional if the Java type can be
deduced, for instance in the <field> node the class is the field’s Java type; but with <param> is needed to resolve the concrete
Java method or to specify the parameter type of a unknown method signature
being declared; with <component> or <fieldType> or <pointer> can be used to complete the
declaration of a native generic interface or “can be native” type like NativeCapableArray or java.lang.reflect.Field (or NativeFieldMethodReflection) or NativePointer respectively.
Example:
private NativePointer ptrToPtr;
The XML descriptor:
<field name="ptrToPtr2" >
<pointer class="MyStructure" />
</field>
The class attribute in this case complete the native
type declaration of NativePointer, the field ptrToPtr only points to NativePointer objects pointing (the internal pointer) to MyStructure objects.
·
dec: declares the native variable type using a string expression, the
format of the expression is explained in the javadoc of the method NativeVarTypeManager.dec(String,String[]). This attribute is an alternative
declaration to use varConv, class attributes
and the native type specific child nodes, these can be omitted. A special case
is the "…"
syntax only useful declaring a parameter type (<param> node): dec="NativeType…" declares a NativeType Java array (NativeType[]) as a varargs parameter; examples: "Object…", "String…", "NativeString…", "int…" etc, more info see the varargs attribute of Element: param
String
based types like String, StringBuffer, NativeString and NativeStringBuffer can specify the encoding/size of
the string with the attribute:
·
encoding: declares the encoding/size of the string. Values are ansi and unicode. Supports a macro base expression. Is
optional, default value is ansi.
The native
view of primitive types is self described: the native memory size is by default
the memory used by Java. This can be changed in a per-declaration basis.
·
memSize: declares the native memory size, valid values are specified in the
javadoc of the specific method setDefaultXSize(long,long) where X is Boolean, Integer etc. Is optional, default value is the
Java size of the primitive type. Allows a cross-platform syntax using macros.
·
prefAlignSize: declares the native memory alignment size, valid values are specified
in the javadoc of the specific method setDefaultXSize(long,long) where X is Boolean, Integer etc. Is optional, the default value
is the value specified with the memSize attribute if declared, else the Java size of
the primitive type. Allows a cross-platform syntax using macros.
If long the address attribute can be used too.
The long type can be declared cross-platform
with the attribute:
·
address: if true declares the long as cross-platform, the memory size (and alignment) is the platform’s
address size (4 bytes in a 32 bits or 8 bytes in a 64 bits platform). Is
optional, default value is false.
Native
instances of types like NativeBoolean, NativeInteger
etc contains a primitive value, a reference can be seen as a primitive pointer
(int*, byte* etc). The default declaration of
the contained primitive can be modified using the optional child element <component>:
·
<component>: is used to redefine the default declaration
of the contained primitive type (see Primitive
types). Is optional, if not declared the primitive
declaration is the default.
Example: a field pointing to a char with 1 byte
size
private NativeCharacter ptrChar = …;
<field name="ptrChar" >
<component memSize="1" />
</field>
Native
instances of types like Boolean, NativeBooleanObject, Integer, NativeIntegerObject etc contains a primitive value, a
reference can be seen as a primitive pointer (int*, byte* etc). The default declaration of
the contained primitive can be modified using the optional child element <component>:
·
<component>: is used to redefine the default declaration
of the contained primitive type (see Primitive
types). Is optional, if not declared the primitive
declaration is the default.
Example: a field pointing to a char with 1 byte
size
private Character ptrChar = …;
<field name="ptrChar" >
<component memSize="1" />
</field>
Custom XML elements
applied to array types (Java arrays and NativeArray derived, predefined or user
defined):
Attribute:
·
length: specifies the fixed length of the array, if -1 is undefined. Is
optional, default value is -1 (undefined).
Element:
·
<component>: specifies the native variable type of the
array component. The native variable type can be declared using the attributes
and child nodes defined in “Native variable type attributes and
nodes”. Is
optional, if not defined the native variable type by default of the component
is used. In multidimensional arrays the <component> node can have new <component> childs.
Example: a 3 dimension array field by value
with lengths 5, 10, 15
private int[][][] array = new int[5][10][15];
<field name="array" length="5" varConv=”byValue”>
<component length="10" >
<component
length="15"
/>
</component>
</field>
Custom XML
elements applied to NativePointer based types (predefined or user defined):
Element:
·
<pointer>: specifies the native variable type of the
pointer contained. The native variable type can be declared using the
attributes and child nodes defined in “Native variable type attributes and
nodes”. Is
optional, if not defined the native variable type by default of the pointer contained
is used.
Behavior
based native variable types like java.lang.reflect.Constructor, java.lang.reflect.Method, java.lang.reflect.Field (field-methods), and NativeBehavior derived classes (predefined and
user defined) need a native behavior signature (how the method is seen from
native side).
Attributes:
same as defined in “Common
native behavior attributes”
Constructor
child nodes: a sequence of <param> nodes defining the constructor parameters. See “Element: param”.
Method
child nodes: a sequence of <return> and <params> defining the return and method parameters. See
“Element: return” and “Element:
params”.
Field-method
child node: the element <fieldType> defining the return a “value” parameter type
of the field-method. See “Element:
fieldType”.
The XML
Java code generation descriptor structure is specified in the XMLSchema
archive:
JNIEasy.jgen.xsd
A header
example:
<?xml version="1.0" encoding="UTF-8"?>
<jniEasyJavaCodeGen version="1.1"
xmlns="http://www.innowhere.com/jnieasy/jgen"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.innowhere.com/jnieasy/jgen
../../../schemas/JNIEasy.jgen.xsd">
<fileGen name="MyCPPClassOnDLLGen">
...
The root
element.
·
version attribute : is mandatory, current version is 1.1.
Used to
include other XML files. Useful to generate classes from command line in
“batch” mode. Identical to the enhancer element.
Declares
the file to be generated.
Attributes:
·
name: the file name. The .java extension will be added to this name. Is
required.
Contains a <package> child node.
Declares
the package of the contained classes (usually one class).
Attributes:
·
name: the format is the same as the Java package declaration. Is required.
Child nodes:
<imports>
and <class>.
Parent
element of <import>
declarations. Identical to the enhancer element.
Identical
to the enhancer element.
Declares the
class to be generated.
Attributes:
·
name: declares the “simple” name of the class. The format is the same as the
name of a Java class declaration. Is required.
·
libraryPath: specifies the name or path of the Dynamic Link Library to be used by
the generated class methods.
·
extends: specifies the class to inherit. The format is the same as the name of
a Java extends
declaration. Is optional.
·
implements: specifies the interfaces to be implemented. The format is the same as
the Java implements
declaration. Is optional.
Child
nodes: <freeCode>,
<field>, <constructor>, <method>, <fieldMethod>.
Optional
element to add custom code to the generated class.
Example:
<freeCode><![CDATA[
public static
int[] array = new int[10];
static
{
for(int i =
0; i < array.length; i++) array[i] = i;
}
]]></freeCode>
Declares a
field to be generated.
Attributes:
·
name: the name of the field to be native. Is required.
·
class: specifies the Java class of the field type. Is required.
Declares a
constructor to be generated.
Attributes:
·
nativeName: specifies the exported name in the DLL to link the constructor. Is
required.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child
nodes: <param>
nodes specify the constructor parameters and their native variable type. Identical
to enhancer (with exception of parameter names, see later).
Declares a
method to be generated.
Attributes:
·
name: the name of the method. Is required.
·
nativeName: idem <constructor>, but in this case is optional, if not defined
the native name used is the method name.
·
methodType: specify if the method is static (C) or instance (C++).Valid values: C and CPP. Is required.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child
nodes: <return>
and <params>
nodes specify the concrete method and native representation of return type and
parameters. Identical to enhancer (with exception of parameter names, see
later).
Declares a
field-method to be generated.
Attributes:
·
name: the name of the method. Is required.
·
nativeName: idem <method>, but in this case the native name is the
exported name of the field in the DLL, if not defined the native name used is
the method name.
·
Common
native behavior attributes: seen in “Common
native behavior attributes”.
Child node:
<fieldType>
node specify the native declaration of the return and “value” parameter of the
field-method. Identical to enhancer.
The main
difference with the enhancer version is the name attribute.
·
name: specifies the name of the parameter. Is required.
Other
attributes and child nodes are the same as the enhancer version, see Element: param.
[1] To simplify the DLL, Dynamic
Link Library, acronym will be used to identify a Linux based shared object too.
[2] This class may be used to create a
native capable object implementing the interface. Examples:
NativeTypeManager typeMgr =
JNIEasy.get().getTypeManager();
TypeCanBeNativeCapable typeStr =
(TypeCanBeNativeCapable)typeMgr.dec(String.class);
NativeString strObj =
(NativeString)typeStr.wrapValue("any string");
TypeNativeObject typeInt =
(TypeNativeObject)typeMgr.dec(NativeInteger.class);
NativeInteger objInt =
(NativeInteger)typeInt.newValue();
[3] Sizes are calculated with the
default values, they can be changed, example: the Java long data type can be reflected as a 4
bytes integer (a C int), sizes of related data types like NativeLong, NativeLongObject, NativeLongArray etc can be affected.
[4] Inline methods (C and C++)
and C static methods (using the static keyword) are not exported by
default. A new visibility model have been introduced in gcc, more info: http://gcc.gnu.org/wiki/Visibility
and http://people.redhat.com/drepper/dsohowto.pdf
[5] This is the default native layout
of Java primitives (and derivatives), but this correspondence can be changed,
for instance: the Java char (2 bytes) can be reflected as a C char (1 byte), the Java long (8 bytes) can be reflected as a 32
bit C int etc. See Cross-platform options.
[6] Microsoft (Visual) C/C++
compiler
[7] All C++ examples of this
manual and included into the JNIEasy distribution compile with MSVC 6.0 and a
modern gcc (MingGW, cygwin, Linux, Mac and Solaris) compiler, nothing prevent
to compile with other compilers like Borland C/C++, Intel C/C++ or Sun Studio,
perhaps with minor modifications of JNIEasy.h (furthermore, native libraries are
language independent, you need to know how native data types are corresponded
with Java/JNI data types). The static linking with JNIEasy is only supported
with MSVC but can be avoided using JNIEasy.c or JNIEasyHelper.cpp provided files, static or dynamic
linking to JNIEasy DLL is not necessary if findExportedMethodAddress(const
char*) method is not
used from native.
[8] In a x86 unix gcc compiler
the MyCPPClassOnDLL C++ class must be compiled with the
flag -malign-double
because by default the alignment of double declared as a field is 4 bytes, this value is
the recommended default in old i386 machines, but this alignment is an
anachronism and is different to the double type size, 8 bytes (primitive sizes and
alignments usually share the same value), and incompatible with modern Win32
compilers (MSVC and Win32 gcc ports always align double with 8 bytes).
[9] The used native names in this
manual follow the C++ name mangling of MSVC compiler; in the Cross-platform options chapter we will see how to define a cross-platform
native name valid with gcc too. The examples contained into the distribution
are fully cross-platform.
[11]http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/undecoratesymbolname.asp
[12] The Class object existence indicates the
class is loaded but it may be not initialized
[13]http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/enumwindows.asp
[14] C/C++ typically use macros to
construct multiple native layouts of the same construction (class, structure …)
[15] In LP64 data models
[16] LP64 data model is used by gcc 64
bit compilers (long size is 8 bytes), MSVC uses a LLP64 model (long remains 4 bytes) but the LONG_PTR data type is platform dependent.
More info at: http://en.wikipedia.org/wiki/64-bit#64-bit_data_models
and http://www.amd.com/us-en/assets/content_type/DownloadableAssets/dwamd_AMD64_PortApp.pdf


@* * Copyright 2023 HM Revenue & Customs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *@ @import testOnly.models.UserModel @import views.html.layouts.unifiedLayout @import testOnly.views.html.helpers.{SummaryErrorHelper, InputHelper} @import uk.gov.hmrc.govukfrontend.views.html.components.FormWithCSRF @import views.html.components._ @this(unifiedLayout: unifiedLayout, form: FormWithCSRF, summaryErrorHelper: SummaryErrorHelper, inputHelper: InputHelper, continueButton: ContinueButton, h1: h1, h2: h2) @(userForm: Form[UserModel], postAction: Call, result: Option[String])(implicit request: Request[_], messages: Messages) @unifiedLayout(pageTitle = "ITVC Stub Users") { @summaryErrorHelper(userForm) @h1(msg = "Add User to Des Simulator") @result.map { data => @h2(msg = data) } @form(action = postAction) {