Eve Application Development

Michael L Brereton - 22 May 2008, http://www.ewesoft.com/

 

Advanced Guide - Contents

>> Next: Advanced ENI Topics

<< Prev: Remote Method Invocation

Eve Native Interface (ENI) – Version 2.0

Creating ENI Functions From Java Native Methods. 1

The Basic ENI Data Types. 2

Accessing the JavaVM – the java_vm_access (or EniVm) Class. 2

Accessing Java Data – The LiveVm Class. 3

Accessing Fields  3

Calling Methods and Constructors  5

Working With Strings. 7

Building Strings  7

Accessing Java Strings  8

EniVm Functions. 8

Encoded Java Types  9

Temporary References. 9

Working With Arrays. 10

ENI Java Synchronizing.. 13

 

The Eve Native Interface allows you to write a dynamic linked library (or shared library in Linux) that implement in C++ methods defined as native in Java classes. DLLs written using ENI have the following properties:

 

1.      An ENI DLL will work with a native Eve VM and a true Java VM (different builds are not required).

2.      The same source code used for Windows works under Linux except for OS or application specific calls your implementation makes.

 

The secret to producing successful ENI DLLs is simple: do as little Java work in the DLL. It is always best to avoid calling methods and creating Objects from ENI code as much as possible. This is partly to avoid extra complication, but mostly to make the code more maintainable. Changing a method or constructor signature in your Java code may cause incompatibility with your ENI code and your C++ compiler will not be able to alert you to this fact. (This is also true for changing field names).

 

If you can do what you need to do in Java before or after calling the native method, then you should do so. It is better to create simple re-usable data transfer objects in your Java code and then pass them to the native code rather than have the native code create and return the objects.

Creating ENI Functions From Java Native Methods

 

The EveMaker application that comes with the Eve SDK has a tool that produces stub ENI code for native methods in any Java class. For every method this tool produces two components of C++ code:

 

1.      An empty function with parameters matching the Java method that you fill in to implement the native method.

2.      A single link macro that links the ENI and JNI method call to the empty function.

 

For example a native method in a Class called eveeni.Test that looks like this:

 

 native String setTheValues(double dv, int av);

 

will produce this code:

 

/**
Implement this method.
 
eveeni.Test.setTheValues()
*/
//============================================================
ObjectReference eveeni_Test_setTheValues(
       EniVm & vm,ObjectReference thisObject,jdouble par1,jint par2)
//============================================================
{
 return vm.nullObject();
}
//------------------------------------------------------------
eve_eni_link(instance,ObjectReference,eveeni_Test_setTheValues,2,(jdouble,jint))
//------------------------------------------------------------
 

 

The only change you need to make is to fill in the eveeni_Test_setTheValues() method and return the correct ObjectReference. You do not need to change (and should not change) the eve_eni_link() code.

 

For any given method, the full name of the method will be of the form: <PackageNames>_<ClassName>_<MethodName>. The difference between a static method and an instance method is in the second parameter. An instance method has this form:

 

<Type> <FullMethodName>(EniVm &vm,ObjectReference thisObject[,par1,par2,…])

 

and a static method has this form:

 

<Type> <FullMethodName>(EniVm &vm,ClassReference thisClass[,par1,par2,…])

 

The parameters par1 and par2 represent the parameters as listed in the Java version of the method.

The Basic ENI Data Types

 

There are some data types used in ENI. These include:

 

ObjectReference – Used to reference a Java Object.

ClassReference – Used to reference a Java Class. Note that a ClassReference is also an ObjectReference.

 

These two references are C++ classes and you can call bool isNull() to determine if it is a null reference and void clear() to set the reference to null. Other than a null reference, there is no way to create these references explicitly. You can only use references provided by the ENI through parameters passed to the native method, Java field accesses or Java method/constructor calls.

 

JValue – This is a union that is capable of holding all Java data types. You will only need to use this type in advanced ENI topics covered in the next chapter.

 

Primitive data types include: jboolean, jbyte, jchar, jshort, jint, jlong, jfloat, jdouble. Note that jboolean does not equate to C++ bool, it is actually an 8-bit integer value that should be 0 to indicate false and 1 to indicate true.

 

Accessing the JavaVM – the java_vm_access (or EniVm) Class

 

The C++ class java_vm_access (which is typedef to be equivalent to EniVm) is an abstract class that is used to access all available VM functions. Everything that you need to do in ENI is available through this Class, similar to the JNIEnv class. However this class can be rather cumbersome to use and incorrectly using the methods can cause DLL failure, so this document instead will focus on the more advanced classes (like LiveVm and LiveReference) that make accessing the VM considerable easier and less error prone. However there are some functions in java_vm_access that are not provided by these classes and these are listed in later sections.

 

Accessing Java Data – The LiveVm Class

 

At runtime each native method is called via an ENI_xxx function call when called from a native Eve VM, and via a JNI_xxx function call when called by a Java VM. Each of these functions creates a concrete implementation of EniVm appropriate for the running VM, and then passes a reference to that Object to the main function used to implement the native method.

 

In order to access the Java VM you must create a LiveVm object. As long as that object is within scope the DLL can access the Java VM safely. Note that on a native Eve VM, this also means that other Java Threads are suspended until the LiveVm is destroyed (goes out of scope). Here is how you might use it:

 

ObjectReference eveeni_Test_setTheValues
    (EniVm &vm,ObjectReference thisObject,jdouble par1,jint par2)
{
// Here I do work not related to the Java VM, possibly access native data.
    callNativeFunction();
// When I want to access the VM, I set up a new scope.
    {           
          LiveVm java(vm);
          // Within this scope I can access the VM.
    }
    // The previous LiveVm object is out of scope here,
    // so I can only access native data again.
    callMoreNativeFunctions();
    // When I’m ready to access the VM again I can do it in the same way.
    {           
          LiveVm java(vm);
          // Within this scope I can access the VM.
    }
    return aCreatedValue;   
}

 

With a LiveVm in scope you can now access Java class and instance methods and fields. To do this you create a LiveReference from the LiveVm.

 

{         
    LiveVm java(vm);
    LiveReference thiz = java[thisObject]; //Create a LiveReference to an Object.
    LiveReference clazz = java[thisClass]; //Create a LiveReference to a Class.
    LiveReference clazz2 = java["eve/sys/Vm"]; //Also create a LiveReference to a Class.
}

 

In the examples above thisObject is of type ObjectReference while thisClass is of type ClassReference. A LiveReference created from an ObjectReference can access instance fields and methods of that Object or static fields and methods of the Class of that Object. A LiveReference created from a ClassReference or a class name can only access static fields and methods.

 

Note that a LiveReference can be cast into an ObjectReference.

 

Accessing Fields

You use a LiveReference to read and assign values to Java Fields and to call Java Methods. The [ ]  operator is used for doing this. For example look at this Java class:

 

public class Test {
 
 public int anInt = 0;
 public static double aStaticDouble = 0;
 public String myStringVal = "Hello from test!";
 public Object anArray = new String[]{"First","Second","Third"};
 
 public String toString()
 {
       return anInt+", "+aStaticDouble+", "+mString.toString(anArray);
 }
 
 native String setTheValues(double dv, int av);
 
 public int callFromEni(double val, boolean abool, int iv, String msg)
 {
       System.out.println("callFromEni("+val+", "+abool+", "+iv+", "+msg+")");
       return (int)(val/10);
 }
 
 
}

 

Within the setTheValues() method we wish to access and modify some field values.

 

//============================================================
ObjectReference eveeni_Test_setTheValues
(EniVm & vm,ObjectReference thisObject,jdouble par1,jint par2)
//============================================================
{
 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 // Get and set the anInt instance field.
 jint ai = thiz["anInt"];
 thiz["anInt"] = par2;
 //
 return java->nullObject();
}

 

 

Here we see the use of the [ ] operator to reference a field in the Object (or Class if it is a static field) referred to by the LiveReference called “thiz”.

 

In the example above, we did not specify the type of the Field – we left it to ENI to determine that. However if the Field is not public, then ENI will not be able to determine its type. In that case you must specify the type of the field explicitly. To do that you add a ‘:’ followed by a type name to the field:

 

 // Specify the type of the field.
 jint ai = thiz["anInt:int"];
 thiz["anInt:int"] = par2;
 //

 

Here I have used “int” to specify the field type. I can use any of the Java primitive type names or a fully qualified Java Class name in dot notation (e.g. “java.lang.String”). It is also possible to use # to represent java.lang.Object and $ to represent java.lang.String.

 

Note the operator [“field_name”] as shown above carries some processing overhead as the field type must be discovered and a reference to the field in the class must be looked up. In the example shown above this is actually done twice for each call of the setTheValues() function. A SavedField ENI object can be used to save a reference to a field without having to do a lookup each time. We can modify the function to be more efficient in this way:

 

//============================================================
ObjectReference eveeni_Test_setTheValues
(EniVm & vm,ObjectReference thisObject,jdouble par1,jint par2)
//============================================================
{
 static SavedField anInt("anInt","int");
 
 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 // Get and set the anInt instance field.
 jint ai = thiz[anInt];
 thiz[anInt] = par2;
 return java->nullObject();
}

 

Note that the SavedField is declared static so that its value will persist between function calls while still being private to the function. It is important that a SavedField only be applied to the same Java class each time. This is because when the SavedField is first used, the field lookup is done on the Java class it is being applied to and that information is then saved. The following times it is used that same information is reused and so it must be applied to the same Java class otherwise severe program errors can occur.

 

In the example above the anInt SavedField was created with the type specifier, but this could have been left out.

 

 
 static SavedField anInt("anInt");
 

 

The form above is also valid but will only work for public Fields.

 

There is a convenient #define for declaring and naming a SavedField called SaveAField() and SaveAPublicField(). This can be used with static or without.

 

//The same as: static SavedField anInt("anInt","int");
 static SaveAField(anInt,int);
//The same as: static SavedField anInt("anInt");
 static SaveAPublicField(anInt);
 

 

You can assign a new ObjectReference value to an existing LiveReference, and you can also create an “empty” LiveReference from a LiveVm.

 

 LiveReference empty(java);
 ObjectReference myString = thiz["myStringVal"];
 empty = myString;
 empty = thiz["myStringVal"];
 empty = thiz["aMethod(int)"] << 20 << CallMethod();
 ObjectReference obj = empty;

 

Note that a LiveReference must have a reference to a LiveVm, which is why the java parameter is needed in the constructor.

Calling Methods and Constructors

Calling methods is similar to accessing fields; the same [ ] operator is used but with a different format for the string used to specify the method. The format is: "MethodName(MethodParameters)MethodType " where MethodType is again optional for public methods. For example in our Java class there was this method:

 

 public int callFromEni(double val, boolean abool, int iv, String msg)

 

This method can be called from ENI using this line:

 

 
 jint got = thiz["callFromEni(double,boolean,int,java.lang.String)int"] 
       << 998877.6655 << true << 890 << "Did you hear me?" << CallMethod();
 

 

The << operator is used to add parameters to the method call before the call is made. You must ensure that you apply the correct number of parameters – ENI does not check this. The final operator must be applied to a CallMethod() class. The only purpose of this class is to initiate the method call, after which the return value is available to be assigned to a variable.

 

There are two shorthand characters that you can use when specifying parameter types. The character # can be used to specify a java.lang.Object and $ can be used to specify java.lang.String. Also, since the method is public I could have left out the return type specifier. So the previous example could have been:

 

 
 jint got = thiz["callFromEni(double,boolean,int,$)"] 
       << 998877.6655 << true << 890 << "Did you hear me?" << CallMethod();
 

 

 

Note that for instance methods the LiveReference must refer to a non-null Object and the method will be invoked on that Object. For static methods, the LiveReference need not reference an Object. Therefore you never need to specify in the parameter list which Object or which Class the method is being applied to.

 

As with fields it is possible save a reference to a method using a SavedMethod object. The same restrictions apply as in SavedFields in that it must only be applied to the same Java Class each time it is used.

 

 static SavedMethod callFromEni("callFromEni","(double,boolean,int,$)","int");
 //
 jint got = thiz[callFromEni] 
       << 998877.6655 << true << 890 << "Did you hear me?" << CallMethod();

 

And there is also a #define for creating a SavedMethod:

 

 
 static SaveAMethod(callFromEni,(double,boolean,int,$),int);
 static SaveAPublicMethod(callFromEni,(double,boolean,int,$));
 

 

Constructors are similar to methods except that you do not specify the name of the method and you do not specify the return value. For example if our Java class had the following constructor:

 

 public Test(String msg)
 {
       System.out.println("Creating with message:"+msg);
 }

 

Then we could create it using a CreateObject() instead of a CallMethod() like this:

 

 
 ObjectReference oj = thiz["($)"] 
       << "Creation message." << CreateObject();
 

 

And again we can use a SaveMethod or SaveAConstructor to save the method:

 

 
 static SavedMethod makeWithString("($)");
 static SaveAConstructor(makeWithString,($)); //Equivalent to line above.
 
 ObjectReference oj = thiz[makeWithString] 
       << "Creation message." << CreateObject();
 

 

Working With Strings

 

There are a number of C++ classes that are used for referring to and manipulating Strings within your ENI application. The classes eniUtfString and eniUnicodeString are very simple classes that contain pointers to a Java UTF-8 encoded String (where each character is usually one byte, but can be more) and a Unicode String (where each character is a 16-bit Unicode character) respectively. Each pointer is named str. Each class also contains a length field (called len) that holds the length of the String. Note: because these classes have an explicit length field the Strings that are pointed to by str are not always null-terminated and you should never assume that they are.

 

The type eniStringData is #defined to be either eniUtfString or eniUnicodeString depending on whether the UNICODE or _UNICODE #define is present when compiling. If you are compiling for Windows Mobile then UNICODE is always defined.

Building Strings

There are two more classes that are used for creating Strings and also for retrieving String values from Java fields and objects. eniUtfBuffer and eniUnicodeBuffer inherit from eniUtfString and eniUnicodeString, however the String data pointed to by the str field is “owned” and maintained by the buffer. The buffer expands its string data as needed and will delete the data when the buffer is destroyed unless the giveUpData(int *length = NULL) method is called.

 

The buffers will always maintain a null character at the end of the internal String so that you can be sure that str for a buffer will always point to a null terminated String. You can call the append(const char *data, int length = -1) method to append data to a buffer. Note that both buffers accept both UTF and Unicode data for appending – the buffer will convert the data as necessary while appending it internally. Therefore both buffer classes support the following methods and operators:

 

 
 bool setLength(int newLength);
 void clear();
 eniUtfBuffer& operator ! ();
 eniUtfBuffer& operator << (const char *data);
 eniUtfBuffer& operator << (eniUtfString data);
 eniUtfBuffer& operator << (const jchar *data);
 eniUtfBuffer& operator << (eniUnicodeString data);
 bool insert(int index,const char *data,int count = -1);
 bool insert(int index,const jchar *data,int count = -1);
 bool append(const char *data, int count = -1);
 bool append(const jchar *data, int count = -1);
 char *giveUpData(int *length = NULL);
 bool ensureCapacity(int newLen);
 

 

Note that the append() methods take an optional count parameter. If this parameter is omitted or set to -1, the method assumes that the data paramter is a null-terminated String. Also, the append() methods return true if the data was appended and false if there was not enough memory to append the data. The << operators return a reference to the buffer itself allowing multiple << operators to be used on a single line.

 

The method giveUpData() method is defined for both buffers (the eniUnicodeBuffer returns a pointer to a jchar String) and this returns a pointer to the String that has been built up in the buffer to that point. The optional int *length parameter can be used to get the length of the String at that point. After calling this method you are required to delete the data when no longer needed. This method also resets the buffer to its original empty state.

 

The clear() method sets the length of the buffer to zero and the operator ! will clear a buffer and return itself.

 

The setLength() method resizes the String and can make it smaller or larger. If you make the String larger, extra space characters are appended to the end of the String. The method returns false if you requested it to be made larger, but there was not enough memory to satisfy the request. The ensureCapacity() method allocates a minimum amount of space for the String but does not alter the current length of the String. This method returns false if there was not enough memory to expand the allocated space and true if there was.

 

The type eniStringBuffer is defined to be either eniUtfBuffer or eniUnicodeBuffer depending on whether UNICODE is defined or not.

Accessing Java Strings

If the Object that a LiveReference refers to is a Java String, then the operator >> can be used to append the String to a buffer (UTF or Unicode). The same operator can be used to retrieve Java String data from a Field that is also a String. In our example we can do:

 

 
 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 eniUtfBuffer sdata;
 thiz["myStringVal"] >> sdata;
 

 

The last line above appends the String data (if any) from the Field to the UTF buffer sdata. If we wanted to clear the sdata buffer before appending the String data we could have used the ! operator like this:

 

 thiz["myStringVal"] >> !sdata;
 

 

If we knew that  the ObjectReference thisObject as used above was an instance of a Java String, then we could fetch the String data directly from the LiveReference like this:

 

 thiz >> !sdata;
 

 

You can also assign Strings to field values and pass them as parameters to method calls using the = operator. You can either use literal (quoted) Strings or you can send String data objects (or buffers).

 

 thiz["myStringVal"] = "A new String";
 

EniVm Functions

 

There are a number of EniVm methods that provide important lookup functions not provided directly by LiveReference. These can be accessed via the provided EniVm class reference directly or via the LiveVm class using the -> operator. For example:

 

 ClassReference cr = java->nullClass();
 return vm.nullObject();

 

Both nullClass() and nullObject() are methods of EniVm which return a null Class and Object reference respectively. The first line calls the method via a LiveVm object, while the second calls via the original EniVm. Here are some more important EniVm methods:

 

These are some important reference manipulation methods.

 

bool isSameObject(ObjectReference one,ObjectReference two);

Returns if the two references refer to the exact same Object.

 

bool isSameClass(ClassReference one, ClassReference two);

Returns if the two references refer to the exact same Class.

 

ClassReference getClass(const ObjectReference &c);

Return the Class of a non-null Object.

 

ClassReference getClass(const char *className);

Get a Class using the java encoded type name.

e.g.: clazz = getClass(“java/lang/String”);

 

ClassReference getSuperclass(ClassReference clazz);

Return the super class for a Class. If there is no superclass a null ClassReference is returned.

 

bool isAssignableFrom(ClassReference clazz,ClassReference targetClass);

Return if an Object of type clazz can be assigned to a variable of type targetClass.

 

bool getEncodedName(const ClassReference &c, eniUtfBuffer &dest);

Given a ClassReference append the Java encoded name (e.g. Ljava/lang/String;) of the class.

 

int getArrayComponentType(ObjectReference array);

Given an array return the single character that indicates the array component type. This returns ‘L’ for object, ‘[‘ for arrays and one of the single character Java type specifiers for primitive types.

 

ClassReference getComponentType(const ClassReference &arrayClass);

Given the Class of an array Object, return the Class of the elements. Note that this works for primitive arrays as well as Object arrays. Primitive data types also have ClassReferences.

 

ObjectReference getCurrentException();

Determine the current exception, if any, that has been thrown and not caught in this Thread.

 

ObjectReference throwException(ObjectReference r);

Throw a new Exception, replacing any previously uncaught exception in this Thread.

 

void clearCurrentException();

Clear any uncaught exception in this Thread.

 

Encoded Java Types

 

Encoded Java Types are used to unambiguously represent as a String a complete Java type, primitive or Object.

 

Encoded Type

Java Type

ENI Type

Z

boolean

jboolean

B

byte

jbyte

C

char

jchar

S

short

jshort

I

int

jint

J

long

jlong

F

float

jfloat

D

double

jdouble

L<encoded_class>;

Object

ObjectReference

[<FieldType>

Array

ObjectReference

 

Note the form for specifying a Java Class. It must be L followed by the encoded class name followed by ; - leaving out the semicolon will cause class searching to fail. The encoded class name is the full class name (including packages names) but using a ‘/’ character as a package separator instead of a ‘.’ as is normally used in a Java application. So the encoded class name for java.lang.String is java/lang/String and the full encoded type would be Ljava/lang/String;

 

Arrays are defined by placing the [ array specifier before any other valid FieldType, including arrays. Therefore an array of an array of floats is: [[F and an array of Strings is [Ljava/lang/String;

 

Temporary References

 

Whenever you retrieve an Object value from a Field, or when one is returned by a Method Call, the ENI system keeps an internal reference (called a local reference) to the Object so that it will not be garbage collected during the life of the native method call. While most native calls perform a simple task and then return quickly, there may be others that run for significant lengths of time, possibly frequently retrieving Object references. It is therefore necessary for your ENI code to be able to explicitly clear Object references so that the Virtual Machine can garbage collect them if no other Objects refer to them.

 

The LiveVm that you created can delete local references like this:

 

 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 ObjectReference myString = thiz["myStringVal:$"];
 //
 // When it is no longer needed, I can remove the reference like this:
 //
 java->deleteLocalReference(myString);
 

 

An easier way to do this is to use a TempReference. A TempReference can be thought of as an ObjectReference that will call deleteLocalReference() on the Object it refers to when it goes out of scope or when a new ObjectReference is assigned to it. You would usually use a TempReference when iterating through an array of Objects but when you do not need to keep a permanent reference to any of the elements in the array.

Working With Arrays

 

To access the elements of an existing array place the array Object reference in a LiveReference or a TempReference and then use the following procedures:

 

The method arrayLength() will return the number of elements  in an array only if the LiveReference/TempReference is a valid reference to an array Object. The method elementType() will return a single character encoded type identifier of the type of the elements in the array. Primitives are represented as ‘B’, ‘Z’, ‘I’ etc., while Objects are represented as ‘L’ or ‘[‘. To get more detailed information on the element type use EniVm getComponentType().

 

To access individual Object elements within an array of Objects use the [index] operator.

 

 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 LiveReference arr = thiz["anArray:#"];
 // Assuming that arr now refers to a valid array Object.
 int size = arr.arrayLength(); // Gets the number of elements.
 int ty = arr.elementType(); // Gets a single character type indicator.
    // To get more detailed element type we do this:
 ClassReference cr = java->getComponentType(java->getClass(arr));
 // To acess elements.
 TempReference t(java);
 EniUtfBuffer sdata;
 t = arr[0]; // Get the Object reference.
 arr[0] = java->nullObject(); // Assign null.
 arr[1] = "There!"; // Assign a String.
 arr[2] >> !sdata; // Get the String data.

 

To access elements in an array of primitive data types you must copy sections of the array to and from local memory. A PrimitiveBuffer is the easiest way to do this. You can define a PrimitiveBuffer of any initial length and you can append data to it as necessary. A PrimitiveBuffer can also be reused, even for data of a different type.

 

Here is an example of a function that will double all the values within an array, if it is an int or double array.

 

//----------------------------------------------------------
static bool doubleArray(LiveVm &java, ObjectReference array)
//----------------------------------------------------------
{
 PrimitiveBuffer buffer;
 LiveReference ar = java[array];
 if (ar.isNullObject()) return false;
 int ty = ar.elementType();
 if (ty == 0 || ty == 'L' || ty == '[') return false;
 ar >> !buffer; // Get all elements.
 int len = buffer.length();
 // We will only double int and double arrays.
 if (ty == 'I'){
       int *ints = (int*)buffer.getData();
       for (int i = 0; i<len; i++) ints[i] *= 2;
 }else if (ty == 'D'){
       double *doubles = (double*)buffer.getData();
       for (int i = 0; i<len; i++) doubles[i] *= 2;
 }
 ar << buffer; // Set all elements.
 return true;
}

 

Note that the >> operator transfers all primitive elements from an array into the PrimitiveBuffer, while << transfers all elements in the PrimitiveBuffer into the array. Neither of these creates a new array, they only work assuming the LiveReference actually refers to a valid array of primitives. A PrimitiveBuffer can be cast to a void * to access the data stored within it and the length() method tells you to current size of the buffer.

 

To get or set only a subset of the primitive elements use these methods of LiveReference or TempReference.

 

 PrimitiveBuffer & getElements(int offset,int length,PrimitiveBuffer &dest);
       //Append the specified elements to the destination buffer.
 PrimitiveBuffer & getElements(PrimitiveBuffer &dest)
       //Append all the elements to the destination buffer.
 void setElements(int offset,int length,void *array)
       //Set specific elements within the array from the source data.
 void setElements(void *array)
       //Set all elements in the array from the source data.

 

There are many methods to manipulate the data in a PrimitiveBuffer:

 

 int length(); //Returns the number of elements.
 int dataType(); //Return the single character element type (e.g. ‘I’);
 int dataSize(); //Return the number of bytes for the particular primitive value.      
 bool ensureCapacity(int newLen); //Ensure that there is at least newLen elements allocated.
 PrimitiveBuffer & operator !(); //Clear the buffer.
 //
 bool append(void *src, int elements, int type); //Append to the buffer.
 /**
 Make space to append to the end. This adds numElements to the length. You should
 then write the data to the pointer that is returned, unless it is NULL which
 indicates no memory. The length value is automatically increased.
 **/
 void *makeSpaceToAppend(int numElements,int type);
 //
 bool setLength(int newLength); //Reduces or increases the length.
 //
 void *getData(int offset = 0); //Get a pointer to the data at a particular offset.
 void * operator [] (int index); //Also get a pointer to the data at a particular offset.
 operator void * () {return data;} //Cast to a pointer at the first index.
 void *giveUpData(); // Release the data. You must delete it when it is no longer needed.
 void free(); //Release all kept memory.
 bool create(int length,int type);//Recreate for a specific length and type.
 //
 //Create a NewArray object which can be assigned to ObjectReferences.
 //
 NewArray toNewArray(int startIndex = 0); //Create a new array from a subset or all elements.
 NewArray toNewArray(int startIndex, int length); //Create a new array from a subset of elements.

 

To create a new Array object use a NewArray Object. You can create them directly in two ways:

 

 NewArray(int length, int elementType, void *dataToCopyIntoArray = 0);
 NewArray(int length, const char *typeName);

 

The first form specifies an array of a certain length for a primitive type given by the elementType parameter. This should be a single character representing the Encoded type name (e.g. ‘I’ for int, ‘D’ for double). You can also specify an optional pointer to data. This data will be copied into the array when created. The second form is used to create Object arrays. The typeName must specify a full Java encoded type name (e.g. Leve/fx/Point;). Note that neither of these constructs actually create the array. To create the array you could do it explicitly using the method:

 

 ObjectReference make(java_vm_access &vm);

 

However assigning a NewArray object to a LiveReference field will automatically create the array and assign it to the field.

 

 
 LiveReference ref = java[thisObject];
 //Create an array of 2 Strings and assign it to the field anArray.
 ref["anArray"] = NewArray(2,"$"); 
 ObjectReference ar = NewArray(4,’I’).make(java);
 TempReference temp(java);
 temp = NewArray(10,’D’);
 

 

Here is an example of changing the array Object itself.

 

//----------------------------------------------------------
static ObjectReference extendArray(LiveVm &java, ObjectReference array)
//----------------------------------------------------------
{
 PrimitiveBuffer buffer; // Reuse this buffer.
 //
 LiveReference ar = java[array];
 if (ar.isNullObject()) return java->nullObject();;
 int ty = ar.elementType();
 if (ty == 0 || ty == 'L' || ty == '[') return java->nullObject();
 ar >> !buffer; // Get all elements.
 int len = buffer.length();
 int nlen = len*2;
 buffer.setLength(nlen); //Double the size of the buffer.
 // We'll only do int and double arrays.
 if (ty == 'I'){
       int *ints = (int*)buffer.getData();
       for (int i = 0; i<len; i++) ints[nlen-i-1] = ints[i];
 }else if (ty == 'D'){
       double *doubles = (double*)buffer.getData();
       for (int i = 0; i<len; i++) doubles[nlen-i-1] = doubles[i];
 }
 //Create and return the new Array.
 //Remember you may have to call java->deleteLocalReference() on
 //when you no longer need to hold a reference to it.
 return buffer.toNewArray().make(java);
}

ENI Java Synchronizing

 

Synchronizing with a Java Object is very simple. Simply create a SynchronizedObject with a LiveReference or a TempReference and the thread will be synchronized with the Object for as long as the SynchronizedObject exists. You can call notify() on the SynchronizedObject which will have the effect of calling notifyAll() on the Java Object. Note that, because of limitations on the Eve virtual machine, you cannot wait() on the synchronized Object.

 

You can use a SynchronizedObject to safely pass information from a Thread running in ENI back to another Thread that may be monitoring its progress in some way. We will use this example below. It runs a new Thread that calls a native method called doProcess(). That native method will not return immediately, instead it will perform some lengthy operation and report its progress through two fields called progress and complete. As it proceeds it sets progress to an increasing number and when it is complete it will set complete true.

 

package eveeni;
import eve.sys.Vm;
 
public class ProcessTest extends Thread{
 
 private int progress = -1;
 private boolean complete = false;
 
 private native void doProcess();
 
 public void waitAndReport() throws InterruptedException
 {
       int lastProgress = -1;
       while(true){
              int p = 0;
              //
              // Wait up to 1/10 of a second for some progress.
              // If none, print out a '.'
              //
              synchronized(this){
                    //
                    // Don't make any blocking calls except wait()
                    // while synchronizing with an ENI thread.
                    //
                    if (progress == lastProgress && !complete)
                          wait(100);
                    p = progress;
              }
             //
              if (p != lastProgress){
                    // progress did change.
                    lastProgress = p;
                    System.out.println("Progress: "+lastProgress);
              }else{
                    // progress did not change within 100 ms.
                    System.out.print(".");
                    System.out.flush();
              }
              if (complete) break;
       }
       System.out.println("Complete!");
 }
 public void run()
 {
       System.out.println("Native Thread Starting.");
       doProcess();
       System.out.println("Native Thread Done.");
 }
 public static void main(String[] args) throws Exception
 {
       Vm.startEve(args);
       System.loadLibrary("test_eni");
       ProcessTest pt = new ProcessTest();
       pt.start();
       pt.waitAndReport();
 }
}

 

The key method is waitAndReport() this is the method that waits for changes to progress and complete and reports on the changes. It prints out the value of progress as it changes and it will exit once complete is true.

 

The synchronized block within the method is where the fields are checked and where a wait() is done. It is very important to note:

 

Do not make any blocking calls except for wait() when synchronized with an Object that an ENI Thread will also be synchronized to.

 

This is due to another limitation of the Eve VM. Within that synchronized block you should do nothing except get and set field values. If you need to make a blocking call, make local copies of any synchronized field values (like progress in the example) and then make the blocking call outside of the synchronized block. This is why all the System.out.print() calls are outside of the synchronized block.

 

The native implementation of doProcess() could look something like this.

 

//============================================================
void eveeni_ProcessTest_doProcess(EniVm & vm,ObjectReference thisObject)
//============================================================
{
 static SaveAField(progress,int);
 static SaveAField(complete,boolean);
 
 for (jint i = 0; i<20; i++){
       //
       // Here we do the real work and access native resources.
       //
       Sleep(100);
       //
       // Now we report the progress to the Java object.
       //
       {
              LiveVm java(vm);
              LiveReference thiz = java[thisObject];
              {
                    SynchronizedObject syn(thiz);
                    thiz[progress] = i+1;
                    syn.notify();
              }
       }
 }
 //
 // Here we can do some cleanup with native resources.
 //
 Sleep(10);
 //
 // Now we report that we are complete.
 //
 {
       LiveVm java(vm);
       LiveReference thiz = java[thisObject];
       {
              SynchronizedObject syn(java,thisObject);
              thiz[complete] = true;
              syn.notify();
       }
 }
}
//------------------------------------------------------------
eve_eni_link(instance,void,eveeni_ProcessTest_doProcess,0,())
//------------------------------------------------------------