JNIWrapper Programmer's Guide (original) (raw)


Version: 3.13
Last Updated: February 3, 2025

Copyright © 2002-2025 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 Guide

This 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 Version

New in this version (3.0) of the Programmer's Guide:

Added: Chapter 8.

The 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:

1.4. About JNIWrapper

JavaTM 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 Advantages

JNIWrapper has a number of technical advantages over the competitors. The most important of them are:

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 Release

For 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 Requirements

The following are general requirements to run JNIWrapper on the following supported platforms:

There are no specific memory or other hardware requirements for developing an application based on JNIWrapper.

2.2. Package Contents

The 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 theReadme.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 File

The 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 Library

The 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.

Important 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 thejava.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 File

Placing the license file is very simple: it should be located in the same folder where the native JNIWrapper library (jniwrap.dll file) resides.

Important 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, thejniwrap.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:

Chapter 4. Working with Native Libraries and Functions


4.1. Finding Native Libraries

JNIWrapper 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 thecom.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 calledMyLibraryLoader. 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 DefaultLibraryLoader

JNIWrapper comes with a convenient default implementation of theLibraryLoader 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 theaddPath() 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 Libraries

Loading 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 thesetDefaultLibraryLoader() 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 Library

Functions are represented by instances of thecom.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, thegetFunction() 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 Variables

Some 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 Conventions

There are two widespread calling conventions:cdecl used in most C/C++ programs andstdcall 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 Windows

Most Windows® API functions use thestdcall 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 X

The majority of libraries on Linux use thecdecl 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 usingcom.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 Types

JNIWrapper 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() andsetValue() 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 Classes

Below is given a table of the mappings for most commonly used data types along with some comments. For more information, visithttp://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 andInt64, 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
[n] PrimitiveArray(.class, n);

5.1.1.1. Mappings for Windows API Types

Windows 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 athttp://www.teamdev.com/winpack/windowsTypes.jsf

5.2. Structures and Unions

JNIWrapper 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 Alignment

Structures 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 Member

Unlike 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. Pointers

JNIWrapper supports C-like pointers to all the defined types. You can create a pointer by instantiating thecom.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 orPointer. 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 thePointer.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 Pointers

Another 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. TheasFunction() 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 theCallback class.

5.3.2. ArithmeticalPointer Class

Limited pointer arithmetics is supported through theArithmeticalPointer 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 Pointers

A typed pointer represented by an instance of thePointer class can be cast to an un-typed (i.e. void) pointer represented by an instance of thePointer.Void class and vice versa. The following code demonstrates casting the instance of thePointer.Void class to the instance of thePointer 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. Arrays

JNIWrapper supports two types of arrays:

  1. Primitive arrays that are made up of primitive values, such as integers or characters.
  2. 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 ofcom.jniwrapper.PrimitiveArray andcom.jniwrapper.ComplexArray for the primitive and complex arrays, respectively. Arrays are represented byPrimitiveArray 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 Contents

When 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 asdouble 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 Arrays

In 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 andExternalArrayPointer. 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 thereadArray(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, useExternalArrayPointer.

5.5. Strings

Strings 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 Class

The 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 asAnsiString. You can also createStr 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 Class

The 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 theStringArray(boolean unicode) constructor.

5.6. Enumerations

A 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 theinvoke() method of theFunction 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 Conventions

If 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 fromcom.jniwrapper.FunctionExecutionException.

These error or information messages from JNIWrapper can be disabled by adding the special category for JNIWrapper classes to yourlog4j 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 Function

The Function class provides a shortcut way to call a function from a library without creatingLibrary 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 Callbacks

Callback 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 andcdecl calling conventions. Callbacks are represented by subclasses of thecom.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 theCallback 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 itsdispose() 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 theCallback class implementing thecallback() 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. Parameters

Parameters 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. Functions

Function 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. Overview

The 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 JNIWrapper

The application can be started by the appropriate file from thebin 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 Options

The application provides an ability to specify various options for code generation. These options can be divided into the following three categories:

8.4. Using Type Replacement Table

The 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 Naming

The 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 Types

The current version of the application supports the following C types:

Also, the application supports generation of pointers and arrays.

8.7. Known Limitations

The 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. Example

Suppose 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 followingSample.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 Support

On the native side, all the JAWT functionality can be accessed through several structures defined in theinclude/jawt.h file in the JDK directory. The root structure - JAWT - is available from theGetAWT function. JNIWrapper provides thecom.jniwrapper.jawt.JAWT class that implements all the functionality available through the JAWT structure. TheJAWT_DrawingSurface andJAWT_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 Data

The 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 theplatformInfo member of the jawt_DrawingSurfaceInfo structure. The contents of this structure are defined in the jawt_md.h file in theinclude// 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 Window

Below 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:

9.4. JAWT Support in Different JDK Versions

When 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 ajava.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 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):

WinPack Demo Application TeamDev Ltd. WinPack Demo Application The demo of WinPack library

The tag above makes the reference to the other jnw.jnlp file which is declared in the following way:

JNIWrapper resources TeamDev Ltd. JNIWrapper Application JNIWrapper Application

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.

Important The license file for JNIWrapper should be placed to theMETA-INF folder of your application JAR file.

Chapter 11. Using JNIWrapper in Applets


To use JNIWrapper in applets, follow these instructions:

  1. 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).
  2. Prepend the following code to theinit() 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 ofjniwrap.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");
  3. Prepend the following code to the method to start the applet method:
    AppletHelper.getInstance().start();
    This call starts NativeResourceCollector thread of JNIWrapper.
  4. Append the following code to the method to stop the applet method:
    AppletHelper.getInstance().stop();
    This call stops NativeResourceCollector thread of JNIWrapper.
  5. The license file (jniwrap.lic) can be included into any JAR file of a JWS application, into theMETA-INF subfolder.
  6. 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:

Below is given the applet usage sample:

Applet Sample

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:

  1. Remove all the signatures from JNIWrapper libraries you are going to use in your application. To do this, deleteJNIWRAPP.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.
  2. Copy the JNIWrapper runtime license file (jniwrap.lic) to theMETA-INF subfolder of the JNIWrapper library (for instance, jniwrapper-3.0.jar\META-INF\).
  3. 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.
  4. Make an executable library.

To run the created executable file, you need to place thejniwrap.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 andjniwrap.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 Problems

If 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.