Difference between revisions of "pas2jni"

From Free Pascal wiki
Jump to navigationJump to search
m (→‎Command line options: Fixed formatting)
 
(22 intermediate revisions by 2 users not shown)
Line 1: Line 1:
=pas2jni utility=
+
=Overview=
  
The '''pas2jni''' utility generates a JNI (Java Native Interface) bridge for a Pascal code. Then the Pascal code (including classes and other advanced features) can be easily used in Java programs.
+
The '''pas2jni''' utility generates a JNI (Java Native Interface) bridge for a Pascal code. Then the Pascal code (including classes and other advanced features) can be easily used in Java programs on various platforms, including [[Android]].
  
 
For example you can do the following in Java:
 
For example you can do the following in Java:
  
<code><pre>
+
<syntaxhighlight lang="java">
 
import pas.classes.*;
 
import pas.classes.*;
  
Line 16: Line 16:
  
 
...
 
...
</pre></code>
+
</syntaxhighlight>
  
 
The following Pascal features are supported by '''pas2jni''':
 
The following Pascal features are supported by '''pas2jni''':
Line 23: Line 23:
 
* var/out parameters;
 
* var/out parameters;
 
* class;
 
* class;
 +
* interface;
 +
* object;
 
* record;
 
* record;
 
* property;
 
* property;
 
* constant;
 
* constant;
 
* enum;
 
* enum;
* set;
+
* set (32 elements maximum);
 
* TGuid type;
 
* TGuid type;
 
* pointer type;
 
* pointer type;
 
* string types;
 
* string types;
 
* all numeric types;
 
* all numeric types;
* method poitner.
+
* method pointer;
 +
* array (limited support: setters/getters for array elements).
  
 
'''USUPPORTED features:'''
 
'''USUPPORTED features:'''
* array (Can be added in future);
+
* array (full support);
* procedure pointer (Not possible to implement. To workaround this limitation create a procedure handler in your Pascal code and call a method pointer declared is some global Pascal class. The you can assign this method pointer from a Java code).
+
* set (with 32+ elements);
 +
* procedure pointer (Not possible to implement. To workaround this limitation create a procedure handler in your Pascal code and call a method pointer declared in some global Pascal class. Then you can assign this method pointer from a Java code).
  
 
Shared libraries, generated by pas2jni were tested with Java on Windows and Android. It should work on other systems as well.
 
Shared libraries, generated by pas2jni were tested with Java on Windows and Android. It should work on other systems as well.
Line 58: Line 62:
 
Note: You need to use '''ppudump''' of the same version as the FPC compiler. Use the <code>-D</code> switch to specify correct '''ppudump''' if it is not in <code>PATH</code>.
 
Note: You need to use '''ppudump''' of the same version as the FPC compiler. Use the <code>-D</code> switch to specify correct '''ppudump''' if it is not in <code>PATH</code>.
  
=CUSTOM HANDLERS=
+
=Custom handlers=
  
 
It is possible to define the following custom handlers in your Pascal code.
 
It is possible to define the following custom handlers in your Pascal code.
  
procedure JNI_OnException;
+
<code>'''procedure JNI_OnException;'''</code>
* is called when an unhandled Pascal exception occurs. For example, you can log a stack back trace in this handler.
+
* It is called when an unhandled Pascal exception occurs. For example, you can log a stack back trace in this handler.
  
Custom handlers must be public and defined in one of the main units specified when calling pas2jni.
+
Custom handlers must be public and defined in one of the main units specified when calling '''pas2jni'''.
  
=CODING TIPS=
+
=Coding tips=
  
 
==Setting handlers (method pointers) in a Java code.==
 
==Setting handlers (method pointers) in a Java code.==
  
For example there is the following event handler in your Pascal code:
+
For example there is the following class and event handler in your Pascal code:
  
<code><pre>
+
<syntaxhighlight lang="pascal">
TMyClass = class
+
type TMyClass = class
...
+
//...
 
   property OnChange: TNotifyEvent;
 
   property OnChange: TNotifyEvent;
...
+
//...
 
end;
 
end;
</pre></code>
+
</syntaxhighlight>
 +
 
 +
In Java code, you add the event handler in a usual Java way:
 +
 
 +
<syntaxhighlight lang="java">
 +
// Create an instance of TMyClass
 +
TMyClass myClass = TMyClass.Create();
 +
 
 +
// Set the event handler
 +
myClass.setOnChange(
 +
    new TNotifyEvent() {
 +
      protected void Execute(TObject Sender) {
 +
        // The handler code
 +
      }
 +
    }
 +
  );
 +
 
 +
// Work with myClass
 +
// ...
 +
// ...
 +
 
 +
// Remove the handler to avoid memory leak
 +
myClass.setOnChange(null);
 +
// Free the object
 +
myClass.Free();
 +
</syntaxhighlight>
 +
 
 +
NOTE: If you are setting handlers (method pointers) for short living objects, it is needed to explicitly remove the handlers  before the object destruction by setting the '''null handler'''. Otherwise you will reach the active handlers limit (10000 handlers) and your application will crash.
 +
 
 +
==Type casting==
 +
In some cases type casting for generated Java classes does not work as expected.
 +
For example, the following code will not compile:
 +
<syntaxhighlight lang="java">
 +
contnrs.TObjectList objList = contnrs.TObjectList.Create();
 +
classes.TList list = objList;
 +
</syntaxhighlight>
 +
Though <code>TObjectList</code> is inherited from <code>TList</code> and similar code works in Pascal, the generated Java classes use a bit different class hierarchy in this case. An intermediate class <code>__TList</code> is used and <code>TList</code> is not ancestor for <code>TObjectList</code> anymore. The <code>__TList</code> class is needed to handle method reintroducing, which is allowed in Pascal, but not allowed in Java.
 +
 
 +
You need to use the following type casting trick in such case:
 +
<syntaxhighlight lang="java">
 +
classes.TList list = new classes.TList(objList);
 +
</syntaxhighlight>
  
In a Java code you get the following TMyClass instance:
+
The same type casting method is used to cast a pointer to an object:
  
TMyClass myclass = TMyClass.Create();
+
<syntaxhighlight lang="java">
 +
// Create a list
 +
classes.TList list = classes.TList.Create();
 +
// Get a pointer to the list
 +
long pList = system.Pointer(list);
 +
// Type cast the pointer to the new TObject variable
 +
system.TObject list2 = new system.TObject(pList);
 +
</syntaxhighlight>
 +
==Passing Java objects to Pascal code==
 +
In some cases it is needed to pass native Java objects to your Pascal code and handle them directly using JNI functions. For example, you can pass a Java array to your Pascal code to perform some quick processing of the array's data.
  
It is possible set a Java handler in 2 ways:
+
To do that you need to use a specially defined type <code>TJavaObject</code>. Use this type for function parameters to indicate that this parameter expect a Java object without any special processing.
  
1) Place the handler inline.
+
Also it is needed a pointer to the JNI environment in order to call JNI functions. Just add a parameter of type <code>PJNIEnv</code> to access the JNI environment using this parameter.
  
<code><pre>
+
Here an example:
...
 
  myclass.setOnChange(
 
      new TNotifyEvent() {
 
        protected void Execute(TObject Sender) {
 
          // The handler code
 
        }
 
      }
 
    );
 
...
 
</pre></code>
 
  
2) Define the handler as a method in a class.
+
<syntaxhighlight lang="pascal">
 +
// Define a special type TJavaObject. The declaration must be exactly as shown here
 +
type
 +
  TJavaObject = type jobject;
  
<code><pre>
+
// Define your procedure
public class MyJavaClass {
+
procedure HandleJavaObject(env: PJNIEnv; obj: TJavaObject; myParam: longint);
  private void DoOnChange(TObject Sender) {
+
begin
    // The handler code
+
  // Work with native Java object 'obj' using JNI functions accessible via 'env'.
   }
+
   // ...
 +
end;
 +
</syntaxhighlight>
  
  public void main() {
+
You will get the following generated declaration in Java for this procedure:
    ...
+
<syntaxhighlight lang="java">
    // Set the handler to the method with the "DoOnChange" name in the current class (this).
+
public native static void HandleJavaObject(Object obj, int myParam);
    myclass.setOnChange( new TNotifyEvent(this, "DoOnChange") );
+
</syntaxhighlight>
    ...
 
  }
 
}
 
</pre></code>
 
  
=COMMAND LINE OPTIONS=
+
=Command line options=
  
<code><pre>
+
<pre>
 
Usage: pas2jni [options] <unit> [<unit2> <unit3> ...]
 
Usage: pas2jni [options] <unit> [<unit2> <unit3> ...]
  
Line 135: Line 180:
 
             semicolon delimited. To read the list from a file use -E@<file>
 
             semicolon delimited. To read the list from a file use -E@<file>
 
   -?      - Show this help information.
 
   -?      - Show this help information.
</pre></code>
+
</pre>
 +
 
 +
[[Category:FPC]]
 +
[[Category:Utilities]]

Latest revision as of 13:42, 29 January 2020

Overview

The pas2jni utility generates a JNI (Java Native Interface) bridge for a Pascal code. Then the Pascal code (including classes and other advanced features) can be easily used in Java programs on various platforms, including Android.

For example you can do the following in Java:

import pas.classes.*;

...

TStringList sl = TStringList.Create();
sl.Add("Hello.");
String s = sl.getStrings(0);
sl.Free();

...

The following Pascal features are supported by pas2jni:

  • function/procedure;
  • var/out parameters;
  • class;
  • interface;
  • object;
  • record;
  • property;
  • constant;
  • enum;
  • set (32 elements maximum);
  • TGuid type;
  • pointer type;
  • string types;
  • all numeric types;
  • method pointer;
  • array (limited support: setters/getters for array elements).

USUPPORTED features:

  • array (full support);
  • set (with 32+ elements);
  • procedure pointer (Not possible to implement. To workaround this limitation create a procedure handler in your Pascal code and call a method pointer declared in some global Pascal class. Then you can assign this method pointer from a Java code).

Shared libraries, generated by pas2jni were tested with Java on Windows and Android. It should work on other systems as well.

How to use

pas2jni uses the PPUDump utility included with Free Pascal Compiler to read unit interfaces. Therefore your Pascal code must be first compiled with FPC. When your units are compiled, you can run pas2jni. You need to specify a list of main units and units search path. When you specify a main unit, all its interface declarations will be available in Java. For linked units only used declarations will be available. You can fine tune included/excluded declaration using -I and -E command line options.

The basic invocation of pas2jni:

pas2jni myunit -U/path/to/my/units;/path/to/FPC/units/*

Here you specify myunit as the main unit and provide path to your compiled units and FPC compiled units.

After successfull run of pas2jni you will get the following output files:

  • file myunitjni.pas - a generated library unit to be compiled to a shared library. It will contain all your Pascal code to be used from Java.
  • folder pas - generated Java package pas to be used in your Java program. Interface to each Pascal unit is placed to a separate Java public class.

Note: You need to use ppudump of the same version as the FPC compiler. Use the -D switch to specify correct ppudump if it is not in PATH.

Custom handlers

It is possible to define the following custom handlers in your Pascal code.

procedure JNI_OnException;

  • It is called when an unhandled Pascal exception occurs. For example, you can log a stack back trace in this handler.

Custom handlers must be public and defined in one of the main units specified when calling pas2jni.

Coding tips

Setting handlers (method pointers) in a Java code.

For example there is the following class and event handler in your Pascal code:

type TMyClass = class
//...
  property OnChange: TNotifyEvent;
//...
end;

In Java code, you add the event handler in a usual Java way:

// Create an instance of TMyClass
TMyClass myClass = TMyClass.Create();

// Set the event handler
myClass.setOnChange(
    new TNotifyEvent() {
      protected void Execute(TObject Sender) {
        // The handler code
      }
    }
  );

// Work with myClass
// ...
// ...

// Remove the handler to avoid memory leak
myClass.setOnChange(null);
// Free the object
myClass.Free();

NOTE: If you are setting handlers (method pointers) for short living objects, it is needed to explicitly remove the handlers before the object destruction by setting the null handler. Otherwise you will reach the active handlers limit (10000 handlers) and your application will crash.

Type casting

In some cases type casting for generated Java classes does not work as expected. For example, the following code will not compile:

contnrs.TObjectList objList = contnrs.TObjectList.Create();
classes.TList list = objList;

Though TObjectList is inherited from TList and similar code works in Pascal, the generated Java classes use a bit different class hierarchy in this case. An intermediate class __TList is used and TList is not ancestor for TObjectList anymore. The __TList class is needed to handle method reintroducing, which is allowed in Pascal, but not allowed in Java.

You need to use the following type casting trick in such case:

classes.TList list = new classes.TList(objList);

The same type casting method is used to cast a pointer to an object:

// Create a list
classes.TList list = classes.TList.Create();
// Get a pointer to the list
long pList = system.Pointer(list);
// Type cast the pointer to the new TObject variable
system.TObject list2 = new system.TObject(pList);

Passing Java objects to Pascal code

In some cases it is needed to pass native Java objects to your Pascal code and handle them directly using JNI functions. For example, you can pass a Java array to your Pascal code to perform some quick processing of the array's data.

To do that you need to use a specially defined type TJavaObject. Use this type for function parameters to indicate that this parameter expect a Java object without any special processing.

Also it is needed a pointer to the JNI environment in order to call JNI functions. Just add a parameter of type PJNIEnv to access the JNI environment using this parameter.

Here an example:

// Define a special type TJavaObject. The declaration must be exactly as shown here
type
  TJavaObject = type jobject;

// Define your procedure
procedure HandleJavaObject(env: PJNIEnv; obj: TJavaObject; myParam: longint);
begin
  // Work with native Java object 'obj' using JNI functions accessible via 'env'.
  // ...
end;

You will get the following generated declaration in Java for this procedure:

public native static void HandleJavaObject(Object obj, int myParam);

Command line options

Usage: pas2jni [options] <unit> [<unit2> <unit3> ...]

Options:
  -U<path> - Unit search path, semicolon delimited. Wildcards are allowed.
  -L<name> - Set output library name.
  -P<name> - Set Java package name.
  -O<path> - Set output path for Pascal files.
  -J<path> - Set output path for Java files.
  -D<prog> - Set full path to the "ppudump" program.
  -I<list> - Include the list of specified objects in the output. The list is
             semicolon delimited. To read the list from a file use -I@<file>
  -E<list> - Exclude the list of specified objects from the output. The list is
             semicolon delimited. To read the list from a file use -E@<file>
  -?       - Show this help information.