JNIEasy
Manual
V1.2.1
Doc. version 1.0
March 31, 2008
Jose Maria Arranz Santamaria
2.2 The JNIEasy/Java Native Objects
solution
3.2 required user technical skills
3.3 Technical requirements, limitations
and dependencies
4.1.6 Native capable object instance
4.1.8 User defined native capable class
4.1.9 “Can be native” class/object
4.1.10 Predefined native capable class
4.1.11 Native enabled Java type
4.1.12 Native (capable) wrapper class/object
4.1.15 Native capable method signature
4.1.16 Native method signature
4.1.18 Native (capable) method
4.1.19 Native (capable) field-method
4.1.20 Native (capable) object method
4.1.22 Native proxy of a DLL exported method
(or field)
4.2.6 Native memory management
5. Declaration and creation of native capable objects
5.1 Creation of predefined native capable
objects
5.1.1 Creation with native types
5.1.3 Creation of proxies of DLL methods
and fields
5.1.4 Creation of proxies of native methods
and fields if the address is known
5.1.5 Creation of direct and reflection
callbacks
5.1.6 Using Java reflection as proxy of
native methods
5.2 Creation of user defined JAVA native
capable objects
6.2 Making native a native capable object
6.2.1 Allocating native memory
6.2.3 By reachability of the fields
6.3 Reading and updating native instances
6.3.1 Reading fields of user defined native
capable classes
6.3.2 Updating fields of user defined
native capable classes
6.3.3 Reading/writing the native memory of CanBeNativeCapable objects
6.3.4 Fetch and “unfetch” modes
7. Definition of user defined JAVA NATIVE METHODS AND classes
7.1 C++ classes, structures, unions and C
Methods
7.1.2 Declaring Java methods working as
proxy of native methods
7.1.3 Declaring Java methods working as
native callbacks
7.1.4 Mixing models: a Java class working
as proxy and callback
7.2 User defined native capable direct
callbacks
7.2.2 Static callbacks with the method
outside the class
7.2.4 Exporting the callbacks to the native
side
7.4 User defined native capable arrays
7.4.2 Arrays of native capable classes
7.5 mapping native legacy classes
8.1 Enhancement done on filesystem
10. Direct memory manipulation
11.1 platform dependent size of addresses
11.2 Multiple native memory sizes and
alignments of primitive types
11.2.1 Changing the global default size and
alignment
11.2.2 Changing the default size and
alignment of a native element
11.3 C “inspired” macro system to resolve
sizes and names
13.1 Interfaces to be implemented by user
defined native capable classes
13.2 Interfaces implemented by normal
classes working as listeners
16.1 XML ENHANCER DESCRIPTOR REFERENCE
16.1.1 Element: jniEasyEnhancer
16.1.11 Common native behavior attributes
16.1.16 Native variable type attributes and
nodes
16.1.17 String based native variable type
16.1.19 Long type as cross-platform
16.1.20 Primitive wrapper types
16.1.21 Primitive object types
16.1.22 Array native variable type
16.1.23 Pointer native variable type
16.1.24 Behavior based native variable types
16.2 XML Java code generation DESCRIPTOR
REFERENCE
16.2.1 Element: jniEasyJavaCodeGen
Copyright 2006 Innowhere Software Services S.L.
Author: Jose Maria Arranz Santamaria
NOTICE: This Document is protected by
copyright. Except as provided under the following license, no part of the Document
may be reproduced in any form by any means without the prior written
authorization of Innowhere Software Services S.L. ("Innowhere"). Any
use of the Document and the information described therein will be governed by
the terms and conditions of this Agreement. Subject to the terms and conditions
of this license, Innowhere hereby grants you a fully-paid, non-exclusive,
non-transferable, limited license (without the right to sublicense) under
Innowhere's intellectual property rights to review the Document only for the
purposes of evaluation or legal use of JNIEasy. This license includes the right
to discuss the Document (including the right to provide limited excerpts of
text to the extent relevant to the point[s] under discussion) with other
licensees (under this or a substantially similar version of this Agreement) of
the Document. Other than this limited license, you acquire no right, title or
interest in or to the Document or any other Innowhere intellectual property,
and the Document may only be used in accordance with the license terms set
forth herein. This license will expire on the finish of the adquired JNIEasy
product license. In addition, this license will terminate immediately without
notice from Innowhere if you fail to comply with any provision of this license.
Upon termination, you must cease use of or destroy the Document.
TRADEMARKS: No right, title, or interest in or
to any trademarks, service
marks, or trade names of Innowhere, Innowhere's
licensors or Document author
is granted hereunder.
DISCLAIMER OF WARRANTIES: THE DOCUMENT IS
PROVIDED "AS IS" AND MAY CONTAIN DEFECTS OR DEFICIENCIES WHICH CANNOT
OR WILL NOT BE CORRECTED BY Innowhere.
THE DOCUMENT COULD INCLUDE TECHNICAL
INACCURACIES OR TYPOGRAPHICAL ERRORS. CHANGES ARE PERIODICALLY ADDED TO THE
INFORMATION THEREIN; THESE CHANGES WILL BE INCORPORATED INTO NEW VERSIONS OF
THE DOCUMENT, IF ANY. Innowhere MAY MAKE IMPROVEMENTS AND/OR CHANGES TO THE
PRODUCT(S) AND/OR THE PROGRAM(S) DESCRIBED IN THE DOCUMENT AT ANY TIME. ANY USE
OF SUCH CHANGES IN THE DOCUMENT WILL BE GOVERNED BY THE THEN-CURRENT LICENSE
FOR THE APPLICABLE VERSION OF THE DOCUMENT.
LIMITATION OF LIABILITY: TO THE EXTENT NOT
PROHIBITED BY LAW, IN NO EVENT WILL
Innowhere OR ITS LICENSORS BE LIABLE FOR ANY
DAMAGES, INCLUDING WITHOUT LIMITATION, LOST REVENUE, PROFITS OR DATA, OR FOR
SPECIAL, INDIRECT, CONSEQUENTIAL,INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO ANY
FURNISHING, PRACTICING, MODIFYING OR ANY USE OF THE DOCUMENTATION, EVEN IF Innowhere
AND/OR ITS LICENSORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
You will hold Innowhere (and its licensors)
harmless from any claims based on your use of the Document for any purposes
other than the limited right of evaluation or legal use as described above, and
from any claims that later versions or releases of any Documentation furnished
to you are incompatible with the Documentation provided to you under this
license.
REPORT: You may wish to report any
ambiguities, inconsistencies or inaccuracies you may find in connection with
your of evaluation or legal use of the Document ("Feedback"). To the
extent that you provide Innowhere with any Feedback, you hereby: (i) agree that
such Feedback is provided on a non-proprietary and non-confidential basis, and
(ii) grant Innowhere a perpetual, nonexclusive, worldwide, fully paid-up,
irrevocable license, with the right to sublicense through multiple levels of
sublicensees, to incorporate, disclose, and use without limitation the Feedback
for any purpose related to the Document and future versions, implementations,
and test suites thereof.
GENERAL TERMS: Any action related to this
Agreement will be governed by the Spanish law and international copyright and
intellectual property laws and that unauthorized use may subject you to civil
and criminal liability. The Document is subject to Spanish export control laws
and may be subject to export or import regulations in other countries. Licensee
agrees to comply strictly with all such laws and regulations and acknowledges
that it has the responsibility to obtain such licenses to export, re-export or
import as may be required after delivery to Licensee. Neither party may assign
or otherwise transfer any of its rights or obligations under this Agreement,
without the prior written consent of the other party, except that Innowhere may
assign this Agreement to an affiliated company. This Agreement is the parties'
entire agreement relating to its subject matter. It supersedes all prior or
contemporaneous oral or written communications, proposals, conditions,
representations and warranties and prevails over any conflicting or additional
terms of any quote, order, acknowledgment, or other communication between the
parties relating to its subject matter during the term of this Agreement. No
modification to this Agreement will be binding, unless in writing and signed by
an authorized representative of each party.
“What
platform and/or language can I use for my new application?” This is a typical
question before starting a new development project. Of course there are many
options, but if you go through mature and quality roads there are not so much
options.
Java is a
prominent and successful language and platform, and perhaps the dominant
platform today to develop enterprise-quality applications. Java promises full
portability around platforms following the WORA (Write Once Run Anywhere)
mantra of Sun.
Anyway,
Java is not alone, Java runs on top of well established operating systems like
Windows and unixes (Linux, Mac, Solaris…), these operating systems were
conceived, of course, before Java, and tons of libraries, applications and
services have been developed before Java starts to shine. Sometimes the WORA is
not fully possible if you must deal with the specific issues of the platform,
or use well established libraries not found equivalent on Java. If your
application goes beyond the typical CRUD (Create, Retrieve, Update and Delete)
of a relational database using web, usually when developing desktop
applications, is common to found “stopper features”, integration problems not
already solved with Java. These stopper features invite to reevaluate Java as
the most appropriate platform to this type of applications (C#?, C/C++?,
Windows only tools?, Gtk+?, Qt?). Another scenario is an old but mature
application developed in C/C++, the cost of a full rewrite in Java may be enormous,
another option is to extend the application following the successful Java
route.
Java offers
a path to deal with the platform: the JNI (Java Native Interface). The JNI is a
C/C++ API working as a gate to/from Java.
But JNI has
many problems:
·
Is
a C/C++ based API, new skills needed.
·
Hard
to learn, code, read and maintain.
·
Error
prone (inherent problems of “native compiled” languages).
·
Memory
issues (memory leaks, crashes).
·
Data
conversions.
·
Strange
double modeling: Java classes are object oriented, the JNI methods are not and
must deal with C and C++ structures and classes. Synchronization issues.
·
Reduction
of Java code in favor of native code: portability issues.
·
Java
classes with native methods and C++ “glue code” usually are not object
oriented.
·
Establishment
of two development/deployment environments (IDEs, configurations …): Java and
C/C++.
JNIEasy is
a new JNI, Java Native Interface, but with no C/C++ code. This is not new;
other tools have done this before, then… what is new?
JNIEasy
starts a new software paradigm that we can call “Java Native Objects” or JNO. JNIEasy takes the main idea of the
Java Data Objects (JDO) world to the world of native programming: transparency.
JNIEasy
converts a POJO (Plain Old Java Object) into a Native Object:
·
an
object instance is equivalent to a native structure or C++ instance
·
a
String object may be seen as a native string
·
an
Object reference
is equivalent to a native pointer
·
fields
of a POJO class are equivalent to fields of a native structure or class
·
a
method call in a POJO is a call of a C or C++ method
·
a
Java method may be called from native as a callback using a method pointer
without JNI conventions.
·
a
Java object representing a native instance may free its related native memory
automatically when is garbage collected reducing (or avoiding) memory leaks.
All is done
(as possible) without special classes/artifacts which mimic the native world,
using directly String, StringBuffer, int, long, double, Method, int[], String[]
user defined classes
(without special data types) etc.
JNIEasy is Java centric, brings the native world
to Java, not Java to the native world (the typical approach), the main purpose of transparency makes Java
native programming no different to normal programming in Java. Alike JDO/Hibernate/JPA
with the persistent programming, JNIEasy
makes the native programming a pleasure, resolving the integration tasks
with the underlying platform without JNI or C/C++ (Java access from C/C++ using
JNIEasy is optional).
This
“magic” is achieved using a technique typically used in JDO and AOP (Aspect
Oriented Programming): bytecode
enhancement (or bytecode weaving). The bytecode enhancement extends a
normal compiled class with new capabilities (new code) but keeping the source
code the same avoiding intrusive and disturbing artifacts used in the
traditional approach to the problem. The bytecode enhancement enables the IOC
(Inversion Of Control) paradigm used by the framework, to be fully transparent
to the programmer; avoids the use of Java reflection making transparent native
programming performant too and may be done in the command line or on class
loading (simplifying the development lifecycle).
Although transparency is the main feature of
JNIEasy (the “easy” level), the framework offers a very rich set of classes and
interfaces to deal with the native world too. The programmer has ever the
option of a fine control of the native memory or relay in the framework using
Java basic data types (int, Integer, int[], String, StringBuffer, String[],
Method … all of them have a “native layout” in JNIEasy), in fact, this rich
model is used behind the scenes by the framework to achieve the transparency.
Of course these utility classes and interfaces are Java centric too.
Does
JNIEasy break the WORA promise?
Yes and no.
The WORA promise is “easily” achieved using Java only and cross-platform Java
libraries, but these “cross-platform” libraries are not ever developed using
Java only, the popular AWT toolkit and many basic standard classes are very
dependent on the platform (many native methods), why are they WORA too? because
they are ported to almost any platform and work (“almost”) in the same way in
all platforms. If the popular SWT toolkit (very platform dependent) was present
in all Java installations, it would be considered part of the WORA.
If your
project needs a special feature present in the underlying platform, do you must
wait to Sun (and others) to deliver a WORA solution? , this is not reasonable
and impossible if your need is an integration issue with another specific tool.
JNIEasy helps you to construct your own “native
WORA in Java”: your
Java code may detect the underlying platform and use a specific Java code to
this platform, and offer a high level cross-platform API, your customers/users
do not need to select a specific platform version of your program, because no custom
DLL is needed to be delivered with your code.
JNIEasy
offers several cross-platform options like the variable size primitives, for
instance: a Java long can be reflected in the native side as a 32 bits, or 64 bits integer
depending on the running platform (a long can hold a cross-platform address), a Java char can be a C char (1 byte), a Win32 wchar_t (2 bytes) or a gcc’s wchar_t (4 bytes) in unixes (Linux, Mac OS
X, Solaris…), in a extreme a Java field can be 0 native size in a specific
platform and not zero in another platform. The objective is simple: one Java
layout-multiple native layouts, the multiple native layouts usually are due to
the use of C macros, to achieve this type of “flexible memory selection” based
on the platform JNIEasy uses a conditional “C inspired” macro system too. This
macro system is used to select the appropriate name of dynamic libraries too
(e.g. MSVCRT.dll on Windows
and libc.so.6 on Linux,
libc.dylib on Mac OS X, libc.so.1 on Solaris) or to resolve the
problems of different name mangling of C++ compiler, the correct native
exported name is selected using this macro system.
Never the
WORA was so easy and powerful… and “so Java”…
This manual
makes an extensive documentation of JNIEasy features but must be complemented
with the API documentation in javadoc format.
JNIEasy
avoids coding with JNI and C/C++, but basic or medium knowledge of C or C++ is
needed, because a native API exported in a dynamic library uses C/C++ types and
conventions.
Only C/C++
languages are considered in the native size because they are the most popular
“native” languages and JNI is defined only to C/C++. JNIEasy tries to avoid JNI
use at all, furthermore native code can access (optionally) to Java code
without JNI, wherefore other native languages like FORTRAN or Delphi can be
used with JNIEasy because these languages can create and bind Dynamic Link
Libraries/shared objects; the developer must convert “mentally” C/C++ data
types to the selected language and vice versa to understand the JNIEasy’s
native operations and structures.
The Java part
of JNIEasy is compiled and tested with Sun’s Java Standard Edition v1.4. It has
been tested with v1.5 (Java SE 5) with no problem.
Four Intel
x86/32 bit (IA-32) platforms are supported at this moment: Windows, Linux, Mac
OS X (since 10.4) and Solaris with Pentium Pro/i686 as the minimum processor:
· Windows: JNIEasy.dll and JNIEasy.lib (Windows) are compiled with Visual
C++ 6.0, Win32 DLL target and Pentium Pro processor
· Linux: libJNIEasy.so is compiled with gcc-4.0.2 using glibc-2.3.2
· Mac OS X: libJNIEasy.jnilib is compiled with gcc-4.0, provided
compiler of XCode 2.5 on a Mac OS X 10.4.5 Tiger Intel (10.5 version, Leopard,
is reported to work too)
· Solaris: libJNIEasy.so is compiled with the native gcc-3.4.3
compiler on OpenSolaris 11 build 85 (Solaris 11, SunOS 5.11)
Java code
is compiled with Java Virtual Machine v1.4.
JNIEasy can map C/C++ exported methods,
structures, unions and classes, but C++ templates may be exported but not bound
to Java generics.
JNIEasy depends on Javassist (LGPL, http://www.csg.is.titech.ac.jp/~chiba/javassist/) and
A Verdana font is used to describe the JNIEasy
architecture.
A Courier New font is used to Java, C/C++ and XML code fragments.
Represents
the C/C++ world, Java elements declared as native means they are related with
the C/C++ side.
Is memory
addressed and controlled by the native side (or by JNIEasy in the Java side), a
block of native memory is created with malloc() (or similar methods), or with new (generally this C++ sentence is
based on malloc()).
Native memory must be distinguished from “Java memory”, Java memory is the
memory controlled exclusively by the Java Virtual Machine (JVM). A JNIEasy main
task is synchronizing native memory and Java memory transparently.
Any Java
element that may have a role in JNIEasy, prepared to represent something in the
native side. Native capable may be classes, methods, fields, object instances…
A native
capable class is a normal Java class previously that implements the NativeCapable interface, prepared to represent native object instances. There are two
types: predefined classes (provided by the framework) and user defined (enhanced).
A native
object instance is a Java object of a native capable class with a related
native memory block, the native memory layout can be a C structure, a C++
class, a C native string (‘\0’ terminated), a simple native integer etc. A
native instance always has a native memory address (different to Java memory
address, this address is managed by the JVM not JNIEasy). A native object
instance is reflected in the native side as a class/structure/union instance, a
pointer to method, a native array (int[]), a zero ended string etc.
An object
instance of a native capable class that is not native (no native address/native
memory block associated). A native capable object instance can be made native.
Are
interfaces predefined by JNIEasy inheriting from NativeCapable. Represent different types of
native elements: Structure represents a C structure, CPPClass a C++ class, NativeString a native string mapped with java.lang.String, NativeIntegerArray a native array mapped with a normal
int[] Java array
etc. The developer does not need deal with these interfaces unless a fine
control is requested.
Is a class
coded by the user and enhanced or prepared to be enhanced on load time. The
enhancement process makes this class native capable implementing the NativeCapable interface. The fields and methods
of these classes can be declared as native, and managed by the framework. The
code of a user defined native capable class is managed code by the enhancer. The enhancement provides the native
capabilities to the user class.
A “can be
native” class is a basic standard Java class with a native corresponding
element. For instance: String and StringBuffer objects correspond to a '\0' terminated native string (char* if ANSI or wchar_t* if UNICODE), an int[] array corresponds to a native jint[] array (jint usually is equivalent to int C data type), a String[] or StringBuffer[] with an array of char* or wchar_t* pointers, an Integer object with an addressed jint integer, a
java.lang.reflect.Method reference
with a native method pointer etc.
Is a native
capable class predefined by JNIEasy. JNIEasy provides a rich set of native
capable classes representing (wrapping) most of the basic native elements like
strings, arrays of primitive types etc. Most of these classes are artifacts to
make native can be native objects, an
instance of a predefined class typically wraps a “can be native” object. These
classes may be not used directly, because the wrapped can be native objects may be used instead (in managed/enhanced
code). Furthermore, predefined native
classes are hidden by the framework and cannot be used explicitly, instead,
every predefined class has a public native interface (NativeString, NativeIntegerObject etc), object instances of theses
classes are created using the framework and may be accessed using the related
native interface. Example: a NativeString object wraps a String object.
Predefined
native capable classes/interfaces provides you with a fine control of the
native memory, they are used extensively and internally to provide the native
behavior of user defined native capable classes with “can be native” fields (String, Method, Integer etc), and methods declared as
native proxies, notwithstanding, they can be used in native fields too (a NativeString field is basically the same as a String field, but with NativeString
is possible to access a
specific character without fetch/unfetch the full native string from/to native).
Use of
predefined native capable classes makes the distinction of a fully transparent
native programming (using POJOs with extensive use of code generation and/or
enhancement) where the Java code is like C/C++ programming in Java with all
Java advantages (the “easy” level), or using predefined native capable classes
to gain more control of the native memory and code no so transparent and clean.
A “native
enabled Java type” is a Java type that can represent a native type. Native
enabled types are: native capable classes and interfaces (user defined and
predefined), “can be native” classes and primitive types.
A native
capable wrapper class is a predefined or user defined native capable class
whose main purpose is to make native a contained (wrapped) “can be native”
object. The user can use these classes directly to take full control of the
native memory associated to the “can be native” object or use directly the “can
be native” object in a managed code (a native wrapper object will be used
behind the scenes). Example: the predefined class implementing NativeString wraps String, NativeIntegerObject wraps Integer and so on.
Is the
“native view” of a native enabled Java type usually contained in a native
instance (e.g. an int field declared as native has a native type). Defines how is made native
an instance of this type. For example, a native object of a class implementing
the NativeString or
a String object
may represent a ‘\0’ terminated string of char or a L’\0’ terminated string of wchar_t, depending on the native type associated
to this instance (used to create the instance or defaulted if used new), the NativeString interface and String class are not sufficient themselves
to describe the native layout. The native type may be seen as the type of the
native object itself (the type of the resulting of new MyNativeClass()), not the type of a reference to
this object (ex. MyNativeClass ref). Any native capable object (non-native) has a
concrete native type, this native type will be used to define the native layout
when native. A native capable or “can be native” class usually has a default,
complete, native type (there are exceptions like Method and related predefined native
capable classes, the default native type is incomplete, a native method
signature must be specified).
Represents
a native declaration of a field member with a native enabled type, a formal
parameter declaration or return type of a Java method declared as native. It
must be seen as the native type of the declaration of a variable. In this Java
field declaration: MyNativeClass ref = new MyNativeClass(), the native view of the created
object instance is driven by the native
type, and the native view of the reference is driven by the native variable type. Native variable
types mimic the C conventionalism of “by pointer” or “by value”, if “by
pointer” ref is a pointer
to the created object on native memory and may change, if “by value” ref “contains” the native memory itself
and cannot change (the implicit address); this distinction is specially useful
declaring method parameters and fields “by value” or “by pointer”, for
instance: a int[] Java field of a user defined native capable class (a C++
class or structure or union in the native side) can be declared “by value” (the
array is embedded) or “by pointer” (the field is a pointer to array, int*). The
“by reference” (using the & sign of C) is a special case of “by pointer” and is not defined by
JNIEasy.
When a Java
method has parameters and return using native enabled types.
Represents
the JNIEasy concrete native declaration of parameters and return of a native
capable method signature (of a DLL method reflected in Java, a Java callback…).
The native call convention, standard call or C call, is declared too.
A native
capable field is a normal Java field member with a native enabled type. If declared
as native this field matches with a hypothetical field in the native layout of
a native instance of the container class, the native layout may be a primitive
data type, a pointer (if declared “by pointer”), a native structure, class or
array (if declared “by value”). A native field is a native capable field of a
native instance and has an implicit address.
A native
capable method is a normal Java method with a native capable signature,
declared as native using a native method signature. This method may be declared
as a proxy of a method contained in a Dynamic Link Library (DLL[1]) or a
callback “callable” from C/C++ and may represent a C++ constructor, a C++
method (instance method), a C method or a field wrapped with a special method.
A native capable method is not necessarily contained in a native capable class.
A Java native method is a native capable method with a native address and can
be called from native. A Java native method has a related native object method ever
(sometimes is created internally by the framework), this object can be used to
manage the method like a pointer to method in Java and pass a reference of this
object to the native side (the native side receives this reference like a
pointer to method).
A native
capable field-method is a native capable method wrapping a native capable field
with the following signature:
Static field
FieldType
fieldMethodName(int opcode,FieldType value)
Instance field
FieldType fieldMethodName(ContainerClass obj,
int opcode,FieldType value)
With the
mission of get/set the value of the field; if opcode is:
NativeFieldMethod.GET, or 0: the method returns the
current value of the field, the value parameter is ignored.
NativeFieldMethod.SET, or 1: the field is updated with
the value parameter,
and returns the new field value.
NativeFieldMethod.GET_SET, or 2: the field is updated with
the value parameter,
and returns the previous field value to the updating.
The
framework can offer this way to access a native Java field or a C/C++ field
directly.
A native
(capable) object method is a native (capable) utility object working as a proxy
of a Java native capable method or a field seen as a method (native callback)
or a DLL exported method or field (native proxy).
A native
callback is a native object method working as a proxy of a Java native capable
method or a field seen as a method. This object makes accessible the Java
native capable method (or field) from native, redirects a native call to the
wrapped Java method using reflection or a direct call.
A native
proxy of a DLL method is a native object method that makes the DLL method
callable from Java, converting the Java call to a native call. Usually a Java
native capable method can be used as an external and transparent front end
using in its implementation the native proxy to call the DLL. In the field case
a special method is used to read/write the DLL exported field, the field is
seen as a method.
A code zone
is “managed” if it was bytecode enhanced by the framework. Managed code zones
are the body of instance methods of user defined native capable classes. In
these methods accessing (read/modification) to native declared fields is
intercepted by the framework ensuring the native memory is read to get the
current “real” value of the field or modified to synchronize the native memory
with a new field value. In managed code the native memory manipulation is made
indirectly and transparent to the user. Managed methods are not necessarily
native (capable) methods.
User
defined classes must be enhanced to be converted in native capable classes.
Enhancement can be done on filesystem or on runtime (on class loading). If the
enhancement is done on filesystem (usually using the NativeEnhancerCmd class in the command line or Ant
task), this task does not use the native memory and/or load dynamic link
libraries. Running this task may be called “enhancement time”.
JNIEasy has
a special tool (usually using the NativeCodeGeneratorCmd class in the command line or Ant
task) to generate the code of Java classes working as a proxy of DLL exported
methods, usually C methods. The generated code is used to call from Java DLL
methods bound on demand (the first time was used); these classes are especially
useful to map DLLs with tons of exported methods. This task does not use native
memory but may use the enhancer (on-load mode). Running this task may be called
“generation code time”.
When
performing any task involving creation or use of native memory (using native
instances) and/or loading DLLs. The most important interface to these tasks is NativeManager.
A native
transaction is conceptuality very similar to a database transaction, or a Java
EE or JDO transaction but with the native memory seen as a “repository”. During
a native transaction the state of the native memory is saved before any
modification and is restored to the original values if the transaction is
aborted (rollback).
The
framework is fully included in the Java package: com.innowhere.jnieasy.core
This
package is divided in several sub packages; these packages are coincident with
the main tasks/parts of JNIEasy: enhancer, code generator, native type
declaration, native interfaces, factories, native memory management,
transaction management and listeners.
The
framework starts with the abstract class JNIEasy. The static get() method returns a singleton object
of this class; using this singleton we can obtain most of the utility
classes/interfaces.
JNIEasy
jnieasy = JNIEasy.get();
JNIEasy’s
public API is based mainly on interfaces; using interfaces helps to keep the
API clean (no undocumented and disturbing methods and fields) and robust to
internal changes and evolutions, but the reader must keep in mind that most of
lower interfaces have a unique implementation class into the framework, for
instance, only one class implements NativeInteger interface, using the NativeInteger data type is not different to use
the related internal class.
The
enhancer utilities are localized in the package: com.innowhere.jnieasy.core.enh
Provides
utility classes/interfaces related to bytecode enhancement tasks of native
capable classes.
The main
interface is NativeEnhancer, this interface is implemented by a singleton
object that can be obtained with the JNIEasy.getEnhancer() method. For instance:
NativeEnhancer enh = JNIEasy.get().getEnhancer();
With the
enhancer object we can enhance the classes (.class files) described with
special XML files, and enable/disable the “on load enhancement” feature.
The
enhancement task may be launch directly on command line using the executable
class NativeEnhancerCmd.
The code
generation utilities are located in the package:
com.innowhere.jnieasy.core.cgen
Provides
utility classes/interfaces used to generate the code of Java classes working as
proxies of exported methods (usually C methods) of big DLLs.
The main
interface is NativeCodeGenerator, this interface is implemented by a singleton
object that can be obtained with the JNIEasy.getCodeGenerator() method. For instance:
NativeCodeGenerator codeGen = JNIEasy.get().getCodeGenerator();
With the
code generator object we can generate the Java code of the user defined classes
specified in special XML files.
The code
generation task may be launch directly on command line using the executable
class NativeCodeGeneratorCmd.
The
package: com.innowhere.jnieasy.core.typedec contains:
1. The native type declaration utility:
the interface NativeTypeManager implemented by a singleton object that can be
obtained with the JNIEasy.getTypeManager() method. For instance:
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
2. Native type interfaces representing
native type declarations: all of them inherit from TypeNative.
3. The variable native type declaration
utility: the interface NativeVarTypeManager implemented by a singleton object that can be
obtained with the JNIEasy.getVarTypeManager() method. For instance:
NativeVarTypeManager
varTypeMgr = JNIEasy.get().getVarTypeManager();
4. The variable native type interface VarTypeNative representing variable native type
declarations.
5. The native method signature
declaration utility: the interface NativeSignatureManager implemented by a singleton object that
can be obtained with the JNIEasy.getSignatureManager() method. For instance:
NativeSignatureManager
sigMgr = JNIEasy.get().getSignatureManager();
6. Interfaces of native method
signatures: all of them inherit from NativeBehaviorSignature.
The native
interfaces are divided in two packages:
1. Package com.innowhere.jnieasy.core.data
Contains all native interfaces
implemented by predefined native capable classes starting on NativeCapable, with the exception of interfaces
related to native methods.
2. Package com.innowhere.jnieasy.core.method
Contains native interfaces related
to native methods.
|
Name |
Java class(es) matched[2] |
Contained element(s) |
User defined classes |
Matched native type of a reference |
Object size[3] |
Notes |
|
CPPClass |
Itself and user
defined classes as C++ classes |
Multiple fields and
methods |
yes |
CPPClass* if “by pointer”, CPPClass if “by value” |
Depends on
declaration |
The predefined
class has not native fields/methods (unknown) |
|
NativeArrayOfArray |
A multiple
dimensional array and user defined classes as multidimensional arrays |
A multiple
dimensional array |
yes |
Depends on
declaration |
Depends on
declaration |
The predefined
class contains an Object[] field |
|
NativeBoolean |
boolean |
boolean field |
no |
jboolean* |
1 |
|
|
NativeBooleanArray |
boolean[] |
boolean[] field |
no |
jboolean*, jboolean x[n] |
1* length |
|
|
NativeBooleanObject |
Boolean |
Boolean field |
no |
jboolean* |
1 |
|
|
NativeBooleanObjectArray |
Boolean[] |
Boolean[] field |
no |
jboolean**, jboolean* x[n] |
platf.size* length |
|
|
NativeByte |
byte |
byte field |
no |
jbyte* |
1 |
|
|
NativeByteArray |
byte[] |
byte[] field |
no |
jbyte*, jbyte x[n] |
1* length |
|
|
NativeByteObject |
Byte |
Byte field |
no |
jbyte* |
1 |
|
|
NativeByteObjectArray |
Byte[] |
Byte[] field |
no |
jbyte**, jbyte* x[n] |
platf.size* length |
|
|
NativeCapable |
Itself |
None |
yes (but using
inherited interf.) |
NativeType* if “by pointer” (“by value”not
allowed) |
Unknown |
|
|
NativeCapableArray |
NativeCapable[] classes and inherited and user defined classes as
native capable arrays |
An inherited Object[] field |
yes |
ElementType** or ElementType* x[n] if “by pointer”, ElementType* or ElementType x[n] if “by value” |
platf.size* length
if “by pointer” or elem.size* length if “by value” |
The predefined
class contains an Object[] field |
|
NativeCharacter |
char |
char field |
no |
jchar* |
2 |
|
|
NativeCharacterArray |
char[] |
char[] field |
no |
jchar*, jchar x[n] |
2* length |
|
|
NativeCharacterObject |
Character |
Character field |
no |
jchar* |
2 |
|
|
NativeCharacterObjectArray |
Character[] |
Character[] field |
no |
jchar**, jchar* x[n] |
platf.size* length |
|
|
NativeDouble |
double |
double field |
no |
jdouble* |
8 |
|
|
NativeDoubleArray |
double[] |
double[] field |
no |
jdouble*, jdouble x[n] |
8* length |
|
|
NativeDoubleObject |
Double |
Double field |
no |
jdouble* |
8 |
|
|
NativeDoubleObjectArray |
Double[] |
Double[] field |
no |
jdouble**, jdouble* x[n] |
platf.size* length |
|
|
NativeFloat |
float |
float field |
no |
jfloat* |
4 |
|
|
NativeFloatArray |
float[] |
float[] field |
no |
jfloat*, jfloat x[n] |
4* length |
|
|
NativeFloatObject |
Float |
Float field |
no |
jfloat* |
4 |
|
|
NativeFloatObjectArray |
Float[] |
Float[] field |
no |
jfloat**, jfloat* x[n] |
platf.size* length |
|
|
NativeInteger |
int |
int field |
no |
jint* |
4 |
|
|
NativeIntegerArray |
int[] |
int[] field |
no |
jint*, jint x[n] |
4* length |
|
|
NativeIntegerObject |
Integer |
Integer field |
no |
jint* |
4 |
|
|
NativeIntegerObjectArray |
Integer[] |
Integer[] field |
no |
jint**, jint* x[n] |
platf.size* length |
|
|
NativeLong |
long |
long field |
no |
jlong* |
8 |
|
|
NativeLongArray |
long[] |
long[] field |
no |
jlong*, jlong x[n] |
8* length |
|
|
NativeLongObject |
Long |
Long field |
no |
jlong* |
8 |
|
|
NativeLongObjectArray |
Long[] |
Long[] field |
no |
jlong**, jlong* x[n] |
platf.size* length |
|
|
NativeObjectArray |
Object[] and inherited |
An inherited Object[] field |
yes (but using
inherited interf.) |
ElementType** or ElementType* x[n] if “by pointer”, ElementType* or ElementType x[n] if “by value” |
platf.size* length
if “by pointer” or elem.size* length if “by value” |
The predefined
class contains an Object[] field.
User defined classes contains an object inherited array field |
|
NativePointer |
Itself and user
defined classes as Pointer |
An inherited Object field |
yes |
ElementType** |
platf.size |
The predefined
class contains an Object field |
|
NativeString |
String |
String field |
no |
char* or wchar_t* |
char size* length |
Depends on encoding |
|
NativeStringBuffer |
StringBuffer |
StringBuffer field |
no |
char* or wchar_t* |
char size* length |
Depends on encoding |
|
NativeShort |
short |
short field |
no |
jshort* |
2 |
|
|
NativeShortArray |
short[] |
short[] field |
no |
jshort*, jshort x[n] |
2* length |
|
|
NativeShortObject |
Short |
Short field |
no |
jshort* |
2 |
|
|
NativeShortObjectArray |
Short[] |
Short[] field |
no |
jshort**, jshort* x[n] |
platf.size* length |
|
|
NativeStringAnsi |
Itself |
String field |
no |
char* |
1*length |
|
|
NativeStringAnsiArray |
Itself |
String[] field |
no |
char**, char* x[n] |
platf.size* length |
|
|
NativeStringArray |
String[] |
String[] field |
no |
char** and char* x[n] or wchar_t**, wchar_t* x[n] |
platf.size* length |
Depends on encoding |
|
NativeStringBufferAnsi |
Itself |
StringBuffer field |
no |
char* |
1*length |
|
|
NativeStringBufferAnsiArray |
Itself |
StringBuffer[] field |
no |
char**, char* x[n] |
platf.size* length |
|
|
NativeStringBufferArray |
StringBuffer[] |
StringBuffer[] field |
no |
char** and char* x[n] or wchar_t**, wchar_t* x[n] |
platf.size* length |
Depends on encoding |
|
NativeStringBufferUnicode |
Itself |
StringBuffer field |
no |
wchar_t* |
2*length |
|
|
NativeStringBufferUnicodeArray |
Itself |
StringBuffer[] field |
no |
wchar_t**, wchar_t* x[n] |
platf.size* length |
|
|
NativeStringUnicode |
Itself |
String field |
no |
wchar_t* |
2*length |
|
|
NativeStringUnicodeArray |
Itself |
String[] field |
no |
wchar_t**, wchar_t* x[n] |
platf.size* length |
|
|
Structure |
Itself and user
defined classes as structures |
Multiple fields and
methods |
yes |
Structure* if “by pointer”, Structure if “by value” |
Depends on
declaration |
The predefined
class has not native fields/methods (unknown) |
|
|
Itself and user
defined classes as unions |
Multiple fields and
methods |
yes |
Union* if “by
pointer”, Union if “by value” |
Depends on
declaration |
The predefined
class has not native fields/methods
(unknown) |
|
Name |
Java class/method matched |
Contained element(s) |
User defined classes |
Matched native type of a reference |
Notes |
|
CFieldMethod |
none |
none |
no |
A pointer to a
field-method (no instance param.) |
Used to map a
static field exported in a DLL |
|
CMethod |
none |
none |
no |
A pointer to a
method (no instance param.) |
Used to map a
static method exported in a DLL |
|
CPPConstructor |
none |
none |
no |
A pointer to a C
method working as a constructor |
Used to map a C
method working as a constructor exported in a DLL |
|
CPPMethod |
none |
none |
no |
A pointer to a
method (first param. is the instance) |
Used to map a
instance method exported in a DLL |
|
NativeConstructor |
none |
none |
yes (but using
inherited interf.) |
A pointer to a C
method working as a constructor |
|
|
NativeConstructorReflection |
Constructor |
Constructor field |
no |
A pointer to a C
method working as a constructor |
|
|
NativeConstructorReflectionArray |
Constructor[] |
Constructor[] field |
no |
ConstrucPtr* or ConstrucPtr x[n] |
|
|
NativeDirectConstructorCallback |
The mapped
constructor |
none |
yes |
A pointer to a C
method working as a constructor |
The Java class is
generated on the fly if necessary |
|
NativeDirectInstanceFieldCallback |
The mapped instance
field |
none |
yes |
A pointer to a
field-method (first param. is the instance) |
The Java class is
generated on the fly if necessary |
|
NativeDirectInstanceMethodCallback |
The mapped instance
method |
none |
yes |
A pointer to a
method (first param. is the instance) |
The Java class is
generated on the fly if necessary |
|
NativeDirectStaticFieldCallback |
The mapped static
field |
none |
yes |
A pointer to a
field-method (no instance param.) |
The Java class is
generated on the fly if necessary |
|
NativeDirecStaticMethodCallback |
The mapped static
method |
none |
yes |
A pointer to a
method (no instance param.) |
The Java class is
generated on the fly if necessary |
|
NativeFieldMethodReflection |
Field |
Field field |
no |
A pointer to a
field-method |
The field may be
static or instance. If static the object can be cast to NativeStaticFieldMethodReflection, else
to NativeInstanceFieldMethodReflection |
|
NativeFieldMethodReflectionArray |
Field[] |
Field[] field |
no |
MethodPtr* or MethodPtr x[n] |
|
|
NativeInstanceFieldMethod |
none |
none |
yes (but using
inherited interf.) |
A pointer to a
field-method (first param. is the instance) |
|
|
NativeInstanceFieldMethodReflection |
Field |
Field field |
no |
A pointer to a
field-method (first param. is the instance) |
The reflection
field object must be non-static. |
|
NativeInstanceMethod |
none |
none |
yes (but using
inherited interf.) |
A pointer to a
method (first param. is the instance) |
|
|
NativeInstanceMethodReflection |
Method |
Method field |
no |
A pointer to a
method (first param. is the instance) |
The reflection
method object must be non-static |
|
NativeMethodReflection |
Method |
Method field |
no |
A pointer to a
method |
The method may be
static or instance. If static the object can be cast to NativeStaticMethodReflection, else to NativeInstanceMethodReflection |
|
NativeMethodReflectionArray |
Method [] |
Method [] field |
no |
MethodPtr* or MethodPtr x[n] |
|
|
NativeStaticFieldMethod |
none |
none |
yes (but using
inherited interf.) |
A pointer to a
field-method (no instance param.) |
|
|
NativeStaticFieldMethodReflection |
Field |
Field field |
no |
A pointer to a
field-method (no instance param.) |
The reflection
field object must be static. |
|
NativeStaticMethod |
none |
none |
yes (but using
inherited interf.) |
A pointer to a
method (no instance param.) |
|
|
NativeStaticMethodReflection |
Method |
Method field |
no |
A pointer to a
method (no instance param.) |
The reflection
method object must be static |
The
package: com.innowhere.jnieasy.core.util contains a utility interface, NativeCapableFactory, used as shortcut to create native
capable objects of predefined native capable classes, wrapping other native
capable or "can be native" objects. Implemented by a singleton object
that can be obtained with the JNIEasy.getNativeCapableFactory() method. For instance:
NativeCapableFactory objFactory = JNIEasy.get().getNativeCapableFactory();
Furthermore it contains two utility
classes: NativeCapableUtil with tasks related to native capable objects and NativePrimitiveUtil to primitive values.
The native
memory management tools are located in the package:
com.innowhere.jnieasy.core.mem
The main
tool is the interface NativeManager, used to manage the native lifecycle of native capable objects and
other native memory tasks. Is implemented by a singleton object that can be
obtained with the JNIEasy.getNativeManager() method. For instance:
NativeManager
natMgr = JNIEasy.get().getNativeManager();
Another
important tool is the DLLManager interface, used to manage DLLs (load/find). Each DLL/shared object is
represented with an object implementing the interface DynamicLibrary; using this interface, DLL exported
methods and fields can be mapped with native proxy methods. The dynamic library
of the framework (JNIEasy.dll on Windows, libJNIEasy.so on Linux and Solaris, libJNIEasy.jnilib on Mac OS X) is a special case, it is mapped
with the JNIEasyLibrary interface.
The DLLManager is
obtained with the JNIEasy.getDLLManager() method:
DLLManager
dllMgr = JNIEasy.get().getDLLManager();
And the JNIEasyLibrary
singleton is obtained
with:
JNIEasyLibrary
fwDll = JNIEasy.get().getJNIEasyLib();
The native
transaction tools are located in the package:
com.innowhere.jnieasy.core.txn
The main
interface is NativeTransaction, used to begin and end (commit or rollback) a
native transaction. The NativeTransaction interface is implemented by thread dependent
instances, only one transaction may be alive per thread, and the current
thread’s transaction is returned with the method NativeManager.currentTransaction().
The package
com.innowhere.jnieasy.core.listener provides interfaces and classes used to
register callbacks and event listeners of the life cycle of native instances.
There are
two types of listeners:
1.
Interfaces
implemented by native capable classes: the native instance changing its native
state receives the matched event. These are the EventTypeCallback interfaces.
2.
Interfaces
implemented by normal Java classes working as listeners: instances of these
classes are registered as listeners. These are the InstanceLifecycleListener based interfaces.
There are
two types of native capable objects: predefined and user defined.
The
predefined native classes are hidden by the framework and cannot be created
explicitly with new, related interfaces must be used against, and we need to specify what
native interface is used to create a predefined native capable object.
A native
interface of a predefined native capable class is not ever a complete native
type. An example is NativeString: a native string may be ANSI (char*) or UNICODE (wchar_t*), a native type may be necessary to
create native capable objects of predefined classes.
To declare
native types, the NativeTypeManager object is the direct way:
NativeTypeManager
typeMgr = JNIEasy.get().getTypeManager();
The NativeInteger is a final interface (native
capable objects can be created) represents native integers (jint addressed values); a native object
of this type is a jint in the native memory, and a Java reference can be seen as a jint* pointer. The TypeNativeObject.newValue() is used to create an object of the
declared class.
TypeNativeObject typeInt =
(TypeNativeObject)typeMgr.dec(NativeInteger.class);
NativeInteger natInt = (NativeInteger)typeInt.newValue();
natInt.setIntValue(10);
When this native
capable object is made native the 10 value is written on the related native
memory (a jint).
TypeNativeString
typeStr = typeMgr.decString(StringEncoding.UNICODE);
Now a
native capable string object can be created using UNICODE encoding and wide character
size, with the method TypeCanBeNativeCapable.wrapValue(Object) or the method TypeNativeString.newString(String):
NativeString
natStr = (NativeString)typeStr.wrapValue("Hello");
Is
equivalent to:
NativeString
natStr2 = (NativeString)typeStr.newString("Hello");
Both native
capable string objects contain the Java String "Hello". When any of them is made native,
the "Hello" string
using wchar_t
characters and ended with '\0', will be written on the related native memory associated.
VarTypeNative varTypeStr = typeStr.decVarType();
TypeNativeArray
typeStrArray = typeMgr.decArray(2,varTypeStr);
The
returned type specifies string arrays with 2 elements.
NativeStringArray strArray = (NativeStringArray)typeStrArray.wrapValue(
new String[]{"hello","bye"});
When this
object is made native, two new native strings are written to the native memory
with the Unicode strings “hello” and “bye” and the two addresses are written on the new native array with two
string pointers (wchar_t*).
The
interface factory, NativeCapableFactory , may be used as a shortcut to create objects
of predefined native capable classes.
NativeCapableFactory objFact = JNIEasy.get().getNativeCapableFactory();
NativeString
natStr = objFact.newString("Hello");
Creates a NativeString object with the default encoding
(usually ANSI)
NativeString
natStr2 = (NativeString)objFact.wrapValue("Hello");
Is equivalent to the previous example, the
method NativeCapableFactory.wrapValue(Object) expects a “can be native” object to be wrapped
with the matching native capable object, the native type used is the default
associated to the “can be native” object (an ANSI string).
NativeStringArray strArray = (NativeStringArray)objFact.wrapValue(
new String[]{"hello","bye"});
Creates a
native capable string array holding the specified string array, the default
string encoding will be used when made native.
NativeInteger
natInt = objFact.newNativeInteger(10);
Creates a NativeInteger object holding the value 10.
The proxies
of DLL methods (and fields seen as methods) are usually created using the DynamicLibrary instance of the DLL.
The
following example maps and calls the C Win32 method:
HWND FindWindowA(LPCTSTR, LPCTSTR)
located in
the Windows User32.dll, from Java:
DLLManager dllMgr = JNIEasy.get().getDLLManager();
DynamicLibrary dll = dllMgr.get("User32");
CMethod method = dll.addCMethod("FindWindowA",int.class,
new Object[]{String.class,String.class},CallConv.STD_CALL);
int hwnd = method.callInt(new Object[]{null,"DDE
Server Window"});
The
returned CMethod object
is already native and maps the specified C method, the callInt method execute a native call from
Java. Note the .dll extension is removed in “User32” DLL name, the DLLManager.get(String) method can follows the System.loadLibrary(String) conventions: no extension and “lib”
prefix (in Unixes) must be used, unless an absolute path is used.
JNIEasy
uses java.library.path
system property to specify where to look for dynamic libraries/shared objects
(unless an absolute path is specified). This system property is usually set
with the parameter –Djava.library.path on the command line.
Alternatively
JNIEasy allows to specify java.library.path on runtime with the call:
JNIEasy.setFeature("java.library.path","pathList");
This call
doesn’t change the real java.library.path system property (mandates JNIEasy to ignore
the system property and use the specified path list).
C global
exported variables or C++ exported static fields can be accessed as native
field-methods; there is no need to exist a “real” native field-method in the
DLL.
The
following example declares a global C variable exported in MyLibrary.dll/libMyLibrary.so/libMyLibrary.dylib:
// Any C or C++ .h file
extern "C" DLLEXPORT int aGlobalVar;
// Any C or C++ .c/.cpp file
int aGlobalVar;
Where DLLEXPORT macro is defined in JNIEasy.h as __declspec(dllexport) in Win32 compilers (VisualC++, MinGW,
cygwin) and none in gcc Linux, Mac and Solaris (by default gcc on unixes
exports all symbols[4]).
This
variable is mapped from Java with a field-method native object.
The Java
side:
DLLManager dllMgr = JNIEasy.get().getDLLManager();
DynamicLibrary dll = dllMgr.get("MyLibrary");
CFieldMethod fieldMethod =
dll.addCFieldMethod("aGlobalVar",int.class,CallConv.STD_CALL);
int value = fieldMethod.callInt(NativeFieldMethod.GET,0); //or .getInt();
value += 10;
fieldMethod.callInt(NativeFieldMethod.SET,value)
// or .setInt(value);
Another way
to create a proxy of a native method is using NativeConstructor, NativeStaticMethod, NativeInstanceMethod, NativeStaticFieldMethod and NativeInstanceFieldMethod interfaces. Instances implementing
these interfaces may be created using a native capable method signature. The
native capable signature can be created with the NativeSignatureManager utility object, this object is
obtained with the method JNIEasy.getSignatureManager().
In the
following example the native method address is obtained with the method DynamicLibrary.getAddress(String), another scenarios are possible
(including fields of structures declared as pointer to method)
DynamicLibrary dll = JNIEasy.get().getDLLManager().get("User32");
long methodAddress = dll.getAddress("FindWindowA");
NativeSignatureManager sigMgr = JNIEasy.get().getSignatureManager();
NativeStaticMethodSignature sig = sigMgr.decStaticMethod(int.class,
new Object[]{String.class,String.class},CallConv.STD_CALL);
NativeStaticMethod method =
(NativeStaticMethod)sig.attachBehavior(methodAddress);
int hwnd = method.callInt(new Object[]{null,"DDE
Server Window"});
The
returned NativeStaticMethod
object is already native and maps the specified C method, the native call using
the method callInt, invokes
the native method in User32.dll from Java.
A special
case is field-methods, the native address must be, really, the address of a
native field-method. The following address:
long variableAddress =
dll.getAddress("aGlobalVariableOrStaticExportedFieldName");
is not a
valid address to attach a NativeStaticFieldMethod, but the address of a CFieldMethod (obtained using NativeCapableUtil.getAddress(Object))
is a valid field-method address.
A native
capable object of these types may be created using a native method signature
and a Java native capable method to call, this method (the method to be finally
called) is represented by a reflection object (Method). A direct or reflection callback
maps the Java method and can be passed to the native side (because they are
native capable objects), the native side sees them as pointer-to-method, if
this pointer-to-method is called the Java method is finally called, direct and
reflection callbacks work as intermediaries in the calling process. A native
object callback allocates native memory and has an address, this address is the
native “pointer” of the Java method (of course is an illusion, it is the
address of a native method created on the fly working as a bridge); this memory
is freed when the object is garbage collected.
Example:
// Static method:
java.lang.Math.max(int,int)
Method method = Math.class.getDeclaredMethod("max",
new Class[]{int.class,int.class});
NativeSignatureManager
sigMgr = JNIEasy.get().getSignatureManager();
NativeMethodSignature sig =
sigMgr.decMethod(method,CallConv.STD_CALL);
Creation of
a new reflection based callback and invoking it with a native call from Java:
NativeManager natMgr = JNIEasy.get().getNativeManager();
NativeStaticMethodReflection objReflect =
(NativeStaticMethodReflection)sig.newMethodReflection(method);
natMgr.makeNative(objReflect);
int max = objReflect.callInt(new Object[]{new Integer(5),
new Integer(3)});
System.out.println("Must
be 5: " + max);
Creation of
a new direct based callback and invoking it with a native call from Java:
NativeDirectStaticMethodCallback objDirectCb =
(NativeDirectStaticMethodCallback)sig.newDirectMethodCallback(method);
natMgr.makeNative(objReflect);
int max2 = objDirectCb.callInt(new Object[]{new Integer(5),new Integer(3)});
System.out.println("Must be 5: " + max2);
The
objects, objReflect and objDirectCb, wrap the specified Java method (Math.max(int,int)) and make it visible from native.
The reflection object (objReflect) uses reflection to call the method and the direct object (objDirectCb) uses a normal Java call (this
object is an instance of a generated Java class “on the fly” if necessary). In
this example the Java method can be called from native with a pointer with the
signature: int (*)(int,int)
The method NativeManager.makeNative(Object) has been used the first time, this
method converts a native capable object to a native object, after the call the
Java object is associated to a chunk of native memory, in this case the cited
native method created on the fly working as a bridge.
Following
the example:
long address = NativeCapableUtil.getAddress(objReflect);
NativeStaticMethod proxy =
(NativeStaticMethod)sig.attachBehavior(address);
int max3 = proxy.callInt(new Object[]{new Integer(5),new Integer(3)});
System.out.println("Must
be 5: " + max3);
Using these
techniques, native Java callbacks can be tested using Java only.
Java native
capable fields, static or instance, can be exported to the native world using
direct and reflection native callbacks too. The fields do not need to be
declared native, because reflection or direct access is used, in the case of
instance fields, the container class must be native capable and a native
instance must be used, but the field itself may be non-native (but the field
type must be native enabled).
Example:
// Accessing the public final static field:
java.lang.Math.PI
Field field = Math.class.getDeclaredField("PI");
NativeSignatureManager
sigMgr = JNIEasy.get().getSignatureManager();
NativeFieldMethodSignature
sig =
sigMgr.decFieldMethod(field,CallConv.STD_CALL);
NativeManager natMgr =
JNIEasy.get().getNativeManager();
// Using reflection
NativeStaticFieldMethodReflection
objReflect =
(NativeStaticFieldMethodReflection)sig.newFieldMethodReflection(field);
natMgr.makeNative(objReflect);
double Pi1 = objReflect.getDouble();
System.out.println("Must be true: " + (Pi1 == java.lang.Math.PI));
// Using a direct access
NativeDirectStaticFieldCallback
objDirectCb =
(NativeDirectStaticFieldCallback)sig.newDirectFieldCallback(field);
natMgr.makeNative(objDirectCb);
double Pi2 = objDirectCb.getDouble();
System.out.println("Must be true: " + (Pi2 == java.lang.Math.PI));
Of course these native objects can be passed to
the native side as pointer-to-methods following the field-method signature, the
native side can access the Java fields calling these field-methods.
The NativeStaticMethodReflection interface is mainly used in Java
callbacks invoked using reflection, the native instance holds a Method reference and this reflection
object is used to call to the Java method. But there is an exception the native
instance does not hold a Method reference, the internal reflection object is
not defined, and we try to get something with a call to NativeStaticMethodReflection.getMethod()… in this case the framework is
instructed to construct a new java.lang.reflect.Method object representing the native method with the
address of the native instance, the
native method can be called using this reflection object.
Example:
DynamicLibrary dll = JNIEasy.get().getDLLManager().get("User32");
long methodAddress = dll.getAddress("FindWindowA");
NativeSignatureManager sigMgr = JNIEasy.get().getSignatureManager();
NativeStaticMethodSignature sig = sigMgr.decStaticMethod(int.class,
new Object[]{String.class,String.class},CallConv.STD_CALL);
TypeNativeStaticMethod type =
sig.decStaticMethod(NativeStaticMethodReflection.class);
NativeStaticMethodReflection obj =
(NativeStaticMethodReflection)type.newValue();
JNIEasy.get().getNativeManager().attach(obj,methodAddress);
Method method = obj.getMethod();
Integer hwnd = (Integer)method.invoke(null,
new Object[]{null,"DDE
Server Window"});
System.out.println("Handle: " + hwnd.intValue());
This feature
is transparent to the user reading a java.lang.reflect.Method field declared as a static method
in a user defined Java class declared as a structure (C++ class or union), this
field represents a “native pointer to method”, if this field was not set (from
Java) before a first read, the Method object read represents the native method in the DLL.
If using
the NativeInstanceMethodReflection interface in a similar scenario, the Method object returned with NativeInstanceMethodReflection.getMethod()
is static, the C++
instance method can be called using as first parameter the native instance.
The NativeConstructorReflection
interface works as
expected; the java.lang.reflect.Constructor can be used to call the C method declared as
constructor.
The
interfaces NativeInstanceFieldMethodReflection and NativeStaticFieldMethodReflection do not work as proxy and an
exception is thrown, the java.lang.reflect.Field can not be used as a proxy of a field-method.
An instance
of a user defined Java native capable class (previously enhanced) works like an
instance of the non-enhanced version, until the instance is made native. Instances
can be created with the Java new sentence using the selected constructor by the user, this instance is
not native (is a native capable object).
Example:
MyStructure
struc = new MyStructure();
User
defined Java classes will be explained later.
The runtime
life cycle of a native capable object is very simple, there are only two
states: native and non-native.
Every native
capable class has, at least, two protected transient instance fields defined by
the framework (added by the enhancer if the class is user defined). If the
class inherits from another native capable class, both fields are present in
the top most native capable class:
·
TypeNative jnieasyType
References the native type
declaration to be used by the object when native. The referenced native type
object is ever present although the object is not native. It can be obtained
with the method NativeCapable.jnieasyGetType().
·
NativeStateManager jnieasyNativeStateManager
References the native state manager
object. This per-instance object manages the native state of the native
instance. It can be obtained with the method NativeCapable.jnieasyGetNativeStateManager() or with NativeCapableUtil.getNativeStateManager(Object)
A native
capable object is native when a NativeStateManager object is associated, the jnieasyGetNativeStateManager() method or returns this object , if
not native returns null.
First of
all the framework must be loaded/initialized:
JNIEasy jniEasy = JNIEasy.get();
jniEasy.load();
This sentence loads the JNIEasy’s dynamic
library/shared object (JNIEasy.dll on Windows, libJNIEasy.so on Linux and Solaris, libJNIEasy.jnilib on Mac OS X).
On loading, the framework needs to locate the
product’s license file, JNIEasy.lic, this file is looked for in the directory specified with the optional JNIEASY_LICENSE_DIR Java property (specified with a –D flag on command line), otherwise the
current directory is looked for.
Optionally the license directory can be specified
programmatically using the method JNIEasy.setFeature(String,Object) and the feature “jnieasy.license.dir” before calling the load() method:
JNIEasy jniEasy = JNIEasy.get();
jniEasy.setFeature("jnieasy.license.dir","some path");
jniEasy.load();
There are
several ways to make native a native capable object:
The method NativeManager.makeNative(Object)
makes native the native capable object specified. This call allocates
the necessary native memory, creates a new NativeStateManager object to manage this memory,
associates to the object, and “unfetches” the values of contained native fields
to the native memory recursively (a “deep” unfetch). After this call the object
and referenced by fields (reachable) native capable objects are made native and
the allocated memory is in sync with the field values before the call.
The
primitive fields are simply copied to the matched native memory.
An object
referenced by a native field with a native capable class type, is made native:
1. Allocating native memory if the
field is declared “by pointer”: the address of the new memory block is set to a
pointer of the container’s memory according to the native memory layout.
2. Or attaching to the start of a
memory part of the container’s native memory if the field is declared “by
value”, according to the native memory layout.
3.
If
the native field type is a “can be native class” (e.g. String, int[])
an internal auxiliary native object is used holding the object
referenced by the field. This auxiliary object is made native as explained
before.
4.
The
native instance owns its related native memory; the pair native
instance-address is registered internally by the framework.
Only one object can be
the owner of a native address.
A shortcut
to obtain the native address is invoking the static method NativeCapableUtil.getAddress(Object); the same instance is returned by
the method NativeManager.findObject(long)using the previously returned address (it only works if the object owns
the memory associated).
If the
framework needs a native object to represent a native address, first looks in
the internal registry to locate the native object owning this address, if not
found creates a new native capable object (with the expected type) and attaches
it to the address.
Example:
Consider
this class declared as a C structure in the XML enhancer descriptor of the
class (only fields are declared as native, no methods):
package examples.manual;
import com.innowhere.jnieasy.core.*;
import com.innowhere.jnieasy.core.data.*;
import com.innowhere.jnieasy.core.factory.*;
import com.innowhere.jnieasy.core.mem.*;
public class MyStructure
{
protected int anInt = -1;
protected NativeIntegerArray intArr1;
// Declared "by pointer" length undefined
protected NativeIntegerArray intArr2;
// Declared "by value" length 2
protected int[] intArr3 = new int[]{5,6};
//
Declared "by pointer" length 2
protected int[] intArr4 = new int[]{7,8};
// Declared "by value" length 2
public MyStructure()
{
NativeCapableFactory
factory =
JNIEasy.get().getNativeCapableFactory();
intArr1 =
(NativeIntegerArray)factory.wrapValue(new int[]{1,2});
intArr2 =
(NativeIntegerArray)factory.wrapValue(new int[]{3,4});
}
}
An example
of use:
MyStructure obj = new MyStructure();
NativeManager natMgr = JNIEasy.get().getNativeManager();
natMgr.makeNative(obj);
long address = NativeCapableUtil.getAddress(obj);
Object obj2 = natMgr.findObject(address);
System.out.println("Must
be true: " + (obj == obj2));
Supposed a
32 bit platform, the native memory layout after made native is:
A native
capable object may be attached to a previously known (supposed valid) native
address and memory zone starting in this address, corresponding to the expected
memory layout of the instance. When attaching, a new NativeStateManager object is created, attached to the
native address and associated to the object (now native).
The attached native instance does not own its
related native memory.
Several native objects may be attached
to the same native address and they are not registered as “owners” of the
address. A native instance may be attached to an address owned by another
native instance (created with NativeManager.makeNative(Object)); the framework tries to avoid this
because it looks first for a native instance owning the address, but the user
can attach explicitly.
There are
two ways to attach a native capable object to a native address:
The method NativeManager.makeNative(Object,long) is used. The current values of the
native fields are mandatory over the native memory, and native memory is
overwritten (“unfetched”) following the rules explained before in the Allocating native memory section using the method NativeManager.makeNative(Object). The differences are: no new native
memory is allocated and the attached native object does not own the associated native
memory/address.
Example:
MyStructure original = new MyStructure();
NativeManager natMgr = JNIEasy.get().getNativeManager();
natMgr.makeNative(original);
original.setIntArray3(null);
System.out.println("Must be null: " +
original.getIntArray3());
long address = NativeCapableUtil.getAddress(original);
MyStructure attached = new MyStructure();
natMgr.makeNative(attached,address);
attached.setIntArray3(new int[]{1,2});
/* Modifies the native memory of “original” */
System.out.println("Must
be 1: " + original.getIntArray3()[0]);
The method NativeManager.attach(Object,long) is used. The native memory state is
mandatory over the fields, but there is no fetch of the native memory to
fields, because when a Java field is read, the field is synchronized before
with the native value (the Java value is set with the native value, native
memory is fetched). Using this lazy synchronization increases the performance if
a small part or no fields are used and ensures the field value read is ever the
current native memory value: native memory can be modified from native in any
time, a very important feature in a multithread programming world. Of course
any Java field updated writes the new value to the native memory (there is no
difference between an object attached or owning the native memory when fields
are read and written). To ensure the Java fields are fully synchronized with
the native memory, the method NativeManager.fetch(Object,long) can be called after the attach call.
Example:
MyStructure original = new MyStructure();
NativeManager natMgr = JNIEasy.get().getNativeManager();
natMgr.makeNative(original);
original.setIntArray3(new int[]{1,2});
System.out.println("Must be 2: " + original.getIntArray3()[1]);
long address = NativeCapableUtil.getAddress(original);
MyStructure attached = new MyStructure();
natMgr.attach(attached,address);
System.out.println("Must be 2: " + attached.getIntArray3()[1]);
In the
previous cases the referenced native capable objects of native fields are made
native by reachability when the container object is made native or when is
first accessed if attached (without unfetch).
Following
the previous example:
public class MyStructure
{
...
public NativeIntegerArray getIntArray1()
{
return intArr1;
}
public void setIntArray1(NativeIntegerArray intArr1)
{
this.intArr1 = intArr1;
}
}
MyStructure obj = new MyStructure();
NativeManager natMgr = JNIEasy.get().getNativeManager();
natMgr.makeNative(obj);
NativeCapableFactory factory = JNIEasy.get().getNativeCapableFactory();
NativeIntegerArray newValue =
(NativeIntegerArray)factory.wrapValue(new int[]{10,11});
obj.setIntArray1(newValue);
The new
value, newValue, is not
native before updating the intArr1 field; the new referenced object will be made native automatically
before the field is set, because the field is native and the method code is
managed by the enhancer.
The native
fields of user defined native capable classes must not be accessed directly
outside the class, because any access must be done on managed (enhanced) code
to be policed by the enhancer, currently only the instance member methods and
constructors of the native capable class are enhanced (managed code), any
access of the native fields is monitored and synchronized, if necessary, with
the native memory.
The fields
of predefined classes are ever accessed using the implemented methods of the
related native interface; these methods synchronize the fields with the native
memory as the enhancer does with user defined classes.
The native
memory is ever mandatory over the current values of the fields. When a native
field is read, the native memory is ever
read first, and the field value is modified with the current matched value of
the native memory, although the field has not been modified after the last read
on the Java side. Any reading action must be seen as “I want to read the native
memory”, because the “Java state” may remain the same between readings, but the
native memory may have changed in the native side, or with direct access to the
native memory using the NativeBuffer, into the same or another thread (the threads synchronization is a developer’s work).
Although
the field is read from the user point of view, the field value is updated if
native memory is different to the current field value.
The
following descriptions apply to fields of user defined native capable classes.
The default fetch mode is supposed to be “fast”.
The native
memory is read and copied to the field and returned.
The matched
native memory of the field is a pointer.
a)
If
the address value is 0 (null): the field is set to null and the current native object referenced, if any, is lost (this object
remains unchanged, if not referenced any more is garbage collected).
b)
If
the address value is not null : the address is compared with the address of the current referenced native
object (if any), if equal the current referenced object is returned, if not
equal, the framework search in the internal registry the native instance owning
the address, if found this object is returned (the object pointed) and the
current object is discarded, if not found and the current object is native then
is discarded (pointer has changed) and a new attached object to the address is
returned, else (if not native) the current object is attached to the address
and returned.
The matched
native memory is a part of the container object’s memory, the address is the
start of this memory part (is managed by the framework as the pair container’s
address - relative offset of the field). The field needs to reference a native
object attached to this address (managing this memory part), if the field is null a new object is automatically created
with the expected type, attached and returned. If the current referenced object
already exists and is already native (then attached to the matched memory part),
it is returned as is, and no synchronization of the instance and the native
memory is performed (if the default fetch mode is set to “fast”), because any
access to the fields of the returned object will perform this synchronization.
In brief:
the native object attached to the matched native memory part is returned, and is
previously created and attached if necessary.
An auxiliary
native object linked to the field, implementing the CanBeNativeCapable interface, is used; this auxiliary object
is managed by the container’s NativeStateManager object. This auxiliary object is managed
following the same rules of a field with a native capable class “by pointer”,
as if the field type was a native capable class referencing this auxiliary
object. This object is not finally returned because is expected a “can be
native” object (a String, int[] etc.), the
auxiliary object, if not null, is used to obtain the “can be native” object with the same value of
the native memory “pointed” calling the method NativeSingleFieldContainer.getValue(int) using the default fetch mode
(“fast” by default).
In this
scenario the field must be not null, because the referenced object represents the always present related
memory part of the container’s native memory (when native of course), otherwise
an exception is thrown.
When
reading, an auxiliary object is needed too, as the “by pointer” case, but this
object is attached to the matched native memory part represented by the field,
following the same rules of a field with a native capable class “by value”, and
never is discarded. Because the current “can be native” object is never null, the auxiliary object used to
obtain the expected “can be native” object is never null too (is created if necessary), and
again the “can be native” object to return is obtained with the method NativeSingleFieldContainer.getValue(int) using the default fetch mode
(“fast” by default), the returned “can be native object” is previously
synchronized with the matched native memory part; if the “can be native” object
is modifiable (int[] etc) the current but modified object is returned, else a new object is
created, synchronized and returned.
Again the
framework supposes the native memory may be update in the native side, or using
direct access to the native memory using the NativeBuffer. When a native field is updated,
the native memory is ever updated
too although the new value is the same as the current value of the field, this
ensures that the native memory is really updated. Any updating action must be
seen as “I want to update the native memory”, because the “Java state” may
remain the same but the native memory may have changed indirectly. A valid
technique is to update a field with the same value to ensure the native memory
is set with this value.
The
following descriptions apply to fields of user defined native capable classes.
The default “unfetch” mode is supposed to be “fast”.
The field
and related native memory are updated with the new value.
The matched
native memory is a pointer. If the new object value is null the pointer is set to 0, else the new
object is made native, if necessary, and its address is set as the value of the
pointer in the native memory. Any previously referenced object by the field is
unreferenced.
The matched
native memory is a part of the container’s memory. The new object can not be null (otherwise an exception is thrown),
if the object is not native is made native with an unfecth-attach otherwise the
native memory is not modified (if the default “unfetch” mode is “fast”).
Updating a
“by value” field must be used to update the container object managing the matched
native memory part, but not the native memory itself, because the field value
(the manager of the memory part) may be used itself to update the native
memory, using its methods or passing as a parameter to NativeManager.unFetch(Object).
If the new
value is null the matched
native pointer is set to 0 and the current auxiliary object is discarded, else
if the auxiliary native object is missing, a new object is created and made
native wrapping the new “can be native” value (the native memory of the new
object is updated with the “can be native” value), else if the new value is not
equal to the current field value, the current auxiliary object is discarded and
a new auxiliary object is created and made native wrapping the new value.
Unless the new value is null, the native address of the final auxiliary object is used to update the
native pointer, else is set to 0. The native memory is ever updated although
the new value is the same as current value because the native pointer may be
changed or in the native side, or using direct access to the native memory.
The
auxiliary object is used, it is created and unfetch-attached if was not; this
object is used as the manager of the matched native memory part and never is
discarded as said in “reading”. The new “can be native” value is used to update
the native memory part using the auxiliary object with the default “unfetch” mode.
Note that
the native memory is ever updated although the new value is identical to the
current value because the native memory may have been updated indirectly.
The
enhancer does not control the updating of an element of a Java array field.
Considering
the following code:
public class MyStructure
{
...
protected int[] intArr3 = new int[]{5,6};
// Declared "by pointer" length 2
...
public int[] getIntArray3()
{
return this.intArr3;
}
public void setIntArray3(int[] intArr3)
{
this.intArr3 = intArr3;
}
public void setIntArray3(int index,int value)
{
this.intArr3[index] = value;
}
}
The fields
access “this.intArr3[index]” implies a native memory read updating the current Java array value
with the native memory. But the element modification is not detected by the
enhancer and no native memory update is performed.
Several
solutions to this problem:
1.
Updating
the method as:
public void setIntArray3(int index,int value)
{
int[] aux = this.intArr3;
aux[index] = value;
this.intArr3 = aux;
}
The last sentence ensures the native memory is
updated with the whole array.
2. Re-setting the modified array:
MyStructure obj = ... // a native instance
int[] arr = obj.getIntArray3();
arr[index] = ... // element modifications
obj.setIntArray3(arr); //
updates the whole array
3. Using a corresponding native class
array wrapper as field and use “by element access” methods:
protected NativeIntegerArray intArr3;
The NativeIntegerArray interface has the method void
setInt(int index,int value) updating only the selected element in native memory. The method int
getInt(int index) is
more performant too because only the selected element is read from native
memory.
This alternative increments the performance and
control but decreases the transparency, is only recommended with big arrays
used with frequent access to the elements.
The
enhancer does not control the updating of a StringBuffer using its methods, use similar
techniques seen with Java arrays.
The CanBeNativeCapable objects enable the developer to
read from and write to native memory “can be native” objects. These utility
objects are used as auxiliary objects to manage “can be native” fields behind
the scenes; these objects can be used explicitly too.
When a NativeString object is made native allocating
memory, the current String value is copied (using the specified encoding) in the native memory,
the memory size is the string length + 1 (the '\0' character) multiplied with the
character size, this memory size can not change. If the object is attached and
no String was set
before, in this case the memory size is undefined, this case is specially
useful on reading strings with unknown length (the NativeString object is attached to native memory
and the length is unknown), usually using the method NativeString.getString().
When
reading, the current String object is returned if the native memory contains the same string as the
current String object,
else a new String is
returned.
When
writing, usually using the method NativeString.setString(String), the new value is written in the
native memory, but the string length can not be greater than the length of the original String value immediately before the
container is made native allocating memory, unless the object was attached and
no String was set
before (memory size is undefined), in this case the framework can not check an
out of bounds writing.
The
behavior is mainly the same as NativeString objects, with only one difference: when reading, the returned StringBuffer is ever the current object because
this object is modifiable.
With the
methods getCharacter(int index)and setCharacter(int index,int value), individual characters can be read/modified,
this supposes a performance gain instead of reading/updating the complete
string. Furthermore using these methods can be read/updated individual
characters of very big native strings without duplication in Java: if a NativeStringBuffer object is attached to a known
address and no StringBuffer was set before, the memory size is unknown and no internal StringBuffer object was created (no duplication
of the string), the methods getCharacter(…) and setCharacter(…) can be used to access individual
characters without having read/update the whole native string; if the method getStringBuffer()
is used the whole
native string is read (supposed that a final '\0' exists) and duplicated inside an
internal StringBuffer
object.
In a similar
fashion as NativeStringBuffer
objects, any native object implementing the NativeArray interface can deal with undefined
length arrays: if an object is attached to a known address and no Java array
was set before, the memory size is unknown and no internal Java array is hold,
and can not because array length is unknown, the methods like NativeObjectArray.getObject(int
index), NativeObjectArray.setObject(int
index,Object value), NativeIntegerArray.getInt(int index), NativeIntegerArray.setInt(int
index) and so on, can
be used to read/update individual elements without read/update the entire
array.
These
objects implement the interface NativePrimitiveArray. Primitive arrays are optimized to
read and write big arrays, because the copy operation between native memory and
Java array (both directions) is realized using the complete memory block of the
array. Remainder array classes read and write element by element.
These objects
implement the interface CanBeNativeCapableArray, a native instance uses internally an
auxiliary array (with the same length) of auxiliary native objects. The
relation of the pair array element-auxiliary native objects follow the same
relationship explained on “can be native” fields of user defined classes; the
processing is different if the array holds elements “by value” of “by pointer”.
With
undefined length arrays the auxiliary array dynamically increase until
necessary to manage the selected element read/updated.
“Fetch” is
the operation to read from native memory and copy to Java memory. “Unfetch” is
the operation to write the native memory with the data copied from Java memory.
All reading
and writing operations between Java and native memory have any kind of
“deepness”. There are four types of deepness:
1. NONE: there is no copy between Java
memory and native memory, the reading and writing operations are performed on
Java memory only. The NONE mode may be used to read or update Java fields with
no native memory interaction.
2. FAST: only field members with
primitive types, “can be native” class types and the address of fields with
native capable class types “by pointer”, are synchronized with the native
memory; fields with native capable class types “by value” are not synchronized
because the native “embedded” object can be used, itself, to access the
associated native memory. The referenced native object of a field with a native
capable class type declared “by pointer”, is not synchronized itself, only the
object’s address and the related pointer in the container’s native memory are
synchronized.
3. EMBEDDED: the native objects
referenced by fields with native capable class types declared “by value”
(native embedded objects) are synchronized, themselves, with the related native
memory too.
4. DEEP: the native objects referenced
by fields with native capable class types declared “by pointer” (native linked
objects) are synchronized, themselves, with the related native memory too. All
members of the native object are synchronized recursively. Cyclic references
are detected with no problem.
The following table shows if a field is synchronized (fetched/unfetched)
with the native memory if accessed (read/write) depending on the fetch/unfetch mode
and field type:
|
Native Field Type |
NONE |
FAST |
EMBEDDED |
DEEP |
|
Primitive |
NO |
YES |
YES |
YES |
|
“Can be native” (by value & by pointer) |
NO |
YES |
YES |
YES |
|
Native capable by pointer |
NO |
YES, address only |
YES, address only |
YES, referenced object’s fields with DEEP mode too |
|
Native capable by value (referenced object
already attached) |
NO |
NO |
YES, the referenced object’s fields with EMBEDDED
mode too |
YES, the referenced object’s fields with DEEP mode
too |
The default
mode to fetch and “unfetch” is FAST, the fast mode ensures a reasonable
synchronization of Java memory and native memory.
The Fetch y UnFetch interfaces define the constants
used to specify the fetch and “unfetch” modes.
The
developer can:
a) change the default fetch and
“unfetch” mode of every read and write operation with the methods NativeManager.setDefaultFetchMode(int) and NativeManager.setDefaultUnFetchMode(int)
b) use the fetchMode or unFetchMode parameters present in multiple read/write
methods of NativeCapable
inherited interfaces
c) execute a fetch or “unfetch”
operation over a native instance with the methods NativeManager.fetch(Object
obj,int mode) and NativeManager.unFetch(Object
obj,int mode).
If the
native instance owns its native memory (was made native with NativeManager.makeNative(Object)); this native memory is
automatically freed if the native instance goes out the scope and is reclaimed
by the garbage collector. This is realized by the finalize() method of the internal
implementation of NativeStateManager, the finalize method of the native instance is not used and the
developer can use it with freedom in user defined classes.
There are
two methods to free the native memory of a native object explicitly:
NativeManager.free(Object)
NativeCapableUtil.free(Object)
Both
methods are roughly equivalent: if the object owns its native memory, the
native memory is freed else does nothing, in both cases the native instance is
made not native. The embedded native object fields are freed too (native
objects pointed by fields “by pointer” are untouched).
There are
two methods to detach a native object from the native memory without freeing
it:
NativeManager.detach(Object)
NativeCapableUtil.detach(Object)
Both
methods are roughly equivalent too: the object is detached from the related
native memory and made not native. The embedded native object fields are
detached too (native objects pointed by fields “by pointer” are untouched). The
developer must free the detached native memory if necessary (taking care with
memory leaks).
To free
(and make not native) an entire object tree (freeing linked objects too) the
following method can be used:
NativeManager.detach(Object obj,int freeMemMode,boolean deep)
with the
parameters freeMemMode set
to FREE_MEMORY and deep to true.
The
developer can define several types of user defined classes mapping native
constructions:
·
C++
classes
·
Structures
(C and C++ based)
·
Unions
(C and C++ based)
·
Wrappers
of native capable arrays
·
Wrappers
of multidimensional arrays (arrays of arrays)
·
Pointer
types (wrappers of pointers)
·
Direct
callbacks
Any other
native construction is solved with a predefined class of the framework, in
fact, the user defined array wrappers and pointer classes are not needed at all
because generic predefined classes are used by the framework to wrap the
contained elements, but user defined classes of these types enable the
developer to avoid castings mainly.
The user
defined classes must be enhanced to be used by the framework using a XML
descriptor (describing the “native view” of the class); the enhancement
converts them in native capable classes. This enhancement can be made before using
in runtime or “on class loading”, in this last case the XML descriptor must be
present alongside the .class file.
C++
classes, structures and unions are basically the same in C++ and in JNIEasy’s
Java classes: they can have fields, constructors, methods, and inheritance
(except unions). C methods or C++ static methods are basically the same: they can
be mapped with Java methods inside (of course) Java classes.
In JNIEasy
a user defined native capable class is a normal Java class, a POJO; this class
can have declared native elements: fields, methods and a base class. A declared
native element must use native enabled types and must be declared in XML as
native (or declared by default as native like fields), elements not declared as
native are not managed by the framework and do not have a “native correspondence”.
Example: a non-native field is not reflected in native memory, a non-native
method is not a proxy of a DLL method or exported to the native world as a
native callback.
The MyStructure class seen before is an example of
a simple C/C++ structure in Java:
package examples.manual;
import com.innowhere.jnieasy.core.*;
import com.innowhere.jnieasy.core.data.*;
import com.innowhere.jnieasy.core.factory.*;
import com.innowhere.jnieasy.core.mem.*;
public class MyStructure
{
protected int anInt = -1;
protected NativeIntegerArray intArr1;
// Declared "by pointer" length undefined
protected NativeIntegerArray intArr2;
// Declared "by value" length 2
protected int[] intArr3 = new int[]{5,6};
// Declared "by pointer" length 2
protected int[] intArr4 = new int[]{7,8};
// Declared "by value" length 2
public MyStructure()
{
NativeCapableFactory
factory =
JNIEasy.get().getNativeCapableFactory();
intArr1 =
(NativeIntegerArray)factory.wrapValue(new int[]{1,2});
intArr2 =
(NativeIntegerArray)factory.wrapValue(new int[]{3,4});
}
...
}
The XML
enhancer descriptor may be:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyStructure.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="MyStructure" type="structure">
<field name="intArr2" varConv="byValue" length="2" />
<field name="intArr3" length="2" />
<field name="intArr4" varConv="byValue" length="2" />
</class>
</package>
</jniEasyEnhancer>
Note the
absence of declarations of anInt and intArr1 fields, in a XML enhancer descriptor all instance fields are considered
as native by default (unless explicitly excluded or qualified with the transient keyword), and default
configurations are valid to these fields: primitives do not need special native
information, and object based fields are declared “byPointer” by default and array fields “byPointer” have undefined length by default.
The code of
instance methods is ever enhanced and managed by the framework to detect any
use of native declared instance fields (to synchronize with the native memory).
If the following method was added to MyStructure:
public void addToAnInt(int value)
{
this.anInt = this.anInt
+ value;
}
This method
increments the related native memory with value without dependence of the current
value of anInt field,
because inside “= this.anInt +” part, the this.anInt access makes a transparent read of
the native memory and updates the field before the addition, then its value is
added to value and
finally the temporal result is set to the anInt field (“this.anInt = …”) updating the native memory too.
The “this.”
qualification is optional in this example (used to enforce the idea of using
the field).
The
previous method body is equivalent (and works the same), to:
this.anInt += value;
Because in
the bytecode level there is no difference (the += operator is expanded).
Out of the enhanced
Java class the fields are accessed without management/interception and native memory
is not involved; encapsulation of any
external access to the fields with get/set methods is strongly encouraged to
encapsulate any external access to the fields with get/set methods.
In this
manual, most of the time, “real” C/C++ constructions and types and ignored,
because any valid JNIEasy construction, usually using Java primitive types, String, StringBuffer, arrays of these types, and user defined classes with methods and
fields using these types, has a corresponding C/C++ construction and the
opposite. Because the Java centric nature of JNIEasy, the developer must select
the Java type matching the C/C++ type using the JNI definitions of the target
platform. Example: in a 32 bit platform the jint type is the C int type, jint of course matches ever with the
Java int type, the C char type matches with the Java byte type, a C int[] array (or int* pointing an array) matches with
Java int[] and so on[5]. The
developer has the option to use NativeIntegerArray instead of int[] , for instance, to take more
control of the native management, int[] and NativeIntegerArray are interchangeable, same is
applied to any other pair of “can be native” class and native capable wrapper.
In a 32 bit
platform the matched C++ structure and initialization of MyStructure would be:
// MyStructure.h
struct MyStructure
{
int anInt;
int* intArr1;
int intArr2[2];
int* intArr3;
int intArr4[2];
MyStructure()
{
anInt = -1;
intArr1 = new int[2];
intArr1[0] = 1; intArr1[1] = 2;
intArr2[0] = 3; intArr2[1] = 4;
intArr3 = new int[2];
intArr3[0] = 5; intArr3[1] = 6;
intArr4[0] = 7; intArr4[1] = 8;
}
};
A C/C++
program accessing a MyStructure C/C++ structure with a MyStructure* pointer could access the native memory created with a native instance
of the Java version or an object created on the native side could be accessed
from Java attaching a MyStructure Java object to the address.
JNIEasy
supports the “by value” convention to pass as a method parameter a structure,
class, union or array (the last is not supported by C/C++ but may be useful to
the developer in special cases). When using the “by value” convention the
native memory of the formal parameter (the receiver) is a copy of the original
argument value; in the structure, class or union cases the constructors of the
copy are not invoked and virtual methods may be not valid, but non-virtual
methods and fields can be accessed.
The memory
alignment of structures/classes/unions is not an issue unless specific
alignment rules are used in C/C++ declarations (using #pragma pack or __declspec(align(N)) in MSVC[6], and __attribute__((aligned(N))) in gcc).
To know the
alignment of a C/C++ data type, the expressions __alignof__(type) in gcc and __alignof(type) in MSVC, can be used.
To know how
a C++ instance field is aligned use the following macro:
#if
defined(_MSC_VER) && (_MSC_VER >= 1300) && defined(__cplusplus)
#
define FIELD_ALIGNMENT(type, field) __alignof(((type*)0)->field)
#elif
defined(__GNUC__)
#
define FIELD_ALIGNMENT(type, field) __alignof__(((type*)0)->field)
#else
/*
FIXME: Not sure if is possible to do without compiler extension */
#endif
Several
methods can be used to obtain alignment info of JNIEasy’s native elements:
·
NativeTypeManager.alignSizeOf(Class) : returns the preferred alignment
size declared in the specified native enabled class.
·
TypeNative.preferredAlignSize() : returns the preferred alignment
size of the native type.
·
NativeClassDescriptor.alignSize() : returns the effective alignment
size of a structure type.
·
NativeFieldStructureDescriptor.alignSize() : returns the effective alignment
size of a concrete structure (class, union) field.
JNIEasy has
four levels to control the memory alignment:
1. Global alignment specification
The method NativeTypeManager.setStructureAlignSize(long) can be used to set the default
memory alignment of structures, classes and unions; this value is imposed if
lower than the preferred alignment of
any field.
The method NativeTypeManager.getStructureAlignSize()
can be used to get the
current default alignment size. The default value is 8 bytes.
2. Per structure alignment
Using the optional XML enhancer attribute alignSize in the class declaration.
Example:
<class name="MyStructure2" type="structure" alignSize="4">
...
3.
Per
field alignment
Using the optional XML enhancer attribute alignSize in the field declaration.
Example:
<field name="someField" alignSize="1"> ...
Another
option is the prefAlignSize XML attribute of primitive types, this attribute is explained in the
chapter Cross-platform
options and XML attributes of Primitive types.
Besides
user defined capable classes working as unions, several contiguous fields can
be grouped forming an anonymous union, the memory of union fields is shared.
A C
example:
struct Dimension
{
int type;
union
{
int x;
double y;
};
const char* desc;
};
The
symmetric Java class:
package examples.manual;
public class Dimension
{
protected int type;
protected int x; //
start union
protected double y;
// end union
protected String desc;
... /* constructors, get/set methods etc */
}
To declare x and y fields forming an anonymous union,
the attribute union with values begin/end must be used
in the XML:
<?xml version="1.0"
encoding="UTF-8"?>
<!--
Archive Dimension.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="Dimension" type="structure">
<field name="x" union="begin" />
<field name="y" union="end" />
</class>
</package>
</jniEasyEnhancer>
Java “verification”:
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
NativeMultipleFieldContainerDescriptor
classDesc =
(NativeMultipleFieldContainerDescriptor)typeMgr.getClassDescriptor(
Dimension.class);
NativeFieldStructureDescriptor
xField =
(NativeFieldStructureDescriptor)classDesc.getField("x");
NativeFieldStructureDescriptor
yField =
(NativeFieldStructureDescriptor)classDesc.getField("y");
System.out.println("Must be 8 (double): "
+ xField.size());
System.out.println("Must be 8 (double): " + yField.size());
System.out.println("Must
be true : " + (xField.getOffset() ==
yField.getOffset()));
Recommendation: if a native capable object with union fields is going to be made native
owning the native memory, is highly recommended make it native explicitly
before setting any field. The explanation is: when an object is made native
owning the native memory the current field values are used to set the native
memory in the creation process; the framework has no way to detect in a non
native object what fields was and was not set, the latest union field set the
native memory state.
Wrong example!!:
Dimension dim = new Dimension();
dim.setX(10);
// No native memory is set (is non-native)
JNIEasy.get().getNativeManager().makeNative(dim);
// BAD!!!!
"y" field with 0 overwrites the "x" field value set
// in the native
memory !
System.out.println("Must
be true : " + (dim.getX() == 0));
Correct example:
Dimension dim = new Dimension();
JNIEasy.get().getNativeManager().makeNative(dim);
dim.setX(10);
// Sets the native memory too
System.out.println("Must
be true : " + (dim.getX() == 10));
Methods of
Java classes declared as structures, C++ classes or unions can be declared as
proxy of C/C++ methods.
C++ methods
fit very well with Java methods (instance or static), what about C methods?,
there are two options to map Java static methods working as proxies of C
methods: use the code generation utilities (see Java Code Generation), or declare a Java class declared as a (false
non-existing) native C++ class and put inside the static Java methods working
as proxy of C methods; these “false” native Java classes usually are very
useful to group the C methods by categories.
A C++ class
example (valid with MSVC and gcc compilers[7]):
// MyCPPClassOnDLL.h
class DLLEXPORT MyCPPClassOnDLL
{
protected:
double m_value;
public:
MyCPPClassOnDLL(int a,int b);
static MyCPPClassOnDLL* __stdcall create(int a,int b);
static void __stdcall destroy(MyCPPClassOnDLL* obj);
static __int64 __stdcall
addStatic(int a,int b);
double __stdcall getValue();
virtual double __stdcall sub(int a,int b);
void
__cdecl
varargsEx(char* buffer,...);
};
// MyCPPClassOnDLL.cpp
#include "MyCPPClassOnDLL.h"
#include <stdio.h>
MyCPPClassOnDLL::MyCPPClassOnDLL(int a,int b)
{
m_value = a + b;
}
MyCPPClassOnDLL* __stdcall MyCPPClassOnDLL::create(int a,int b)
{
return new MyCPPClassOnDLL(a , b); // may be an inherited class
}
void __stdcall MyCPPClassOnDLL::destroy(MyCPPClassOnDLL* obj)
{
delete obj;
}
__int64 __stdcall MyCPPClassOnDLL::addStatic(int a,int b)
{
return (__int64)a + (__int64)b;
}
double __stdcall MyCPPClassOnDLL::getValue()
{
return m_value;
}
double __stdcall MyCPPClassOnDLL::sub(int a,int b)
{
m_value = m_value - (a + b);
return m_value;
}
void __cdecl MyCPPClassOnDLL::varargsEx(char* buffer,...)
{
va_list marker;
va_start( marker, buffer );
const char* name = va_arg( marker, const char*);
int age = va_arg( marker, int);
int brothers = va_arg( marker, int);
va_end( marker );
sprintf(buffer,"%s is %d years old and
has %d brothers",name,age,
brothers);
}
If used a Linux/Mac/Solaris
gcc compiler, __int64, __cdecl and __stdcall keywords
valid with Win32 compilers (MSVC, MinGW gcc, cygwin gcc), are defined in JNIEasy.h as:
#define __cdecl
__attribute__((cdecl))
#define __stdcall __attribute__((stdcall))
typedef long long __int64;
The DLLEXPORT (__declspec(dllexport) in Win32 compilers) macro, exports
all methods and static fields of the class, this modifier can be used in a per
method (and static field) basis. In Visual C++ 6 and gcc 32 bit compilers the
presence of a virtual method (the sub method) adds an internal and hidden int field: the pointer to the virtual
table (is added in the top most class only)[8].
JNI types
are not used consciously (jlong is __int64 in
Windows, long long in
Linux/Mac/Solaris x86) to show that JNIEasy integrates with legacy non-Java
related code.
The Java
class to be used as proxy:
package examples.manual;
public class MyCPPClassOnDLL
{
protected int _virtualTable; // the C++ class has a virtual method
protected double value;
public MyCPPClassOnDLL() // mandatory
(is not native)
{
}
public MyCPPClassOnDLL(int a, int b)
{
throw new RuntimeException("Not
enhanced");
}
public static void destroy(MyCPPClassOnDLL obj)
{
throw new RuntimeException("Not
enhanced");
}
public static
native long addStatic(int a,int b);
public double getValue()
// not a proxy
{
return value;
}
public double sub(int a,int b)
{
throw new RuntimeException("Not
enhanced");
}
public static void varargsEx(byte[] buffer,Object[] args)
{
throw new RuntimeException("Not
enhanced");
}
}
JNIEasy
supports two forms to declaring a Java native method:
public static void destroy(MyCPPClassOnDLL obj)
{
throw new RuntimeException("Not
enhanced");
}
public static
native long addStatic(int a,int b);
The body of
the first method will be replaced by the enhanced one and a new body will be
added to the second method removing the native modifier. If the specified RuntimeException exception is thrown or a standard Java linker
exception is thrown (trying to link the native method) are signs telling us the
class was not enhanced, the enhancement process replaces the provided default
body and removes the native flag if present.
The XML
enhancer descriptor (MSVC exported names only):
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyCPPClassOnDLL.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports/>
<class name="MyCPPClassOnDLL" type="class"
libraryPath="MyLibrary" >
<constructor onLibrary="true"
nativeName="?create@MyCPPClassOnDLL@@SGPAV1@HH@Z"
params="int, int" >
</constructor>
<method name="destroy" onLibrary="true"
nativeName="?destroy@MyCPPClassOnDLL@@SGXPAV1@@Z"
params="MyCPPClassOnDLL">
</method>
<method name="addStatic" onLibrary="true"
nativeName="?addStatic@MyCPPClassOnDLL@@SG_JHH@Z">
<return />
<params params="int, int" />
</method>
<method name="sub" onLibrary="true"
nativeName="?sub@MyCPPClassOnDLL@@UAGNHH@Z">
<return />
<params>
<param class="int" />
<param class="int" />
</params>
</method>
<method name="varargsEx" onLibrary="true"
nativeName="?varargsEx@MyCPPClassOnDLL@@SAXPADZZ"
callConv="c_call">
<return />
<params>
<param class="byte[]"
/>
<param class="Object[]" varargs="true"
/>
</params>
</method>
</class>
</package>
</jniEasyEnhancer>
The XML
descriptor declares the constructor with two parameters, the addStatic, sub and varargsEx methods as proxies of the
corresponding exported methods of the C++ class. The class declares the DLL to
be used with the libraryPath attribute (a hypothetical MyLibrary.dll or libMyLibrary.so or libMyLibrary.dylib), the .dll, .so and .dylib extensions
and the UNIX “lib” prefix may be omitted following the same rules as the System.loadLibrary Java method.
The onLibrary="true" declares a method as proxy
(otherwise is a callback, the default) of a native method and the nativeName attribute declares the exported
name of the DLL method to link[9].
Several tools can be used to obtain the native name of a DLL/shared object’s exported method or field,
in Windows use the free tool PEDUMP[10] (included inside the distribution),
on Linux use objdump (with --dynamic-syms argument), with Mac use nm, and on Solaris use elfdump (-s argument). On Linux, Mac and
Solaris c++filt command
does the reverse task: demangles an exported name showing the C/C++ original
header (very useful in C++), in Windows Microsoft provides a special method UnDecorateSymbolName[11] and the command undname included in the Microsoft SDK.
To specify
what method/constructor is being enhanced, JNIEasy supports three syntaxes:
1) <constructor/method params="class,class,..." >
2) <method ...>
<return />
<params params="class,class,..." />
</method>
3) <method ...>
<return />
<params>
<param class="class" />
<param class="class" />
</params>
Only the
third mode can be used to modify the default native memory layout of the parameter.
For instance:
<param class="Object[]" varargs="true"
/>
The param attribute, <param> nodes and the class attribute are used to pick the
“real” Java class method to be used as proxy; in this example parameters with
primitive types are used and the native declaration is straightforward but
native information can be added to declare the native view of the parameter as
a native variable with the same syntax of the <field> tag. The <return/> tag works in a similar fashion
(note the class attribute
is optional because the return class is known). The default constructor and getValue() method are not declared as proxy in
this example, but the value field is enhanced and when the getValue() Java method is invoked the native
C++ field is read too (it works as the C++ getValue() version).
If not
specified the attribute callConv, the default call convention of a method is std_call, callConv valid
values are std_call or c_call. The C call convention is used in
the varargsEx method
because methods with variable arguments only support C calls. Call convention
declared in the Java side and native side must be coincident, only standard and
C calls are supported by JNIEasy, any other convention like the “fast call”,
the default with compilers like MSVC or gcc, is not supported.
The
parameter args of the
method varargsEx is
declared as “variable number of arguments” (varargs), using the varargs attribute set to true. A varargs attribute mimics in Java
the C … parameter convention. Without this attribute (or set to false), the
default convention of the Object[] 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 the “intrinsic” native type.
A Java
method working as a proxy does not need a body, because this body is
overwritten in enhancement time with a new body calling the native method using
an internal CPPConstructor, CMethod, CPPMethod or CFieldMethod object attached to the native C/C++ method in
the DLL. A good practice is to throw a RuntimeException or inherited as method body (the
enhancer removes this) to remember the class must be enhanced.
The
constructor proxy is a special case because the C++ linked version must be a
static method returning the pointer of a new allocated C++ object (a C++ exported constructor can not be used
directly). When calling the Java proxy constructor, the address returned by
the C style constructor is internally used to attach the Java instance to the
native C++ object, the Java instance created with a proxy constructor is ever
native and attached to the native object, and C++ virtual methods can be called
because the C++ related object was created with a normal C++ constructor.
The native
static method addStatic() is inside a C++ class, this is not a limitation, JNIEasy can link
normal C methods, in C++ is recommendable use extern "C" to avoid the name mangling of
exported C methods in C++ files (or to declare the desired name in a custom
.DEF file with MSVC or similar approaches in gcc).
An example
of a static method call:
byte res = MyCPPClassOnDLL.addStatic(1,2);
System.out.println("Must be 3: " + res);
Example of
a complete lifecycle of a native instance:
MyCPPClassOnDLL obj = new MyCPPClassOnDLL(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);
MyCPPClassOnDLL.destroy(obj);
// Calls
the C++ method destroy(), the memory is freed
An example of varargs call:
byte[] buffer = new byte[256];
MyCPPClassOnDLL.varargsEx(buffer,
new Object[]{"Joe",new Integer(25),new Integer(2)});
String res = NativePrimitiveUtil.toString(buffer);
System.out.println("Must be true: " +
"Joe
is 25 years old and has 2 brothers".equals(res));
The default native type of "Joe" is char*, and the default native type of Integer in a varargs context is the int data type (in any other context the
related native type is int*, to use a pointer to int parameter in a varargs call, use a NativeInteger object). The
method NativePrimitiveUtil.toString(byte[]) converts a byte based string ended with a 0 element in a String object (very useful to work with
ANSI strings).
A Java
method declared as a native callback is a normal method (with a signature using
native enabled types) callable from native. Selected constructors, static and
instance methods in a user defined Java class, declared as a structure, C++
class or union, can be declared as native callbacks.
Example:
package examples.manual;
import com.innowhere.jnieasy.core.JNIEasy;
public class MyCPPClassOnJava
{
protected double value;
public MyCPPClassOnJava() //
mandatory
{
}
public MyCPPClassOnJava(int a,int b)
{
this.value = a + b;
JNIEasy.get().getLockedRegistry().lock(this);
// To avoid GC if called from native
}
public static void destroy(MyCPPClassOnJava obj)
{
JNIEasy.get().getLockedRegistry().unlock(obj);
// To enable GC again
JNIEasy.get().getNativeManager().free(obj);
}
public static long addStatic(int a,int b)
{
return (long)a + (long)b;
}
public double sub(int a,int b)
{
this.value = this.value - (a + b);
return this.value;
}
}
The XML
enhancer descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyCPPClassOnJava.jnieasy.enh.xml
-->
<jniEasyEnhancer version="1.1">
<package name="examples.manual">
<imports />
<class name="MyCPPClassOnJava" type="class">
<constructor exportMethod="true" params="int,int" >
</constructor>
<method name="destroy" exportMethod="true"
params="MyCPPClassOnJava">
</method>
<method name="addStatic" exportMethod="true"
params="int,int">
</method>
<method name="sub" exportMethod="true" params="int,int">
</method>
</class>
</package>
</jniEasyEnhancer>
The
constructor’s sentence: JNIEasy.get().getLockedRegistry().lock(this); inserts the object with a normal
reference in the framework’s lock registry to avoid the garbage collector to
collect the instance. Why? because a Java instance created from native code
does not retain the object with a Java reference (the native call returns a native
pointer not a Java reference), after object construction, the object goes out
the scope and the garbage collector can free the instance (freeing the
allocated native memory) any time; the lock action prevents the GC to free the
instance. The method working as a “destructor” unlocks the object, if there is
no Java reference pointing the instance it is marked to be garbage collected
(an asynchronous “delete”).
If the Java
object is created from native side, a “real” Java object is created and made
native allocating native memory. But if the Java object is created from Java
with a normal new, no native memory is allocated and the callback methods work as normal;
like a native capable Java object it can be made native (allocating or
attaching) in any time, and in this state, native fields are synchronized with
the native memory.
There are
two ways to call Java callbacks from native:
A “native
descriptor” of a user defined native capable class can be obtained with the
method: NativeTypeManager.getClassDescriptor(Class)
Example:
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
NativeMultipleFieldContainerDescriptor classDesc =
(NativeMultipleFieldContainerDescriptor)typeMgr.getClassDescriptor(
MyCPPClassOnJava.class);
Constructor construc = MyCPPClassOnDLL.class.getDeclaredConstructor(
new Class[]{int.class,int.class});
NativeConstructorDescriptor constrDesc =
(NativeConstructorDescriptor)classDesc.getBehavior(construc);
NativeConstructor
natConstr = constrDesc.getNativeConstructor();
The natConstr object is the native constructor
instance “making native” the MyCPPClassOnJava constructor with two int parameters. This native object can
be submitted to the native side as a pointer to method.
By default
the native method instances are “direct” (natConstr is a NativeDirectConstructorCallback
instance in this case),
but the optional useReflection=”true” attribute (by default is false) can be added
to use the reflection version (NativeConstructorReflection in this case). Example:
<constructor exportMethod="true" useReflection="true"
params="int,int" >
</constructor>
Exists a C
method findExportedMethodAddress exported by the JNIEasy DLL.
void* __stdcall findExportedMethodAddress(const char* signature)
Where __stdcall is defined in JNIEasy.h with a valid compiler-dependent
declaration.
This method
returns the address of the method registered in the framework with the
specified signature. Any method registered with DynamicLibrary methods are automatically exported
and accessible using findExportedMethodAddress using the following syntax:
"[DLLName:]MethodSignature"
Where the
optional DLLName is the name used to get the DynamicLibrary object from DLLManager.get(String) the first time; MethodSignature follows a mixed Java/C++ syntax, this syntax
is explained in the javadoc documentation of the method NativeBehaviorSignature.getSignatureString(String).
The method findExportedMethodAddress(const
char*)
is equivalent to the Java method JNIEasyLibrary.findExportedMethodAddress(String
signature) (in fact this method is called), this method parses
the DLLName, if
declared, and calls DynamicLibrary.findBehaviorBySignature(String) using
the DynamicLibrary object associated to the specified DLL with DLLName. Example:
String sig = ...
NativeBehavior cb =
JNIEasy.get().getJNIEasyLib().findExportedMethodAddress(sig);
If no
method was registered with the specified signature returns null.
If the DLL
name is omitted the JNIEasy’s DLL is supposed, this applies to Java methods as
native callbacks, the XML exportMethod="true" attribute specifies that the internally used
native method object to make native the Java method declared as callback, is
exported and accessible with findExportedMethodAddress, the native method object is registered with
JNIEasyLibrary.exportBehavior(String
nativeName,NativeBehavior method) using the Java method name as the first parameter (if the method is
static the absolute class name is added as prefix and with constructors the "<init>" name is ever used).
Any DLL
exported method can be found calling findExportedMethodAddress(const char*)
from native if the exported
method was previously registered using an appropriated DynamicLibrary method, this “dynamic binding”
technique is an alternative to the classic LoadLibrary/GetProcAddress
in Windows and dlopen/dlsym in UNIXes, the main difference is Java exported callbacks can be
obtained too (they are “simulated” exported native methods by JNIEasy DLL).
The findExportedMethodAddress method is declared in the JNIEasy.h public framework file and can be
statically linked using JNIEasy.lib with MSVC. Is exported by the JNIEasy DLL with the
method name "findExportedMethodAddress" and can be dynamically linked (in
Win32 using LoadLibrary/GetProcAddress in Windows and dlopen/dlsym in Linux/Mac/Solaris). The files JNIEasy.c and JNIEasyHelper.cpp are provided to help the dynamic
linking process (both files and JNIEasy.h can be found in the include folder). If findExportedMethodAddress is not used your native code has no
dependency with JNIEasy (useful macros like DLLEXPORT are not directly related with
JNIEasy and can be easily copied in your code for your convenience).
The Java
class exports the specified callback methods when is loaded and initialized, to
ensure a class is loaded and initialized this utility method can be used[12]:
NativeCapableUtil.initializeClass(MyCPPClassOnJava.class);
or calling NativeTypeManager.getClass(String) with the class name, the ClassLoader currently defined in JNIEasy will
be used to load and initialize the class:
JNIEasy.get().getTypeManager().getClass("examples.manual.MyCPPClassOnJava");
Following
with the example, the signatures of the exported MyCPPClassOnJava callbacks are:
"examples.manual.MyCPPClassOnJava.<init>(int,int)"
"examples.manual.MyCPPClassOnJava.destroy(examples.manual.MyCPPClassOnJava*)"
"examples.manual.MyCPPClassOnJava.addStatic(int,int)"
"examples.manual.MyCPPClassOnJava.sub(int,int)"
To
understand how these signatures are constructed see the javadoc of JNIEasyLibrary.exportBehavior(String
nativeName,NativeBehavior method)
A C++ class
ready to work as a proxy of the Java class may be:
// MyCPPClassOnJava.h
#include <JNIEasy.h>
#include <JNIEasyHelper.h>
class MyCPPClassOnJava
{
protected:
static void* (__stdcall * _MyCPPClassOnJava)(int,int);
static void (__stdcall * _destroy)(void*);
static __int64 (__stdcall * _addStatic)(int,int);
static double (__stdcall * _sub)(void*,int,int);
public:
static MyCPPClassOnJava* create(int a,int b)
{
if (_MyCPPClassOnJava == 0)
_MyCPPClassOnJava = (void* (__stdcall *)(int,int))
JNIEasyHelper::findExportedMethodAddress(
"examples.manual.MyCPPClassOnJava.<init>(int,int)");
return (MyCPPClassOnJava*)_MyCPPClassOnJava(a,b);
}
static void destroy(MyCPPClassOnJava* obj)
{
if (_destroy == 0)
_destroy = (void (__stdcall *)(void*))
JNIEasyHelper::findExportedMethodAddress(
"examples.manual.MyCPPClassOnJava.destroy(
examples.manual.MyCPPClassOnJava*");
_destroy(obj);
}
static __int64 addStatic(int a,int b)
{
if (_addStatic == 0)
_addStatic = (__int64 (__stdcall *)(int,int))
JNIEasyHelper::findExportedMethodAddress(
"examples.manual.MyCPPClassOnJava.addStatic(int,int)");
return _addStatic(a, b);
}
double sub(int a,int b)
{
if (_sub == 0)
_sub = (double (__stdcall *)(void*,int,int))
JNIEasyHelper::findExportedMethodAddress(
"examples.manual.MyCPPClassOnJava.sub(int,int)");
return _sub(this, a, b);
}
};
// MyCPPClassOnJava.cpp
#include "MyCPPClassOnJava.h"
void* (__stdcall * MyCPPClassOnJava::_MyCPPClassOnJava)(int,int) = 0;
void (__stdcall * MyCPPClassOnJava::_destroy)(void*) = 0;
__int64 (__stdcall * MyCPPClassOnJava::_addStatic)(int,int) = 0;
double (__stdcall * MyCPPClassOnJava::_sub)(void*,int,int) = 0;
Example of
use:
MyCPPClassOnJava* proxy = MyCPPClassOnJava::create(2,3);
// like a Java: new
MyCPPClassOnJava(2,3);
double res = proxy->sub(2,3); // Must be 0
MyCPPClassOnJava::destroy(proxy); // Unlock the Java
object
C++ objects
created as proxy of Java objects (using the Java constructor) are not created
with the normal C++ new sentence, the Java side allocates the native memory, then the C++ delete sentence must not be used to
destroy a Java allocated object (the C++ new was not used), the Java native
instance will free the native memory.
Of course a
C++ object can be constructed with a normal C++ constructor. Following the
example:
// MyCPPClassOnJava.h
class MyCPPClassOnJava
{
protected:
double m_value; // the same as in Java class
. . .
public:
MyCPPClassOnJava(int a,int b) // the same as in Java class
{
m_value
= a + b;
}
. . .
};
Example of
use:
MyCPPClassOnJava* proxy = new MyCPPClassOnJava(2,3);
double res = proxy->sub(2,3); // Must be 0
delete proxy;
This
example do the same as the previous, but it works in a different manner: only
the method sub is working
as proxy and calling the related Java method, when invoking, a temporal MyCPPClassOnJava Java instance is created and
attached to the address of the C++ object and the Java sub method is called, the Java field value accessed inside the method is
synchronized with the native memory, the read/write change of the Java field is
performed with the native memory field, the returned value is the expected.
This operational mode (allocating memory in the C++ side) is not performant
because a new Java instance is created and attached to native object address in
a per C++ proxy/Java method call basis, the temporal Java instance is discarded
in the end of call. The other operational mode, calling a Java constructor
(allocating memory in the Java side) is more performant because the Java
instance is unique it is internally registered as a native memory owner
(reminder: this registry does not hold the object with a strong reference) and
locked in the constructor (with a strong reference).
A note
about Mac OS X 10.4 (Tiger) gcc 4.0 and findExportedMethodAddress: gcc on Mac makes aggressive
optimizations using the flag -Ox (x != 0), for instance the “omit-frame-pointer”
optimization. A call to findExportedMethodAddress enters inside the Java VM, if you are
experimenting strange problems try to add -fno-omit-frame-pointer to the gcc compiler options or remove the –Ox
flag.
The Java
fields, static or instance, of native capable classes are a special case of
callbacks. A non-native Java field can be wrapped, made native and exported to
the native world with a related field-method callback created by the framework
behind the scenes.
To do this,
is necessary to add a <fieldMethod> declaration in the XML enhancer file,
declaring the field name and the native type declaration of the field type.
In the
previous XML file MyCPPClassOnJava.jnieasy.enh.xml:
...
<fieldMethod
name="value"
exportMethod="true"
/>
</class>
...
The new tag
declares the field value as a field-method (in this case the field is already declared native
but this is not mandatory), using the default native related type of int and the standard call convention (std_call), the method is exported and can be
obtained using the method findExportedMethodAddress(const char*). By default the native field-method
instance is “direct” (NativeDirectInstanceFieldCallback in this case), but the optional useReflection=”true” attribute can be added to use the
reflection version (NativeInstanceFieldMethodReflection in this case):
<fieldMethod name="value"
exportMethod="true"
useReflection="true" />
The native
field-method instance to be used can be obtained as any other native method
with the native class descriptor, but using the java.lang.reflect.Field object of the field.
NativeTypeManager typeMgr = JNIEasy.get().getTypeManager();
CPPClassDescriptor classDesc =
(CPPClassDescriptor)typeMgr.getClassDescriptor(MyCPPClassOnJava.class);
Field field = MyCPPClassOnJava.class.getDeclaredField("value");
NativeBehaviorDescriptor
methodDesc = classDesc.getBehavior(field);
If not using reflection:
NativeDirectInstanceFieldCallback callback =
(NativeDirectInstanceFieldCallback)methodDesc.getNativeBehavior();
If using
reflection:
NativeInstanceFieldMethodReflection callback =
(NativeInstanceFieldMethodReflection)methodDesc.getNativeBehavior();
The
signature to use with findExportedMethodAddress is:
"examples.manual.MyCPPClassOnJava.value(int,double)"
To use this
exported method from native:
// MyCPPClassOnJava.h
class MyCPPClassOnJava
{
. . .
static double (__stdcall * _value)(void*,int,double);
. . .
double MyCPPClassOnJava::valueField(int opcode,double value)
{
if (_value == 0)
_value =
(double