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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With