Accessing Java code from Delphi

In this article, we will use native Android code (in Java) inside of a Delphi project. We will create a Java class, compile it to a JAR file (or DEX file for XE5 and 6), and add it to the project. The project's target has to be Android, obviously.

During deployment, JAR file is converted to bytecode, which is stored inside of APK file. The bytecode then gets executed when the application runs inside of Java Virtual Machine. This is made possible by Java Native Interface (JNI) framework, which is a part of Firemonkey.


  1. Let's create a Java file that finds square root:


  2. import java.lang.*;
    
    class SquareRoot
    {
        // how many digits are after decimal point
        public int precision = 5;
    
        public void _Setprecision (int value) {
            this.precision = value;
        }
    
        public int _Getprecision() {
            return this.precision;
        }
    
        // static method that does not require an instance
        public static String DisplayDescription() {
            return "finds square root";
        }
    
        public String findSquareRoot (double value) {
            double sr = Math.sqrt (value);
            return String.format ("%." + this.precision + "f", sr);
        }
    }
    

    Save this file as SquareRoot.java. Let's assume this file is saved to folder C:\MyJavaFiles.

  3. Now in the same directory, i.e. C:\MyJavaFiles, create a batch file build.bat. This file should create *.class, *.jav, and *.dex files. The content is show below. Location of the files may be different for your system, so change them appropriately.



  4. rem create a variable for current directory
    set PROJ_DIR=%CD%

    rem directory where javac.exe is located
    set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_25\bin

    pause

    rem find where file android.jar is located
    set ANDROID_PLATFORM="C:\program files\adt-bundle-windows-x86-20131030\sdk\platforms\android-20"
    rem or
    set ANDROID_PLATFORM="C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk\platforms\android-17"

    rem compile SquareRoot.java - create SquareRoot.class file
    "%JAVA_HOME%\javac.exe" -source 1.6 -target 1.6 -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar -d "%PROJ_DIR%" SquareRoot.java

    pause

    rem create jar file that contains SquareRoot.class
    "%JAVA_HOME%\jar.exe" cvf SquareRoot.jar SquareRoot.class

    pause

    rem if you have Delphi XE7 or greater, you can stop here
    rem if you have Delphi XE5 or XE6, continue with this batch file

    rem directory where dx.jar is located
    set DX_LIB=C:\program files\adt-bundle-windows-x86-20131030\sdk\build-tools\android-4.4\lib
    rem or
    set DX_LIB=C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk\build-tools\android-4.2.2\lib

    rem converting from jar to dex
    "%JAVA_HOME%\java.exe" -Xmx1024M -Djava.ext.dirs="%DX_LIB%" -jar "%DX_LIB%\dx.jar" --dex --verbose --output="%PROJ_DIR%\SquareRoot_classes.dex" --positions=lines "%PROJ_DIR%\SquareRoot.jar"

    pause

    rem default Delphi dex file
    set DEFAULT_DEX="C:\Program Files\Embarcadero\RAD Studio\12.0\lib\android\debug\classes.dex"

    rem merge dex files
    "%JAVA_HOME%\java.exe" -cp "%DX_LIB%\dx.jar" com.android.dx.merge.DexMerger "%PROJ_DIR%\output_classes.dex" "%PROJ_DIR%\SquareRoot_classes.dex" %DEFAULT_DEX%

    pause

    This should generate the following files: SquareRoot.class, SquareRoot.jar, SquareRoot_classes.dex, and output_classes.dex.

    1. If you use Delphi XE7 or greater, you need to add JAR file to the project (skip this if you use XE5 or XE6): in Project Manager, expand Target Platforms, Android, and right-click on Libraries. Then select Add from the pop-up menu and choose SquareRoot.jar file from the dialog:


    2. add JAR file

      For more info, see

      Adding a Java Library to Your Application

    3. If you are using Delphi XE5 or XE6, you will need output_classes.dex file (which we created earlier). During deployment, Delphi supplies classes.dex file, which then becomes part of the APK file. We need to deploy our output_classes.dex file instead.


    4. There is a detailed explanation how to do this, see section Deploying the classes.dex File:

      Using a Custom Set of Java Libraries In Your RAD Studio Android Apps


  5. Now we need to create a Delphi wrapper that would define Java methods and properties in Delphi

  6. This can be done by utility java2pas, which is available for download here:

    Software Union

    Download it and place java2Pas.exe file into the same directory, i.e. C:\MyJavaFiles. Then run it as following:


    C:\MyJavaFiles>java2Pas.exe SquareRoot.class

    This will produce file SquareRoot.pas:

    //
    // Generated by JavaToPas v1.4 20150925 - 020927
    // *** unregistered evaluation copy ***
    ////////////////////////////////////////////////////////////////////////////////
    unit SquareRoot;

    interface

    uses
    AndroidAPI.JNIBridge,
    Androidapi.JNI.JavaTypes;

    type
    JSquareRoot = interface;

    JSquareRootClass = interface(JObjectClass)
    ['{AF8DA385-B3BD-45BE-98B0-E519C43365C7}']
    function DisplayDescription : JString; cdecl;
    end;

    [JavaSignature('SquareRoot')]
    JSquareRoot = interface(JObject)
    ['{70731C8B-5C1A-40F4-8C04-A641D8273214}']
    function _Getprecision : Integer; cdecl; overload;
    function _Getprecision : Integer; cdecl; overload;
    function findSquareRoot(Doubleparam0 : Double) : JString; cdecl;
    procedure _Setprecision(Integerparam0 : Integer) ; cdecl; overload;
    procedure _Setprecision(Value : Integer) ; cdecl; overload;
    property precision : Integer read _Getprecision write _Setprecision;
    end;

    TJSquareRoot = class(TJavaGenericImport<JSquareRootClass, JSquareRoot>)
    end;

    implementation

    procedure RegisterTypes;
    begin
    TRegTypes.RegisterType ('SquareRoot.JSquareRoot', TypeInfo(SquareRoot.JSquareRoot));
    end;

    initialization
    RegisterTypes;

    end.



    Notice that functions are grouped into two blocks, one starts with

    JSquareRootClass = interface(JObjectClass),

    and the other with

    [JavaSignature('SquareRoot')].

    Make sure you put static function into the first block, and non-static - into the second. Java2pas utility sometimes does it wrong. Also add procedure RegisterTypes.


  7. Now we can call our functions from Delphi

  8. Add file SquareRoot.pas to the project. Then switch to the main form and place a button on it.

    Switch to the code and reference unit SquareRoot and a few files of JNI framework, see the code below:

    unit android_SquareRoot;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes,
      System.Variants, FMX.Types, FMX.Controls, FMX.Forms,
      FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
      AndroidAPI.JNIBridge,
      Androidapi.JNI.JavaTypes,
      FMX.Helpers.Android,
      Androidapi.JNI.App,
      SquareRoot;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.fmx}
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
        a_square_root : JSquareRoot;
    begin
        // call static function
        ShowMessage (JStringToString (TJSquareRoot.JavaClass.DisplayDescription));
    
        a_square_root := TJSquareRoot.Create;
        a_square_root.precision := 3;
        ShowMessage (JStringToString (a_square_root.findSquareRoot (2)));
    end;
    
    end.
    


    Notice that on the button press, we call static function DisplayDescription.

    Then we create an object a_square_root, which is an instance of class TJSquareRoot.

    Then we set property precision and call function findSquareRoot.

    When we run the application and click on the button, we should see two message boxes:

    "finds square root" and "1.414"