从Delphi直接调用Android NDK函数的困难

可以通过JNI和NDK从Delphi调用Android C函数。 为了实现这一点,相当多的工作,并build议直接调用NDK函数。 为此,我创build了一个小的示例文件来声明一个外部的C函数,这些函数是我在Delphi源代码中find的。 在<path to delphi>\source\rtl\android更具体。

我创build了一个非常小的testing程序来testing直接从Delphi调用C函数的function。 所有源代码,你会发现下面,这是我目前正在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. 

初始化和终结是必要的,因为链接错误是指一些缺less的初始化和终止代码。 调用类:

  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. 

与中间的本地图书馆miditest 。 这是使用ndk-build 。 生成的库libmiditest.so被复制到C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\android-ndk-r8e\platforms\android-14\arch-arm\usr\lib中, Delphi已经放置了自己的库。

  #include <jni.h> int test_1 (int n) // little test for callability { return n * n; } 

当我做一个ndk-build一个文件libmiditest.so在子目录libs\armeabi-v7a 。 我把这个文件复制到<path to your ndk directory>\platforms\android-14\arch-arm\usr\lib 。 由于我在开始时有一些链接错误(错误的名称和那种愚蠢的错误),我使用readelf -AWs libmiditest.so来产生一个符号列表和期望的库结构。 名称test_1与arm v7架构(我使用Nexus 7进行testing)在符号列表中。 当我运行Delphi程序时,它立即崩溃在Android上:“不幸的是,DLL_Test_Propject已经停止”。 检查adb输出(见下文)看起来应该是文件libDLL_Test_Project.so 。 我用libmiditest.soreplace了单元DLL_external中的libDLL_Test_Project.so并将/usr/lib/libmiditest.so复制到/usr/lib/libDLL_Test_Project.so 。 这没有帮助。

有谁明白为什么Delphi生成的应用程序尝试加载自己的库? 更好:任何build议,我应该通过Delphi调用一个Android C函数?

  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. 

更新1

从我收集的意见,代码可能是一个更大的系统的一部分。 这个代码是一个小型的独立程序。 本地代码库的确如此小。

更新2

正如Arioch所指出的那样,通过使用静态链接(或者Windows的隐式加载),当程序库不加载时,主程序将不会加载。 这解释了上面提到的adb消息。 querstion是这样的:为什么libmiditest.so不加载?

Solutions Collecting From Web of "从Delphi直接调用Android NDK函数的困难"

一个解决方案是通过Arioch'The在他的评论中提出的动态绑定图书馆。 在他的维基链接中描述了如何做到这一点的机制。 要在库中链接,请使用Posix.Dlfcn单元中的dlopen

一个必须提供一个路径给图书馆。 在下面的示例代码中,我在sdcard0目录中创建了目录Data\d并将sdcard0复制到其中。 该目录在其他系统上可能不同。 事实上,这可能解释了静态绑定不起作用的原因(还)。 在查找库时,库不会复制到系统搜索的路径中。

  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.