JNIWrapper Programmer's Guide Version: 3.12 Last Updated: October 21, 2016
Copyright © 2002-2021 TeamDev Ltd. Chapter 1. Introduction
In the few years since its first release, the
JavaTM programming language has grown immensely to
become a popular platform. Many developers working on different platforms
find their own advantages in using Java technology. One of them is, of
course, the "write once, run anywhere" ability allowing developers to
write software on one platform and run it on another. Sometimes, however, Java programs have to interact with native code.
This is well justified for such reasons as performance, a lack of features
for platform integration in the Java platform or the need of legacy
software interoperability. To solve this problem, Java Native Interface
(JNI) was introduced in the Java platform, allowing programmers to write
native code pieces and integrate them into their Java programs. The main
difficulty arising from such an approach is that native code is completely
disjoint from Java code in terms of writing, browsing, debugging and
maintenance. In this document, we are introducing
JNIWrapperTM - the product that allows developers to
interface native code while retaining full control of the application on
the Java side at any level. 1.1. About This GuideThis guide introduces JNIWrapperTM, describes
its design goals, concepts and principles, provides the requirements for
using the product as well as sufficient information you need to know
before starting to work with the product. This document covers all platform versions of JNIWrapper. In cases
where functions treat a particular platform in a specific way, or
specific configuration settings are needed, these are marked
accordingly. 1.2. New in This VersionNew in this version (3.0) of the Programmer's Guide: Added: Chapter 8.
1.3. Related DocumentsThe documents provided on the Documents page at the JNIWrapper
area at TeamDev site (http://www.teamdev.com) are
intended to help you understand and effectively use of the JNIWrapper
technology. To make your learning curve shorter and smoother, we suggest that
you read the documentation in the following order: You can start with the current Programmer's
Guide document describing the ideas and basics of the
proposed software. If you plan to develop your own software effectively, proceed
from this Programmer's Guide to
JNIWrapper Tutorial, which provides
step-by-step instructions with code samples. Each version of JNIWrapper is supplied with updated
Release Notes. To receive the up-to-date
version of specific information, be sure to check the
Release Notes section inside
Readme.txt file before installing
JNIWrapper. You can also find helpful information in the
Frequently Asked Questions document, which we
regularly update based on the questions we get from our users. The
document can be accessed online at the JNIWrapper site.
1.4. About JNIWrapperJavaTM is a very powerful platform allowing
programmers to develop state-of-the-art software. It is, however,
designed to run on a variety of different platforms and therefore does
not include every feature of every platform. Certain basic things like
creating a symbolic link under Linux or operating with registry items
under Windows® are not supported in JavaTM.
Programmers willing to do this are forced to write a native library and
classes interfacing with it, then debug the code using two different
debuggers (Java- and native-side). These are sometimes difficult and
always very time-consuming tasks. All of them can be avoided by using
JNIWrapper - a Java library for calling native library functions. With
JNIWrapper, you can extensively use the potential of the underlying
platform (like tray icons or custom shape splash screens) with only a
single native library, having the full control over the program flow on
the Java side. 1.4.1. Technical AdvantagesJNIWrapper has a number of technical advantages over the
competitors. The most important of them are: Comprehensive native function
invocation support JNIWrapper supports both stdcall
and cdecl calling conventions and all
complex C types including structures and unions. Callbacks are
fully supported with any parameter and return types and both
calling conventions. For unexpected cases, you can create your own
types taking full control over parameter behavior. Automatic resource
management All resources allocated by JNIWrapper components are
released automatically when no longer required. You can
treat JNIWrapper variables as ordinary objects that can be
picked up by Java garbage collector. JNIWrapper objects are also safe with regard to
finalizers: all resources are guaranteed to be available
during finalization.
High performance This has always been our priority. JNIWrapper has been
specially tuned for performance, particularly in cases where large
amounts of data are involved in the interactions. Minimum behind-the-scenes
operation You should always be able to see and understand what is
happening when they work with native-side data. This helps both to
develop and debug complex interactions between Java and native
code.
The product is extensively used in the projects carried out by
our company, which ensures its efficiency, reliability, future
support, and improvement. 1.4.2. New in This ReleaseFor detailed information about the changes in the current
version, check the Readme.txt inside the
JNIWrapper package. The JNIWrapper changes history is also available online
at: http://www.teamdev.com/jniwrapper/whats_new.jsf
Chapter 2. Getting Started
2.1. System RequirementsThe following are general requirements to run JNIWrapper on the
following supported platforms: Windows OS: Windows 9x, Me, NT 4.0, 2000, XP and Vista Java: Java 2 SDK/JRE 1.3.x and later. For CodeGen GUI,
the preferred JDK is 1.4.2. or later.
Linux OS: Red Hat Linux 7.2, 9.0; Mandrake Linux 9.1, 9.2,
10.0; Mandriva 2005 LE; WhiteBox Linux; Gentoo Linux (in
general, all distros on kernel 2.4.x and kernel 2.6.x) Java: Java 2 SDK/JRE 1.4.x
Mac OS X
There are no specific memory or other hardware requirements for
developing an application based on JNIWrapper. 2.2. Package ContentsThe JNIWrapper package consists of the following main files
required for work: All the files need to be placed in the appropriate locations.
Please see the section "Configuring Software" for more details about the
product installation instructions. The package may also contain other
files providing some useful information for the you, for example the
Readme.txt file. Chapter 3. Configuring JNIWrapper
JNIWrapper consists of three main files required for the software
functioning: a JAR file, native code library, and a license file. The
following sections describe where each file should be located. No other
configuration is required. 3.1. Library JAR FileThe JNIWrapper JAR file should be located in the program class
path. Due to the limitations of the Java native library loading
mechanism, it is not recommended to load JNIWrapper in custom class
loaders, unless you are sure that it will be loaded in only one such
class loader. The library file can also be placed in the boot class path or in
the extension directory of Java runtime, but this is not
required. 3.2. Native Code LibraryThe JNIWrapper native code library is loaded using the standard
Java native code loading mechanism. There are no known problems with
placing the native code library file on a mapped drive or even using it
from the network share using a UNC path. | Do not rename the library file, or else it will not be
loaded. |
Even though the native code library can be placed virtually
anywhere, its actual location should be determined considering the fact
that Java code must find the library to load. It can be placed somewhere
within the program library search path (value of the
java.library.path system property, which is by
default equal to the value of the system variable PATH on Windows or
LD_LIBRARY_PATH on Linux). Alternatively, you can add a search path to the default library
loader used by JNIWrapper or even write a custom one that searches for
native code in a predefined location. Using a default path may be
preferable for development and library loader as a much better way for
distributing a complete application. Since version 3.0 it is possible to keep native libraries within a
JAR file. JNIWrapper will automatically locate and install a library on
demand. You may want to install the native code library into the
directories on the default system path, for example: Note that this requires having appropriate access rights on the
Windows NT/2000/XP, Linux and Mac OS X systems. Installing the native
code library by using this way may be convenient, but is not a required
procedure. 3.3. License FilePlacing the license file is very simple: it should be located in
the same folder where the native JNIWrapper library
(jniwrap.dll file) resides. | Do not rename the license file, or else it will not be
recognized. |
Also, there is one universal way for redistribution of the runtime
license file. You will just need to save this file to your application's
JAR file, into its META-INF subfolder. For example,
if you have some application called
<application_name>.jar, the
jniwrap.lic file should be located in the following
folder: <application_name>.jar
\META-INF
jniwrap.lic You may appear to have several licenses for JNIWrapper for
different platforms supported. In this case, you need to put all the
license key files as described above but make sure there is no file name
conflict. JNIWrapper accepts multiple license files named like shown
below: jniwrap.lic jniwrap.lic1 jniwrap.lic2 ... jniwrap.lic999
Chapter 4. Working with Native Libraries and Functions
4.1. Finding Native LibrariesJNIWrapper accesses native libraries by name using library
loaders. Library loaders are responsible for finding and loading a
library by its name. You can use the supplied implementation of a
path-based library loader or create your own. The latter approach requires implementation of the
com.jniwrapper.LibraryLoader interface.
This interface defines two methods:
findLibrary() for finding a file containing the
required library by name, and loadLibrary() for
loading the library (by calling System.load),
also by name. Suppose you have implemented a library loader called
MyLibraryLoader. To make JNIWrapper look for
the libraries using this loader, you can write something like
this: Library.setDefaultLibraryLoader(new MyLibraryLoader()); Remember that the default loader specified this way is also used
for loading the JNIWrapper native library. Alternatively, you may use a custom loader to load a specific
library only: myLibrary.load(new MyLibraryLoader()); 4.2. Using DefaultLibraryLoaderJNIWrapper comes with a convenient default implementation of the
LibraryLoader interface:
DefaultLibraryLoader. It looks for the
libraries in a set of directories (path). The initial search path
includes all directories from the java.library.path
system property. New directories can be added to the search path using the
addPath() method.
DefaultLibraryLoader is a singleton and is also the one
JNIWrapper uses by default. If using the default path with some additions is sufficient, then
adding directories to the search path is the only configuration needed.
For example, if native libraries (or the JNIWrapper native code) are to
be looked for in the bin directory relative to the
program working directory, the following line should be added to the
JNIWrapper initialization part of your program: DefaultLibraryLoader.getInstance().addPath("bin"); 4.3. Loading Native LibrariesLoading of libraries does not require special attention, if only
one library loader is used, i.e. when the necessary library is found and
loaded using the loader set by the
setDefaultLibraryLoader() method (or an
instance of the DefaultLibraryLoader class, which
is the initial value of that property). If a custom library loader should be used, an explicit call to the
library's load() method should be made before
any function is loaded from that library. Take a look at these examples. Using one library loader: Library kernel32 = new Library("kernel32");
// Use kernel32 here Using a custom library loader: Library customLib = new Library("myCustomLib");
LibraryLoader myCustomLoader = new MyCustomLibraryLoader();
customLib.load(myCustomLoader);
// Use customLib here 4.4. Getting Functions from a Native LibraryFunctions are represented by instances of the
com.jniwrapper.Function class. A function is
always part of some library and therefore, cannot be instantiated on its
own. To get a function from the library, the
getFunction() method should be used. For
example: Function getLastError = kernel32.getFunction("GetLastError"); Please note that some compilers may mangle function names to
include argument types or sizes. This version of JNIWrapper performs
search only by the exact name, not by mangled names. Therefore, if you
need to invoke a function with such a name, you should specify the full
mangled name. For example: Function myFunc = myLibrary.getFunction("_myFunc@4"); Most libraries, however, export nice unmangled names. You can find
out an exported function name by using tools such as
dumpbin or Dependency
Checker. 4.4.1. Managing Library's Global VariablesSome global variable called _data is declared
in the native library. To find a global variable on the Win32
platform, we can use the following example: extern "C" __declspec (dllexport) int* _data = new int(123); To retrieve a value of the variable in the library, we can use
the Library.getVariable() method. The sample
code below shows how it can be done: // load library
Library SAMPLE_LIB = new Library("Library_Name");
Int result = new Int();
// get a variable pointer
SAMPLE_LIB.getVariable("_data", new Pointer(result));
// read a variable value
long variableValue = result.getValue(); 4.5. Calling ConventionsThere are two widespread calling conventions:
cdecl used in most C/C++ programs and
stdcall used, for example, in Pascal. Calling
convention is a function property since a library can export functions
that use different calling conventions. Use your library documentation
to find out which calling convention is used by the functions you are
going to invoke. 4.5.1. Calling Conventions on WindowsMost Windows® API functions use the
stdcall calling convention. This is the
default convention used by JNIWrapper. If the function you need to
call uses a different calling convention, you should set it in the
function object. For example: cdeclFunction.setCallingConvention(Function.CDECL_CALLING_CONVENTION); 4.5.2. Calling Conventions on Linux/Mac OS XThe majority of libraries on Linux use the
cdecl calling convention. This is the default
calling convention on Linux. Chapter 5. Passing Parameters to/from Native Code
In JNIWrapper, parameters are passed to and from native code using
com.jniwrapper.Parameter objects. These objects
behave like variables of different types depending on the actual subclass
used. All parameters are mutable, which means that you can change any
value at any time. All parameters can be shared across function calls,
i.e. one can pass the result of one function call directly to another
without creating a new object. 5.1. Primitive TypesJNIWrapper provides parameter classes for all the primitive types
available in the native program: signed and unsigned integers of
different sizes, floating point values, single-byte and wide characters,
etc. Related types usually have a common superclass or implement a
common interface. All simple types have a no-argument constructor that initializes a
parameter to the default value (usually zero or equivalent), a
constructor with the initial value and a copying constructor. They also
have appropriately typed getValue() and
setValue() methods. Take a look at the example
of using the DoubleFloat parameter: DoubleFloat d1 = new DoubleFloat(1.2345);
DoubleFloat d2 = new DoubleFloat(d1);
d1.setValue(9.876);
System.out.println(d2.getValue()); 5.1.1. Mappings of Native Types to JNIWrapper ClassesBelow is given a table of the mappings for most commonly used
data types along with some comments. For more information, visit
http://www.teamdev.com/jniwrapper/nativeTypes.jsf Table 5-1. Mappings of Native Types to JNIWrapper Classes Native Type (C/C++) | JNIWrapper type | Comments |
---|
Boolean
Types | bool | Bool(1 byte), IntBool(4 bytes) | | Character
Types | char | Char | | wchar_t | WideChar | | uchar * | Char, UInt8 | | Integer
Types | short | ShortInt | The unsigned types are represented by prefixing U to
the type name. For example, the unsigned int (or
unsigned) type is UInt. | int | Int | There are also types for predefined-width integers:
Int8, Int16, Int32 and
Int64, they also have the unsigned
variants. | long | LongInt | | Floating-point
Types | float | SingleFloat | | double | DoubleFloat | | long double | LongDouble | Long double is the same as double (8-byte
floating-point value) on the Win32 platform. | Pointer Types (not
arrays) | void * | Pointer.Void | | const | Pointer.Const | Use Pointer.Const if the
referenced object is not to be modified by the calling
function. | type * | Pointer | To create a pointer to the value (variable) of a known
type, use the Pointer class. For
example: int *i; is Pointer i =
new Pointer(new Int()); | type * | Pointer.OutOnly | Use Pointer.OutOnly if the
referenced object is not to be read by the calling
function. | char * | AnsiString | | wchar_t * | WideString | | Arrays | <c primitive type>[n] | PrimitiveArray(<corresponding JNIWrapper
type>.class, n); | |
5.1.1.1. Mappings for Windows API TypesWindows API includes many data types which are not listed here
(for example, DWORD, HANDLE). If you need to use one of such types,
use Windows-specific documentation such as MSDN to find out the
actual C type that corresponds to it (for example, LPSTR corresponds
to char*), and use the relevant JNIWrapper type for the argument.
You can also check the online Windows Data Types table available at
http://www.teamdev.com/winpack/windowsTypes.jsf 5.2. Structures and UnionsJNIWrapper supports C-like structures and unions. Like in C, the
structure and union definitions are similar. Both provide a constructor
that takes an array of parameters defining the structure or union
contents. If you want to create a reusable Java class for a structure or
unions, you can use a protected no-argument constructor and initialize
the content by calling the init method. This
method is provided for convenience, because class fields are initialized
after the superclass constructor is invoked and therefore, cannot be
used as constructor arguments. The structure and union member values are
accessed by directly querying the parameters specified in the
constructor for their values. For example: /*
Structure definition in C:
struct SomeStruct
{
int a;
char b;
double c;
}
*/
Int intField = new Int();
Char charField = new Char();
DoubleFloat doubleField = new DoubleFloat();
struct = new Structure(new Parameter[] {intField, charField, doubleField});
doubleField.setValue(10); // set struct.a value to 10
// invoke some code that modifies the structure here
System.out.println(doubleField); // prints new value of struct.c To learn how to use linked structures, open: <jniwrapper-3.x-win32.zip>/samples/src/LinkedStructureSample.java 5.2.1. Setting Structure AlignmentStructures can be defined with different alignments. The
structure alignment is specified in the constructor (or in the call to
the init method). Here is the example of a
reusable structure definition that supports different
alignments: private static class TestStruct extends Structure {
public Int _int = new Int();
public Int8 _int8 = new Int8();
public Int16 _int16 = new Int16();
public Int32 _int32 = new Int32();
public Int64 _int64 = new Int64();
public LongInt _long = new LongInt();
public TestStruct() {
init(new Parameter[] {
_int, _int8, _int16, _int32, _int64, _long
});
}
public TestStruct(short alignment) {
init(new Parameter[] {
_int, _int8, _int16, _int32, _int64, _long
}, alignment);
}
} 5.2.2. Setting an Active Union MemberUnlike C, in JNIWrapper you need to define the active union
member explicitly using the setActiveMember()
method. However, this can be done after the function call where the
union data was modified. Take a look at the following examples of the
union usage: union = new Union(new Parameter[] {intField, charField, stringField, structField});
union.setActiveMember(stringField);
stringField.setValue(STRING_VALUE);
func1.invoke(result, union);
// ...
func2.invoke(union, (Parameter[])null);
union.setActiveMember(structField, true);
assertEquals(X_VALUE, structField.getX()); Structures and unions can consist of any simple or complex
members that are JNIWrapper parameters. In the above example, one of
the union members is a structure. 5.3. PointersJNIWrapper supports C-like pointers to all the defined types. You
can create a pointer by instantiating the
com.jniwrapper.Pointer class. Pointers should
always refer to some object. For example: Pointer pInt = new Pointer(new Int()); A pointer automatically allocates memory for its referenced
object; it handles reads and writes requiring the referenced object to
read or write its data when the pointer itself is read or written. Thus,
after any native function call, all the parameters are updated even if
they are referenced by several nested pointers. For example: Int value = new Int();
Pointer ppInt = new Pointer(new Pointer(value));
// invoke func(int **i) passing ppInt as a parameter
func.invoke(null, ppInt);
System.out.println(value) // value is updated! In cases where the referenced object is read-only or write-only,
one can use the Pointer.Const or
Pointer. OutOnly types, respectively. Note,
however, that using these classes cannot enforce the read-only or
write-only policy on the native function which may still access the data
inappropriately. It is recommended that these classes are only used for
performance improvements. JNIWrapper supports pointers that refer to undefined values
(void* in C) through the
Pointer.Void class. Use this class when you do
not care about the actual referenced object and do not need to allocate
the memory a pointer points to. For example, if you need a constant
(HWND)-1 you can use the following
construct: Pointer.Void HWND_TOPMOST = new Pointer.Void(-1); Pointer.Void is not a pointer. It doesn't
have a referenced object and it is not assignable to and from any other
kind of pointer. The name of the type only reflects the fact that this
parameter always has the same size as a platform-dependent
pointer. 5.3.1. Function PointersAnother capability of the Pointer.Void
class is that it can also be used to represent a function pointer and
to call the function it points to. The
asFunction() method returns a function object
that can be used to invoke a function that a given pointer points
to. Consider the following example. A native library provides a
function to install a callback that returns an old callback function
pointer: typedef void (*PCallbackType)(int);
PCallbackType installCallback(PCallbackType); One can install a hook to monitor the callback invocation in the
following way: // Field declaration
Pointer.Void myOldCallback;
// ...
// Callback installation code
installCallback.invoke(myOldCallback, new HookCallback()); and later inside the HookCallback
class: protected void callback() {
// do some hook stuff
myOldCallback.asFunction().invoke(null, intParam);
} To create an object callable from native code, use the
Callback class. 5.3.2. ArithmeticalPointer ClassLimited pointer arithmetics is supported through the
ArithmeticalPointer class. This pointer also
manages one referenced object, but also accommodates an offset from
its initial value. Such a pointer can be used for passing to the
functions such as strtok that offset a pointer to
iterate through data. Note that the referenced object cannot be
changed and is always read and written at its initial offset. 5.3.3. Casting PointersA typed pointer represented by an instance of the
Pointer class can be cast to an un-typed (i.e.
void) pointer represented by an instance of the
Pointer.Void class and vice versa. The
following code demonstrates casting the instance of the
Pointer.Void class to the instance of the
Pointer class: // retrieve the pointer somewhere
Pointer.Void handle = getHandle();
// prepare the data pointer, assuming that it points to an integer value
Int data = new Int();
Pointer dataPtr = new Pointer(data);
// cast the pointers
handle.asTypedPointer(dataPtr); After the last operation, the data variable
obtains the value which the handle pointer points
to. Casting an instance of the Pointer class
to an instance of the Pointer.Void class is
done as shown on the sample below: // create typed pointer
Int data = new Int(123);
Pointer dataPtr = new Pointer(data);
// create void pointer
Pointer.Void handle = new Pointer.Void();
// cast the pointers
dataPtr.asVoidPointer(handle); After the last operation, the handle pointer
will have the same address as the dataPtr
pointer. 5.4. ArraysJNIWrapper supports two types of arrays: Primitive arrays that are made up of primitive values, such as
integers or characters. Complex arrays that can consist of elements of any implemented
type.
You can use complex arrays to store primitive values, but the
primitive arrays are more efficient for this purpose. Arrays are represented by the instance of
com.jniwrapper.PrimitiveArray and
com.jniwrapper.ComplexArray for the primitive and
complex arrays, respectively. Arrays are represented by
PrimitiveArray and ComplexArray types
parametrized by parameters that represent the actual type. For
example: int i[10]; is PrimitiveArray i = new PrimitiveArray(Int.class, 10); The main difference between the two types of arrays is that a
primitive array is a plain data block that contains sequential data of a
given type, while a complex array is a sequential storage of elements of
any complexity that are all read and written whenever an array is read
or written, respectively. This means that an array of pointers cannot be
implemented as a primitive array because the referenced objects will not
be written or read when needed. PrimitiveArray can be only
of primitive types (int, char,
float) and not of arrays, pointers, structures, etc.
ComplexArray has no restriction on its element type. Sometimes arrays can be specified as pointers in function
signature, for example: void foo(int *arg); But if the actual value is a pointer to some number of integers,
use array as a parameter. The simplest method of creating an array is using a constructor
specifying a sample parameter and array size. Note that the array's
member type should be correctly cloneable. For example, to create an
array of bytes, you can use the following construct: PrimitiveArray val = new PrimitiveArray(new Int8(), 256); Another method of creating an array is using a Java array of
parameters that should constitute it. This is achieved by using a
constructor taking a Parameter[] argument. Both
primitive and complex arrays can be created this way. Here is an example
of creating an array of pointers to integers: Parameter[] members = new Parameter[10];
for(int i = 0; i < 10; i++) {
members[i] = new Pointer(new Int(i));
}
ComplexArray result = new ComplexArray(members); 5.4.1. Pointers to Array ContentsWhen using arrays you should always remember that sometimes
arrays are stored or passed to functions not as plain data, but as a
pointer to the array contents. The most typical case is when an array
argument or member is defined as a pointer to type (e.g.
double*). In this case, the actual passed parameter
should be a Pointer. For example: /*
C declaration:
struct s
{
int size;
double *data;
}
*/
Int intMember = new Int(50);
PrimitiveArray arrayMember = new PrimitiveArray(DoubleFloat.class, 50);
Structure s = new Structure(new Parameter[] {intMember,
new Pointer(arrayMember)}); If in the previous example, the second member is declared as
double data[50] (predefined size array), the
pointer wrapper should not be used. The same rule applies for passing arrays to a function call. For
example: /*
C declaration:
double foo(double* data, int elementsCount);
*/
Function foo = library.getFunction("foo");
DoubleFloat result = new DoubleFloat();
int count = 50;
PrimitiveArray array = new PrimitiveArray(DoubleFloat.class, count);
foo.invoke(result, new Pointer(array), new Int(count); 5.4.2. Controlling Memory Allocation for ArraysIn most cases, the array sizes are known or may be computed
before a call is made. There are cases, however, when defining an
array size before a function call is not possible or not efficient. In
these cases, the array that is passed by a pointer is either resized
to accommodate all the data or allocated altogether by the callee. In
the first case, the caller is usually still responsible for memory
deallocation, while in the second case, the memory management is most
likely the responsibility of the callee. The common thing in both
cases is that the called function returns the new size of the array as
one of the results of the call. JNIWrapper supports both ways of required memory management by
using special array pointers -
ResizingPointer and
ExternalArrayPointer. Each of these pointers
does not read the array it points to after the function call. The
array should be read after the call is complete using the
readArray(int count) method of the pointer.
For example: PrimitiveArray myArray = new PrimitiveArray(Int8.class, length);
Int16 len = new Int16(length);
ResizingPointer pArray = new ResizingPointer( myArray);;
Function func = getFunction("myFunction");
func.invoke(null, pArray, new Pointer(len));
length = (short)len.getValue();
pArray.readArray(length);
// use myArray here The rule of thumb for choosing the correct pointer is as
follows: if you want JNIWrapper to manage the memory allocated for the
array, use ResizingPointer; if you do not
need the memory management for the array memory, use
ExternalArrayPointer. 5.5. StringsStrings in JNIWrapper are character arrays of a predefined size
with convenience methods for getting and setting string values as zero
terminated strings. JNIWrapper supports two types of strings:
single-byte, or ANSI strings, and Unicode strings. All strings are defined with the maximum length that a string
value can occupy. It is illegal to set a string parameter value to the
value longer than its maximum length. Passing a string argument to a
function that writes more data than is allocated may result in an error
just like in the native programs. On the other hand, strings are safe in
the way that the data is parsed until the terminating zero (of
appropriate length) is found or the maximum string length is reached.
Therefore, even a bad string without a terminating zero will not cause
memory access failures in your program. Here are the examples of string
usage: AnsiString s = new AnsiString("Hello, World!");
WideString s2 = new WideString(20);
s2.setValue("Goodbye, World!"); Similar to arrays, strings in native code can be both pointers to
string data and character arrays themselves. When using strings as
structure members, use the same guidelines to determine whether a
pointer wrapper should be used. When passing strings as function
arguments, however, strings are always passed as pointers and JNIWrapper
does pointer wrapping automatically. You should remember though that
wrapping is just a convenience feature and passing a char**
will require creating a new Pointer(new Pointer(new
AnsiString())). 5.5.1. Str ClassThe Str class unifies different ways of
working with native string data. Instances of the class work with ANSI
or Unicode characters depending on the Unicode support available in
the underlying operating system. If Unicode is supported, this class
works as WideString, otherwise as
AnsiString. You can also create
Str instances that work specifically with ANSI
or Unicode. Using this class would give the code that is easier to
read, refactor and maintain. 5.5.2. StringArray ClassThe StringArray class provides
functionality for working with native double zero terminated string
data. When not explicitly defined, the type of characters depends on
the Unicode support of an operating system under which the code is
being executed. To set the required character type, use the
StringArray(boolean unicode)
constructor. 5.6. EnumerationsA C enumeration type (for example enum a {first,
second, third}) can be substituted by an integer parameter,
for example Int class (or any other integer
parameter). In this case, new Int(0) corresponds
to 'first', new Int(1) corresponds to 'second'
and so on. Chapter 6. Calling Native Functions
Native functions are called using the
invoke() method of the
Function class. The arguments and return value are
specified using variables of the Parameter type. There are
several overloaded versions of the invoke()
method, but the idea and argument structure is similar: the first argument
is always a holder for the return value and the rest
are arguments in the order they appear in the function declaration. When a function is called, all passed parameters are passed to it
and then the return value is stored in its placeholder. It is allowed to
pass null instead of the return value parameter, in
which case it will be ignored, but it is allowed for primitive types only.
Doing this when the return value is not of a primitive type may result in
an error, because memory must be allocated by the function caller if the
function return value is big and there is no way to allocate appropriate
structure automatically without knowing the actual return value type.
There are no restrictions on the argument or return value types as long as
they are what a function expects. 6.1. Calling ConventionsIf you are going to use a calling convention that differs from the
default one used by JNIWrapper, the calling convention must be set
before a native function is first called. Failure to do so will result
in an error. Here is a complete example of calling a function: Function sprintf =
new Library("msvcrt").getFunction("sprintf");
sprintf.setCallingConvention(
Function.CDECL_CALLING_CONVENTION);
AnsiString result_buffer = new AnsiString();
sprintf.invoke(
null,
result_buffer,
new AnsiString("Hello, %s!"),
new AnsiString("World"));
System.out.println("result = " + result_buffer.getValue());
//Output: result = Hello, World! This example shows that there is no problem with calling functions
even with variable argument number. JNIWrapper checks that stack is left in consistent state after the
function is called and handles most of the failures by throwing a Java
exception. All exceptions descend from
com.jniwrapper.FunctionExecutionException. These error or information messages from JNIWrapper can be
disabled by adding the special category for JNIWrapper classes to your
log4j configuration file: log4j.category.com.jniwrapper=FATAL Also, it can be configured programmatically: Category jniwrapper = Category.getInstance("com.jniwrapper");
jniwrapper.setLevel(Level.FATAL); 6.1.1. A Quick Way to Call a FunctionThe Function class provides a shortcut
way to call a function from a library without creating
Library and Function
instances: the call() method. To use it,
write the following: Table 6-1. | Function.call("user32.dll", "MessageBeep", retVal, new UInt(1)); | | Function.call("/lib/libc.so.6", "printf", null, new AnsiString("Hello, World!\n")); | | Function.call("/usr/lib/libSystem.dylib", "printf", null, new AnsiString("Hello, World!\n")); |
This will load a library using the default loader, look up a
function and invoke it using the default calling convention. It is not recommended to use the call()
method when your program makes a lot of native function calls, because
it is more resource intensive. However, it is perfectly fine to use it
in simple cases like getting the current directory. 6.2. Using CallbacksCallback is a user-defined function that is called by the library
code at the appropriate time. Callbacks can have arguments and return a
value. JNIWrapper supports callbacks with any kind of arguments and
return values, as well as stdcall and
cdecl calling conventions. Callbacks are
represented by subclasses of the
com.jniwrapper.Callback class. Passing a callback as an argument is no different from passing any
other value: you just need to put an instance of the
Callback class as the corresponding argument. For
example: Table 6-2. | final class EnumWindowsProc extends Callback
// ...
EnumWindowsProc enumWindowsProc = new EnumWindowsProc();
Function.call("user32.dll", "EnumWindows", retVal,
enumWindowsProc, new Int32(57)); | | class MyComparator extends Callback
// ...
Function.call("/lib/libc.so.6", "qsort", null,
dataPointer, new Int(size), new Int(memberLen), new MyComparator()); | | class MyComparator extends Callback
// ...
Function.call("/usr/lib/libSystem.dylib", "qsort", null,
dataPointer, new Int(size), new Int(memberLen), new MyComparator()); |
When a callback is no longer needed, you should explicitly free
the resources associated with it by invoking its
dispose() method. This is the only case in
JNIWrapper where resources are to be explicitly freed, because some
types of the callback objects may have no references from code and still
be active, for example a window procedure callback instance may be
created once when the window class is registered and remain active
during the entire program lifecycle. To implement a callback, you need to subclass the
Callback class implementing the
callback() abstract method. The callback
arguments and return value are specified in the constructor (by calling
either the superclass constructor with parameters or init()
method). The order of parameters is following: first go the
arguments of a callback, and then a return value. Calling convention
should also be set in the constructor if it is different from the
default one . When the callback() method is invoked,
argument parameters contain the values of the passed arguments; after
the callback() method is complete, the value of
the return value parameter is returned to the caller. Here is an example
of a callback that takes an integer argument and returns its value
incremented by one: /*
C callback declaration:
int callback(int);
*/
private static class IncIntCallback extends Callback {
private Int _arg = new Int();
private Int _result = new Int();
public IncIntCallback() {
init(new Parameter[] {_arg}, _result);
}
public void callback() {
_result.setValue(_arg.getValue() + 1);
}
} Chapter 7. Working with Multithreading
JNIWrapper was designed with threading in mind and is already
successfully used in a multithreaded environment. The following sections
describe JNIWrapper components in terms of thread safety. 7.1. ParametersParameters in JNIWrapper are not synchronized because
synchronizing at this level is very inefficient, and performance was a
high-priority goal in JNIWrapper development. Therefore, access to the
parameter data should be enclosed in synchronization blocks if there is
a possibility for more than one thread to access the data at a time. If
you have only one parameter that is shared between threads, you can
synchronize on that object's monitor: Int32 sharedInt = new Int32();
// ...
// First thread
synchronized(sharedInt) {
someFunction.invoke(sharedInt);
}
// ...
// Second thread
synchronized(sharedInt) {
if (sharedInt.getValue() == 10)
System.out.println("Value is set to 10");
// ... 7.2. FunctionsFunction invocation in JNIWrapper is completely thread-safe. If
the called function is thread-safe, any number of threads may invoke it
concurrently at any time. No synchronization is required and/or
performed, and calls are executed fully concurrently as if invoking
simple Java methods. Chapter 8. Code Generator for JNIWrapper
In the new version of JNIWrapper 3.0 we have added a new Code
Generator application. Its main goal is to provide a convenient and
efficient way to generate Java wrappers for custom C types such as
structures, unions, callbacks, etc. 8.1. OverviewThe Code Generator for JNIWrapper allows you to select C code
containing the required C types and generate the corresponding Java
wrappers for them. This code can be either in a C header file or in a
fragment of code, which can be inserted from the clipboard or just typed
as text. The resulting Java classes (Java wrappers for C types) are
saved to the specified folder. 8.2. Running Code Generator for JNIWrapperThe application can be started by the appropriate file from the
bin directory of JNIWrapper installation. The table
below shows what file should be executed (depending on the platform) to
start the applicaiton: This application does not provide command-line mode. 8.3. Specifying Input Parameters and OptionsThe application provides an ability to specify various options for
code generation. These options can be divided into the following three
categories: Input parameters specify the
input C source code in which the necessary types are defined. It can
be a C header file, a folder that contains C headers files or a
fragment of C code. Generation parameters specify
the set of types to generate wrappers for, their names and base
types respectively. Output parameters specify the
destination folder to save all generated Java files to and the root
package name for all generated classes.
8.4. Using Type Replacement TableThe Code Generator application imports all types that are defined
in the specified source and builds a correspondence table: every
imported C type is mapped to the corresponding type from the JNIWrapper
library. The application provides an ability to change the name of a
particular type as well as its base type (the parent type from the
JNIWrapper library), in case this type was not recognized by the C
parser. Also, it is possible to define a set of types to be generated: you
can just select the necessary types in the appropriate table. 8.5. Namespaces and Package NamingThe destination root package (which can be specified in the
options) is actually a root for other subpackages automatically
generated by the application. Also, it is possible to give a custom
package name to each type (resulting class) individually. 8.6. Supported TypesThe current version of the application supports the following C
types: Also, the application supports generation of pointers and
arrays. 8.7. Known LimitationsThe current version of the application does not recognize the
following C preprocessor directives: The inline functions (for example "void GetData() { return;}"
should not be present in the C code either. Nester pointer types (for example: typedef int** PPInt) are not
supported yet and they are processed as usual single pointers. Also, please keep in mind that the application just parses the
given C code, but does not validate it anyhow. Therefore, before giving
some C code to this application, make sure that code is compilable and
valid. To generate a more complete set of wrappers, we recommend you to
precompile the input C code with a C-compiler first, and then give the
resulting file to the Code Generator application. This step will help
you eliminate all unresolved dependencies. 8.8. ExampleSuppose you need to create a wrapper for the following C
structure: struct Sample {
int a;
float b;
}; which is used in the following C function: void sample(Sample* structure); The Code Generator application will generate the following
Sample.java wrapper for this C structure: public class Sample extends Structure {
private Int _a = new Int();
private SingleFloat _b = new SingleFloat();
public Sample() {
init(new Parameter[] {_a, _b});
}
...
} Then, this generated Sample.java file should
be included to the project and compiled. And the function call can be as
follows: Library lib = ...; // library that contains 'sample' function
Function sample = lib.getFunction("sample");
Sample sample = new Sample();
function.invoke(null, new Pointer(sample)); Chapter 9. Using AWT Native Interface
JavaTM includes the cross-platform standard
windowing library called Abstract Window Toolkit(AWT). One of the design
principles of the AWT was to include only the features that can be
implemented on all platforms targeted for JavaTM.
This imposed some limitations on the windowing interface features. To help
you access the native controls that stand behind the AWT, the AWT native
interface (JAWT) was introduced. JNIWrapper also supports this interface
so that you don't need to write native code to access the JAWT
features. 9.1. Using JAWT SupportOn the native side, all the JAWT functionality can be accessed
through several structures defined in the
include/jawt.h file in the JDK directory. The root
structure - JAWT - is available from the
GetAWT function. JNIWrapper provides the
com.jniwrapper.jawt.JAWT class that implements
all the functionality available through the JAWT structure. The
JAWT_DrawingSurface and
JAWT_DrawingSurfaceInfo classes correspond to the
structures with the same names in the jawt.h file
and provide the same functionality. 9.2. Accessing Native Control's DataThe most common use of the JAWT interface is to get a handle of
the native control that corresponds to a given component. This data is
returned in the platform-dependent structure pointed to by the
platformInfo member of the
jawt_DrawingSurfaceInfo structure. The contents of this
structure are defined in the jawt_md.h file in the
include/<platform>/ directory of the JDK
installation. The correct structure needs to be passed to the
constructor of the JAWT_DrawingSurfaceInfo class.
A sample structure for Win32 is shown below: public class Win32DSI extends Structure
{
private Pointer.Void _handle = new Pointer.Void();
private Pointer.Void _hdc = new Pointer.Void();
private Pointer.Void _hpalette = new Pointer.Void();
public Win32DSI()
{
init(new Parameter[]{_handle, _hdc, _hpalette},
(short) 8);
}
/**
* Returns target component handle (either window or
* bitmap handle).
*/
public Pointer.Void getHandle()
{
return _handle;
}
/**
* Returns DC handle. This handle should be used for
* drawing instead of handles returned * from
* <code>GetDC</code> or <code>BeginPaint</code>.
*/
public Pointer.Void getHdc()
{
return _hdc;
}
/**
* Returns palette handle.
*/
public Pointer.Void getHpalette()
{
return _hpalette;
}
} 9.3. Getting HWND of a WindowBelow is given an example of a code snippet that gets a Win32
window handle of an AWT window. It is very similar to the C-code
required to do the same thing. JAWT_DrawingSurface ds = JAWT.getDrawingSurface(window);
ds.lock();
Win32DSI win32DSI = new Win32DSI();
JAWT_DrawingSurfaceInfo dsi = new JAWT_DrawingSurfaceInfo(win32DSI);
Pointer pDsi = new Pointer(dsi);
ds.getDrawingSurfaceInfo(pDsi);
int result = (int) win32DSI.getHandle().getValue();
ds.freeDrawingSurfaceInfo(pDsi);
ds.unlock();
JAWT.freeDrawingSurface(ds);
return result; In the table below you can see how to create the wrappers for Java
windows on different platforms: Table 9-1. | // Using Wnd class from WinPack library
JFrame window = new JFrame();
window.setVisible(true); //a Java window should be visible
Wnd wnd = new Wnd(window); | | // Using NSWindow class from MacPack library
JFrame window = new JFrame();
window.setVisible(true); // a Java window should be visible
NSWindow wnd = new NSWindow(window); |
9.4. JAWT Support in Different JDK VersionsWhen programming using the JAWT interface, you should keep in mind
that JAWT is a relatively new technology. It was introduced only in JDK
1.3 with limited functionality, has had many improvements in JDK 1.4,
and JAWT is intended to completely replace such native-related functions
as sun.awt.windows.
WToolkit.getNativeWindowHandleFromComponent. For JDK 1.3, you
should expect very limited support for JAWT. For example, the argument
of the getDrawingSurface() method can only be a
java.awt.Canvas. JDK 1.4 introduced new
functions, such as AWT locking and unlocking, as well as extended the
capabilities of the existing functions. Chapter 10. Using JNIWrapper in Java Web Start Applications
This section describes the way of deploying your applications that
use JNIWrapper with the help of Java Web Start (JWS). One of the major requirements for any JWS application is that all
its resources are located inside signed JAR files. Although JAR files can
be signed multiple times, JWS does not accept files with more than one
signature. It is also mandatory that all application JAR files are signed
with the same signature. All JNIWrapper libraries are supplied already signed and signing
them with a new signature makes them unacceptable for JWS. Fortunately,
there is a simple solution. The main idea is to use the
<extension> tag in the .jnlp
file and to create two different .jnlp files for your
application. One .jnlp file should contain your
application files and the other - JNIWrapper resources. This technique is
demonstrated in the example below. The first file is the application
.jnlp file (demo.jnlp): <?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://www.teamdev.com/" href="demo.jnlp">
<information>
<title>WinPack Demo Application</title>
<vendor>TeamDev Ltd.</vendor>
<description>WinPack Demo Application</description>
<description kind="short">The demo of WinPack library</description>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<resources>
<j2se version="1.3+" initial-heap-size="64m"/>
<property name="sun.java2d.noddraw" value="true"/>
<jar href="demo.jar"/><!-- demo.jar is your jar file signed with your own signature-->
<extension name="jnw" href="jnw.jnlp"/>
</resources>
<component-desc/>
<application-desc main-class="com.jniwrapper.win32.samples.demo.WinPackSet"/>
</jnlp> The <extension> tag above makes the reference
to the other jnw.jnlp file which is declared in the
following way: <?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://www.teamdev.com/" href="jnw.jnlp">
<information>
<title>JNIWrapper resources</title>
<vendor>TeamDev Ltd.</vendor>
<description>JNIWrapper Application</description>
<description kind="short">JNIWrapper Application</description>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<resources os="Windows">
<nativelib href="jniwraplib.jar"/>
</resources>
<resources>
<jar href="jniwrap.jar"/>
<jar href="winpack.jar"/>
</resources>
<component-desc/>
</jnlp> The second jnw.jnlp file represents the
JNIWrapper resource bundle for redistribution as part of another JWS
application. The jniwraplib.jar package should only
include one file: jniwrap.dll. After you've configured the .jnlp files, place
them to your Web site and create a link to your main
.jnlp file that will also download JNIWrapper
resources by the reference. | The license file for JNIWrapper should be placed to the
META-INF folder of your application JAR
file. |
Chapter 11. Using JNIWrapper in Applets
To use JNIWrapper in applets, follow these instructions: Copy jniwrap.dll to the folder of a web
server computer where the applet resides (it is recommended, but you
can place jniwrap.dll to some other folder on the
web server). Prepend the following code to the
init() method of the applet: // passes the instance of the current object as parameter
AppletHelper.getInstance().init(this); This call downloads jniwrap.dll from the
web server and copies it to the Windows system32
folder in the client's computer. The presence of
jniwrap.dll on the client side is required,
otherwise the applet will not work. If you copied jniwrap.dll to a folder with
no residing applet, you should provide JNIWrapper library URL, for
example: AppletHelper.getInstance().init("http://applets.com/native/jniwrap.dll"); Prepend the following code to the method to start the applet
method: AppletHelper.getInstance().start(); This call starts NativeResourceCollector thread of
JNIWrapper. Append the following code to the method to stop the applet
method: AppletHelper.getInstance().stop(); This call stops NativeResourceCollector thread of
JNIWrapper. The license file (jniwrap.lic) can be
included into any JAR file of a JWS application, into the
META-INF subfolder. application.jar should be signed and should
reference the JNIWrapper library(s) in its manifest file by setting
the class path variable. Signing JNIWrapper libraries is not necessary
as they are already signed.
The sample build file that prepares an applet library is shown
below: <project name="Applet Sample" default="build" basedir=".">
<property name="certificate" value=".keystore"/>
<property name="licenseFile" value="jniwrap.lic"/>
<property name="appletClasses" value="classes"/>
<target name="build">
<jar destfile="sample.jar">
<fileset dir="${appletClasses}" includes="AppletSample.class"/>
<manifest>
<attribute name="Class-Path" value="jniwrap-2.5.jar"/>
</manifest>
<zipfileset file="${licenseFile}" prefix="META-INF" />
</jar>
<signjar jar="sample.jar" alias="your_alias" keystore="${certificate}"
storepass="your_storepass" keypass="your_keypass" />
</target>
</project> Below is given the applet usage sample: <html>
<body><h1>Applet Sample</h1>
<p>
<applet docbase="http://your_url" code="AppletSample.class" width="400" height="300" archive="sample.jar"/>
</body>
</html> Chapter 12. Using JNIWrapper in Native Executable Archives
To demonstrate how to make a native executable file (which uses
JNIWrapper) using JBuilderX "Archive Builder" utility, we have prepared
the following algorithm: Remove all the signatures from JNIWrapper libraries you are
going to use in your application. To do this, delete
JNIWRAPP.DSA and JNIWRAPP.SF
files from the META-INF subfolder in JNIWrapper
JAR-libraries (such as jniwrapper-3.0.jar). This
is necessary because "Archive Builder" does not correctly copy JAR's
signatures and manifest files to the resulting archive. Copy the JNIWrapper runtime license file
(jniwrap.lic) to the
META-INF subfolder of the JNIWrapper library (for
instance, jniwrapper-3.0.jar\META-INF\). Select the "Always include all classes and resources" option in
the "Dependencies" section. This is necessary because "Archive
Builder" does not correctly track the dependencies. Make an executable library.
To run the created executable file, you need to place the
jniwrap.dll library in the same folder. Chapter 13. Using JNIWrapper in Servlets
If you're going to use JNIWrapper in a web application, just place
the jniwrap.dll, jniwrap.lic and
jniwrap.jar files in the appropriate web server
catalog within the program library search path. For example, if you use
the Tomcat web server, you can place the files in: $TOMCAT_HOME/webapps/MyApp/WEB-INF/lib but not in $TOMCAT_HOME/common/lib Also, in a servlet you can add WEB-INF/lib directory to the path
using the following code: DefaultLibraryLoader.getInstance().addPath(this.getServletContext().getRealPath("/") + "WEB-INF/lib"); Chapter 14. Support
If you have any problems or questions regarding JNIWrapper, please
check the documents listed below. The answer to your question may already
be there: If none of the above resources contain the required information,
please e-mail us at: jniwrapper-support@teamdev.com
14.1. Reporting ProblemsIf you find any bugs, please submit the issue to us using a
special report form on the TeamDev integrated customer support and
troubleshooting center at: http://support.teamdev.com/forms/reportForm.jsf
The form will help you provide all necessary information. |
| Copyright © 2002-2021 TeamDev Ltd. | |
|
|