Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difficulties with calling an Android NDK function from directly Delphi

It is possible to call an Android C-function from Delphi via JNI and NDK. To implement this is quite a lot of work and it was suggested to call the NDK-functions directly. To that effect I created a small example file to declare an external C function along the lines I found in the Delphi source code. More specific in <path to delphi>\source\rtl\android.

I created a very small testprogram to test the functionality of calling a C-function directly from Delphi. All source code you'll find below, this is what I am currently testing.

  unit DLL_external;

  interface

  const
     MIDI_Lib = '/usr/lib/libmiditest.so';
     test_fun = 'test_1';

  function test_1 (n: Integer): Integer; cdecl;
    external MIDI_Lib name test_fun;

  implementation

  initialization

  finalization

  end.

The initialization and finalization are necessary because else linking errors occur referring to some missing initialization and finalization code. The calling class:

  unit DLL_Test_Main;

  interface

  uses
    System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
    FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
    DLL_external;

  //{$I Androidapi.inc}

  type
     TForm1 = class(TForm)
      Button_Load: TButton;
      Label1: TLabel;

      procedure Button_LoadClick (Sender: TObject);
      procedure FormCreate(Sender: TObject);

     public
        procedure call_external_function (value: Integer);
     end; // Class: TForm1 //

  var
     Form1: TForm1;

  implementation

  {$R *.fmx}

  procedure TForm1.FormCreate (Sender: TObject);
  begin
     Label1.Text := 'External function not called yet';
  end; // FormCreate //

  procedure TForm1.Button_LoadClick (Sender: TObject);
  begin
     call_external_function (3);
  end; // Button_LoadClick //

  procedure TForm1.call_external_function (value: integer);
  var n: Int32;
  begin
     n := test_1 (value);
     Label1.Text := Format ('%d = test_1 (%d)', [n, value]);
  end; // call_external_function //

  end.

Together with a native library miditest. This was built using ndk-build. The resulting library libmiditest.so was copied to C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\android-ndk-r8e\platforms\android-14\arch-arm\usr\lib as this is the place where Delphi has placed its own libraries.

  #include <jni.h>

  int test_1 (int n) // little test for callability
  {
     return n * n;
  }

When I do an ndk-build a file libmiditest.so is produced in the subdirectory libs\armeabi-v7a. I copied this file to <path to your ndk directory>\platforms\android-14\arch-arm\usr\lib. As I had some linking errors in the beginning (wrong names and that kind of stupid errors) I used readelf -AWs libmiditest.so to produce a symbol list and the expected architecture of the library. The name test_1 was in the symbol list as was the arm v7 architecture (I use a Nexus 7 for testing). When I run the Delphi program it crashes immediately on Android: “Unfortunately, DLL_Test_Propject has stopped”. Examining the adb output (see below) it appears that the file libDLL_Test_Project.so is expected. I replaced the libmiditest.so in unit DLL_external by libDLL_Test_Project.so and copied /usr/lib/libmiditest.so to /usr/lib/libDLL_Test_Project.so. That did not help.

Does anyone understand why the Delphi generated app tries to load a library of itself? And better: any suggestion how I should call an Android C-function via Delphi?

  I/InputReader(  608): Reconfiguring input devices.  changes=0x00000010
  D/dalvikvm(  799): GC_FOR_ALLOC freed 2003K, 15% free 14582K/16964K, paused 29ms, total 29ms
  I/PCKeyboard(  799): Loaded dictionary, len=841005
  I/HK/LatinKeyboardBaseView(  799): onMeasure width=1200
  I/HK/LatinKeyboardBaseView(  799): onMeasure width=1200
  D/Documents( 3358): Used cached roots for com.android.providers.downloads.documents
  D/Documents( 3358): Used cached roots for com.android.externalstorage.documents
  D/Documents( 3358): Used cached roots for com.android.providers.media.documents
  D/Documents( 3358): Used cached roots for com.google.android.apps.docs.storage
  D/Documents( 3358): Update found 7 roots in 28ms
  D/BackupManagerService(  608): Received broadcast Intent { act=android.intent.action.PACKAGE_ADDED dat=package:com.embarcadero.DLL_Test_Project flg=0x4000010 (has extras) }
  V/BackupManagerService(  608): addPackageParticipantsLocked: #1
  D/SystemBroadcastService(  987): Received broadcast action=android.intent.action.PACKAGE_ADDED and uri=
  W/ContextImpl(  987): Implicit intents with startService are not safe: Intent { act=com.google.android.gms.games.service.INTENT } android.content.ContextWrapper.startService:494 com.google.android.gms.games.service.GamesIntentService.a:101 com.google.android.gms.games.service.GamesIntentService.b:368
  I/ActivityManager(  608): Delay finish: com.android.vending/com.google.android.finsky.receivers.PackageMonitorReceiver$RegisteredReceiver
  I/ActivityManager(  608): Resuming delayed broadcast
  I/ActivityManager(  608): Delay finish: com.google.android.apps.plus/.service.PackagesMediaMonitor
  I/ActivityManager(  608): Resuming delayed broadcast
  V/GelStubAppWatcher( 3631): onReceive: android.intent.action.PACKAGE_ADDED
  I/Icing.InternalIcingCorporaProvider( 3631): Updating corpora: A: com.embarcadero.DLL_Test_Project, C: MAYBE
  I/ActivityManager(  608): START u0 {flg=0x10800000 cmp=com.estrongs.android.pop/.app.InstallMonitorActivity (has extras)} from pid 3466
  D/dalvikvm(  608): GC_EXPLICIT freed 1385K, 11% free 19671K/22028K, paused 3ms+8ms, total 194ms
  D/dalvikvm(  608): WAIT_FOR_CONCURRENT_GC blocked 24ms
  D/AndroidRuntime( 4437): Shutting down VM
  D/dalvikvm( 4437): GC_CONCURRENT freed 95K, 16% free 560K/660K, paused 0ms+0ms, total 2ms
  W/InputMethodManagerService(  608): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@42c98f48 attribute=null, token = android.os.BinderProxy@42b91f10
  D/dalvikvm( 3631): GC_CONCURRENT freed 560K, 6% free 10268K/10860K, paused 2ms+2ms, total 23ms
  D/AndroidRuntime( 4476):
  D/AndroidRuntime( 4476): >>>>>> AndroidRuntime START com.android.internal.os.RuntimeInit <<<<<<
  D/AndroidRuntime( 4476): CheckJNI is OFF
  D/dalvikvm( 4476): Trying to load lib libjavacore.so 0x0
  D/dalvikvm( 4476): Added shared lib libjavacore.so 0x0
  D/dalvikvm( 4476): Trying to load lib libnativehelper.so 0x0
  D/dalvikvm( 4476): Added shared lib libnativehelper.so 0x0
  D/dalvikvm( 4476): No JNI_OnLoad found in libnativehelper.so 0x0, skipping init
  D/dalvikvm( 4476): Note: class Landroid/app/ActivityManagerNative; has 179 unimplemented (abstract) methods
  D/AndroidRuntime( 4476): Calling main entry com.android.commands.am.Am
  I/ActivityManager(  608): START u0 {flg=0x10000000 cmp=com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativeActivity (has extras)} from pid 4476
  D/dalvikvm(  608): GC_FOR_ALLOC freed 807K, 12% free 19517K/22028K, paused 63ms, total 63ms
  D/AndroidRuntime( 4476): Shutting down VM
  D/dalvikvm( 4476): GC_CONCURRENT freed 96K, 15% free 586K/684K, paused 0ms+0ms, total 2ms
  D/dalvikvm( 4507): Late-enabling CheckJNI
  I/ActivityManager(  608): Start proc com.embarcadero.DLL_Test_Project for activity com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativeActivity: pid=4507 uid=10113 gids={50113, 3003, 1028, 1015}
  I/dalvikvm( 4507): Enabling JNI app bug workarounds for target SDK version 9...
  V/PhoneStatusBar(  667): setLightsOn(true)
  D/AndroidRuntime( 4507): Shutting down VM
  W/dalvikvm( 4507): threadid=1: thread exiting with uncaught exception (group=0x41ccbba8)
  E/AndroidRuntime( 4507): FATAL EXCEPTION: main
  E/AndroidRuntime( 4507): Process: com.embarcadero.DLL_Test_Project, PID: 4507
  E/AndroidRuntime( 4507): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativ
  eActivity}: java.lang.IllegalArgumentException: Unable to load native library: /data/app-lib/com.embarcadero.DLL_Test_Project-1/libDLL_Test_Project.so
  E/AndroidRuntime( 4507):        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195)
  E/AndroidRuntime( 4507):        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
  E/AndroidRuntime( 4507):        at android.app.ActivityThread.access$800(ActivityThread.java:135)
  E/AndroidRuntime( 4507):        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
  E/AndroidRuntime( 4507):        at android.os.Handler.dispatchMessage(Handler.java:102)
  E/AndroidRuntime( 4507):        at android.os.Looper.loop(Looper.java:136)
  E/AndroidRuntime( 4507):        at android.app.ActivityThread.main(ActivityThread.java:5017)
  E/AndroidRuntime( 4507):        at java.lang.reflect.Method.invokeNative(Native Method)
  E/AndroidRuntime( 4507):        at java.lang.reflect.Method.invoke(Method.java:515)
  E/AndroidRuntime( 4507):        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
  E/AndroidRuntime( 4507):        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
  E/AndroidRuntime( 4507):        at dalvik.system.NativeStart.main(Native Method)
  E/AndroidRuntime( 4507): Caused by: java.lang.IllegalArgumentException: Unable to load native library: /data/app-lib/com.embarcadero.DLL_Test_Project-1/libDLL_Test_Project.so
  E/AndroidRuntime( 4507):        at android.app.NativeActivity.onCreate(NativeActivity.java:183)
  E/AndroidRuntime( 4507):        at com.embarcadero.firemonkey.FMXNativeActivity.onCreate(FMXNativeActivity.java:67)
  E/AndroidRuntime( 4507):        at android.app.Activity.performCreate(Activity.java:5231)
  E/AndroidRuntime( 4507):        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
  E/AndroidRuntime( 4507):        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159)
  E/AndroidRuntime( 4507):        ... 11 more
  W/ActivityManager(  608):   Force finishing activity com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativeActivity
  I/WindowManager(  608): Screenshot max retries 4 of Token{42a06c48 ActivityRecord{42a42de8 u0 com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNat
  iveActivity t17 f}} appWin=Window{42a1bab8 u0 Starting com.embarcadero.DLL_Test_Project} drawState=4
  W/WindowManager(  608): Screenshot failure taking screenshot for (1200x1920) to layer 21015
  W/ActivityManager(  608): Activity pause timeout for ActivityRecord{42a42de8 u0 com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativeActivity t17 f}
  E/WindowManager(  608): Starting window AppWindowToken{4308ff58 token=Token{42a06c48 ActivityRecord{42a42de8 u0 com.embarcadero.DLL_Test_Project/com.embarcadero.firemonkey.FMXNativeActivity t17}}} timed out
  I/Process ( 4507): Sending signal. PID: 4507 SIG: 9
  W/InputMethodManagerService(  608): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@42a1abd0 attribute=null, token = android.os.BinderProxy@42b91f10
  I/ActivityManager(  608): Process com.embarcadero.DLL_Test_Project (pid 4507) has died.
  D/Finsky  ( 1567): [1] 5.onFinished: Installation state replication succeeded.

Update 1

From the comments I gathered that the code might be part of a bigger system. This code is a small stand-alone program. The native code library is indeed as small as you see here.

Update 2

As Arioch'The points out is that by using static linking (or implicit loading in Windows terms) the main program will not load when the library does not load. That explains the adb message mentioned above. The querstion is thus: why does libmiditest.so not load?

like image 427
Arnold Avatar asked Jan 10 '14 05:01

Arnold


1 Answers

One solution is by dynamically binding the library as suggested by Arioch'The in his comments. The mechanism on how to do that is described in his wiki link. To link in the library use dlopen from the Posix.Dlfcn unit.

One has to provide a path to the library though. In the example code below I created directory Data\d in the sdcard0 directory and copied libmiditest.so into it. This directory may be different on other systems. In fact, this might explain the reason that static binding does not work (yet). The library is not copied into the paths the system searches when looking for libraries.

  unit DLL_Test_Main;

  interface

  uses
     System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
     FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
     Posix.Dlfcn, FMX.Layouts, FMX.Memo, System.Math;


  const
     lib_path = '/storage/sdcard0/Data/d/libmiditest.so';
     fun_name = 'test_1';

  type
     TForm1 = class(TForm)
        Button_Load: TButton;
        Memo1: TMemo;

        procedure Button_LoadClick (Sender: TObject);
        procedure FormCreate(Sender: TObject);

      protected
         Lib_Handle: THandle;
      public
         procedure call_external_function (value: Integer);
      end; // Class: TForm1 //

  var
     Form1: TForm1;

  implementation

  {$R *.fmx}

  procedure TForm1.FormCreate (Sender: TObject);
  begin
     Lib_Handle := THandle (dlopen (lib_path, RTLD_LAZY));
     if Lib_Handle = 0 then
     begin
        Memo1.Lines.Add ('Cannot open library: ' + lib_path);
     end else
     begin
        Memo1.Lines.Add ('Opened library: ' + lib_path);
     end; // if
  end; // FormCreate //

  procedure TForm1.Button_LoadClick (Sender: TObject);
  begin
     call_external_function (RandomRange (0, 99));
  end; // Button_LoadClick //

  procedure TForm1.call_external_function (value: integer);
  var
     test_1: function (n: integer): integer; cdecl;
     n: Int32;
  begin
     if Lib_Handle <> 0 then
     begin
        test_1 := dlsym (Lib_Handle, fun_name);
        if not assigned (test_1) then
        begin
           Memo1.Lines.Add ('Cannot create function: ' + fun_name);
        end else
        begin
           n := test_1 (value);
           Memo1.Lines.Add (Format ('%d = %s (%d)', [n, fun_name, value]));
        end;
     end;
  end; // call_external_function //

  end.
like image 128
Arnold Avatar answered Nov 03 '22 13:11

Arnold