I am looking since some time for a way to play MIDI in Delphi XE5 with Android targeted. Several of my questions before were related to this "quest" :-). I have filed two requests to embarcadero: #119422 to add MIDI support to TMediaPlayer and #119423 to add a MIDI framework to Firemonkey, but that did not help. I have succeeded at last. As I know there are some more people who were looking for MIDI on Android I post this question with answer for documentation.
The Android system has an internal MIDI synthesizer. You can access it via the Android NDK. I have described this in an article containing some downloads. This answer is a short description of this article. What you'll see here is a Proof of Concept. It will show how to play MIDI notes on an Android system but needs improvement. Suggestions for improvement are welcome :-)
Use Eclipse to interface with the Java project. I presume you have Delphi XE5 with the Mobile pack, which gives you two things already installed: the Android SDK and NDK. Do not reinstall these by downloading the complete Android SDK from Google. Download and install the Eclipse Android Development Tools (ADT) plugin and follow the installation instructions. This allows you to use the Android SDK/NDK environment already installed by Delphi XE5 (you will find the paths in Delphi, Options | Tools | SDK Manager). In this way Delphi and Eclipse will share the same SDK and NDK.
I used the MIDI library developed by Willam Farmer. He also has the full SoniVox documentation available which I couldn't get elsewhere. His driver comes with a full example (Java) program. I created my own project with and changed the package name to org.drivers.midioutput, so all functions are prefixed by Java_org_drivers_midioutput_MidiDriver_ (see code below).
When you wish to compile the midi.c jus open a command window, and call ndk-build in the project directory. Some error messages are ok. The mips and x86 libraries were not built in my case.
There is one point though you should be aware of: the path to the ndk may not contain spaces. As you let the Delphi installer install Delphi there is bound to be a space in it: the subdirectory Rad Studio in that terrible long file name where Delphi installs the SDK and NDK. In order to work around this problem, create an empty directory on drive C:, call it C:\ndk. Use MKLINK to link this directory to the ndk directory. This can only be done from an elevated command prompt and as you do so you'll lose your network connections. The link is persistent so just close the command prompt and open another, unelevated one, and all should work now. Now you can really use ndk-build.
midi.c - the NDK interface with the SoniVox
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
// Some slight modifications by Arnold Reinders. Added a test function and changed
// the package to org.drivers.midioutput. The original copyright still applies
#include
// for EAS midi
#include "eas.h"
#include "eas_reverb.h"
// determines how many EAS buffers to fill a host buffer
#define NUM_BUFFERS 4
// EAS data
static EAS_DATA_HANDLE pEASData;
const S_EAS_LIB_CONFIG *pLibConfig;
static EAS_PCM *buffer;
static EAS_I32 bufferSize;
static EAS_HANDLE midiHandle;
// This function is added to test whether the functionality of this NDK code can be accesses
// without needing to access the MIDI system. Added for testing purposes
jint
Java_org_drivers_midioutput_MidiDriver_version (JNIEnv *env, jobject clazz)
{
return 3;
}
// init EAS midi
jint
Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
// get the library configuration
pLibConfig = EAS_Config();
if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
return 0;
// calculate buffer size
bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
NUM_BUFFERS;
// init library
if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
return 0;
// select reverb preset and enable
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
EAS_PARAM_REVERB_CHAMBER);
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
EAS_FALSE);
// open midi stream
if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return 0;
}
return bufferSize;
}
// midi config
jintArray
Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
jobject clazz)
{
jboolean isCopy;
if (pLibConfig == NULL)
return NULL;
jintArray configArray = (*env)->NewIntArray(env, 4);
jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);
config[0] = pLibConfig->maxVoices;
config[1] = pLibConfig->numChannels;
config[2] = pLibConfig->sampleRate;
config[3] = pLibConfig->mixBufferSize;
(*env)->ReleaseIntArrayElements(env, configArray, config, 0);
return configArray;
}
// midi render
jint
Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
jobject clazz,
jshortArray shortArray)
{
jboolean isCopy;
EAS_RESULT result;
EAS_I32 numGenerated;
EAS_I32 count;
jsize size;
// jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
// void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,
// void* GetPrimitiveArrayCritical(JNIEnv*, jarray, jboolean*);
// void ReleasePrimitiveArrayCritical(JNIEnv*, jarray, void*, jint);
if (pEASData == NULL)
return 0;
buffer =
(EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);
size = (*env)->GetArrayLength(env, shortArray);
count = 0;
while (count < size) { result = EAS_Render(pEASData, buffer + count, pLibConfig->mixBufferSize, &numGenerated);
if (result != EAS_SUCCESS)
break;
count += numGenerated * pLibConfig->numChannels;
}
(*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);
return count;
}
// midi write
jboolean
Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
jobject clazz,
jbyteArray byteArray)
{
jboolean isCopy;
EAS_RESULT result;
jint length;
EAS_U8 *buf;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
length = (*env)->GetArrayLength(env, byteArray);
result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);
(*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);
if (result != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
// shutdown EAS midi
jboolean
Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return JNI_FALSE;
}
if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
When the library is built by ndk-build this will prefix the compiled library with lib and replace the extension by .so. So midi.c will compile to libmidi.so. Compiled libraries are added to the download, so you needn't compile midi.c.
MidiDriver.Java declares an interface, an audioTrack and a thread to handle all these. I haven't taken the trouble to find how exactly this works. Because I didn't know how to handle an interface and such in Delphi I created a Java wrapper for MidiDriver: class MIDI_Output. This class is used to interface with Delphi.
Class MidiDriver is the interface between Java and the C-functions that call SoniVox functions. Class MIDI_Output is the interface between Java and Delphi. MIDI_Output creates an instance of MidiDriver.
Class MidiDriver - the interface with the NDK
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
package org.drivers.midioutput;
import java.io.File;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
// MidiDriver
public class MidiDriver implements Runnable
{
private static final int SAMPLE_RATE = 22050;
private static final int BUFFER_SIZE = 4096;
private Thread thread;
private AudioTrack audioTrack;
private OnMidiStartListener listener;
private short buffer[];
// Constructor
public MidiDriver ()
{
Log.d ("midi", " *** MidiDriver started");
}
public void start ()
{
// Start the thread
thread = new Thread (this, "MidiDriver");
thread.start ();
} // start //
#Override
public void run ()
{
processMidi ();
} // run //
public void stop ()
{
Thread t = thread;
thread = null;
// Wait for the thread to exit
while (t != null && t.isAlive ())
Thread.yield ();
} // stop //
// Process MidiDriver
private void processMidi ()
{
int status = 0;
int size = 0;
// Init midi
Log.d ("midi", " *** processMIDI");
if ((size = init()) == 0)
return;
buffer = new short [size];
// Create audio track
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE, AudioTrack.MODE_STREAM);
if (audioTrack == null)
{
shutdown ();
return;
} // if
// Call listener
if (listener != null)
listener.onMidiStart();
// Play track
audioTrack.play();
// Keep running until stopped
while (thread != null)
{
// Render the audio
if (render (buffer) == 0)
break;
// Write audio to audiotrack
status = audioTrack.write (buffer, 0, buffer.length);
if (status < 0) break; } // while // Render and write the last bit of audio if (status > 0)
if (render(buffer) > 0)
audioTrack.write(buffer, 0, buffer.length);
// Shut down audio
shutdown();
audioTrack.release();
} // processMidi //
public void setOnMidiStartListener (OnMidiStartListener l)
{
listener = l;
} // setOnMidiStartListener //
public static void load_lib (String libName)
{
File file = new File (libName);
if (file.exists ())
{
System.load (libName);
} else
{
System.loadLibrary (libName);
}
} // Listener interface
public interface OnMidiStartListener
{
public abstract void onMidiStart ();
} // OnMidiStartListener //
// Native midi methods
public native int version ();
private native int init ();
public native int [] config ();
private native int render (short a []);
public native boolean write (byte a []);
private native boolean shutdown ();
// Load midi library
static
{
System.loadLibrary ("midi");
}
}
Class MIDI_Output - providing a wrap for class MidiDriver
package org.drivers.midioutput;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import org.drivers.midioutput.MidiDriver.OnMidiStartListener;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;
public class MIDI_Output implements OnMidiStartListener
{
protected MidiDriver midi_driver;
protected MediaPlayer media_player;
public MIDI_Output ()
{
// Create midi driver
midi_driver = new MidiDriver();
Log.d ("midi", " *** midi_driver opened with version " +
String.valueOf (midi_driver.version ()));
// Set onmidistart listener to this class
if (midi_driver != null)
midi_driver.setOnMidiStartListener (this);
} // MIDI_Output () //
public int test_int (int n)
{
int sq = n * n;
// Log.d ("midi", " *** test_int computes " + String.valueOf (sq));
return n * n;
}
public void start ()
{
if (midi_driver != null)
{
midi_driver.start ();
Log.d ("midi", " *** midi_driver.start ()");
}
} // start //
public void stop ()
{
if (midi_driver != null)
{
midi_driver.stop ();
Log.d ("midi", " *** midi_driver.stop ()");
}
stopSong ();
} // stop //
// Listener for sending initial midi messages when the Sonivox
// synthesizer has been started, such as program change. Runs on
// the MidiDriver thread, so should only be used for sending midi
// messages.
#Override
public void onMidiStart()
{
Log.d ("midi", " *** onSMidiStart");
// TODO
}
// Sends a midi message
protected void putShort (int m, int n, int v)
{
if (midi_driver != null)
{
byte msg [] = new byte [3];
msg [0] = (byte) m;
msg [1] = (byte) n;
msg [2] = (byte) v;
Log.d ("midi", " *** putShort (" + String.valueOf (m) + ", " + String.valueOf (n) + ", " + String.valueOf (v) + ")");
midi_driver.write (msg);
} // if
} // putShort //
public boolean isPlayingSong ()
{
return media_player != null;
} // isPlayingSong //
public void playSong (String audioFilename)
{
String audioPath;
try
{
FileDescriptor fd = null;
audioFilename = "/Data/d/song.mid";
File baseDir = Environment.getExternalStorageDirectory ();
audioPath = baseDir.getAbsolutePath () + audioFilename;
Log.d ("midi", " *** Look for file: " + audioPath);
FileInputStream fis = new FileInputStream (audioPath);
fd = fis.getFD ();
if (fd != null)
{
Log.d ("midi", " *** Found file, trying to play: " + audioPath);
MediaPlayer mediaPlayer = new MediaPlayer ();
mediaPlayer.setDataSource (fd);
mediaPlayer.prepare ();
mediaPlayer.start ();
}
} catch (Exception e)
{
Log.d ("midi", " *** Exception while trying to play file: " + e.getMessage ());
}
}
public void stopSong ()
{
if (media_player != null)
{
media_player.stop ();
media_player.release ();
media_player = null;
} // if
} // stopSong //
} // Class: MIDI_Output //
From MidiDriver and MIDI_Output an Eclipse Android project was created, a MainActivity added and run. After eliminating a lot of bugs I got it up and running. A useful tool is the android debugger (adb). Open a command windows and run adb -d logcat. I have added a lot of log.d ('midi", " *** message') statements in the code in order to see where things went wrong. Remove them if you don't like them, but if you are unknown to Android (which I still am to a great extent) it is a useful way to see what happens in your application. Log works as wel in Delphi, see the Delphi Sources.
When the program compiles well you have a MIDI_Output.apk package in your project\bin directory. This package will be used by Delphi to run the Java methods.
Java can be accessed from Delphi by using JNI. A hands-on tutorial can be found on the site of RedTitan. The ideas of this tutorial were implemented in class TMIDI_Output_Device.
As you may see a constant string test_apk_fn is defined with the path to the MIDI_Output.apk Android package. This string provides JNI with the name where the Java library can be found. The string javaClassName provides the package name necessary to interface with Java. With these string the Delphi JNI is able to find the requested classes.
Class TMIDI_Output_Device - providing a Delphi wrap for Java class MIDI_Output
unit MIDI_Output_Device;
interface
uses
System.SysUtils,
FMX.Types,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.Jni,
Androidapi.JNI.Dalvik,
Androidapi.JNI.GraphicsContentViewText;
const
test_apk_fn = '/storage/sdcard0/Data/d/MIDI_Output.apk';
type
TMIDI_Output_Device = class (TObject)
private
JavaEnv: PJNIEnv;
context: JContext;
CL: JDexClassLoader;
JavaObject: JObject;
JavaObjectID: JNIObject;
jTempClass: Jlang_Class;
jTemp: JObject;
oTemp: TObject;
jLocalInterface: ILocalObject;
optimizedpath_jfile: JFile;
dexpath_jstring, optimizedpath_jstring: JString;
fun_version: JNIMethodID;
fun_start: JNIMethodID;
fun_put_short: JNIMethodID;
fun_play_song: JNIMethodID;
public
constructor Create;
procedure setup_midi_output (class_name: string);
procedure put_short (status, data_1, data_2: integer);
procedure play_song (file_name: string);
end; // Class: MIDI_Output_Device //
implementation
uses
FMX.Helpers.Android;
constructor TMIDI_Output_Device.Create;
begin
setup_midi_output ('MIDI_Output');
end; // Create //
procedure TMIDI_Output_Device.setup_midi_output (class_name: string);
var
javaClassName: string;
ji: JNIInt;
jiStatus, jiData_1, jiData_2: JNIValue;
begin
javaClassName := Format ('org.drivers.midioutput/%s', [class_name]);
context := SharedActivityContext;
JavaEnv := TJNIResolver.GetJNIEnv;
Log.d ('Loading external library from "' + test_apk_fn + '"');
dexpath_jstring := StringToJString (test_apk_fn);
// locate/create a directory where our dex files can be put
optimizedpath_jfile := context.getDir (StringToJString ('outdex'), TJContext.javaclass.mode_private);
optimizedpath_jstring := optimizedpath_jfile.getAbsolutePath;
Log.d ('Path for DEX files = ' + JStringToString (optimizedpath_jstring));
Log.d ('APK containing target class = ' + JStringToString (dexpath_jstring));
CL := TJDexClassLoader.JavaClass.init (dexpath_jstring, optimizedpath_jstring, nil, TJDexClassLoader.JavaClass.getSystemClassLoader);
// Test whether the Dex class is loaded, if not, exit
if not assigned (CL) then
begin
Log.d ('?Failed to get DEXClassLoader');
exit;
end; // if
// Load the Java class
jTempClass := CL.loadClass (StringToJString (javaClassName));
if assigned (jTempClass) then
begin
jTemp := jTempClass; // N.B You could now import the entire class
if jTemp.QueryInterface (ILocalObject,jLocalInterface) = S_OK then
begin
// supports ilocalobject
JavaObject := jTempClass.newInstance;
oTemp := JavaObject as TObject;
JavaObjectID := tjavaimport (otemp).GetObjectID;
Log.d (oTemp.ClassName);
// try to access the version function from the midi_output class
fun_version := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'version', '()I');
if not assigned (fun_version) then
begin
Log.d ('?fun_version not supported');
end else
begin
ji := JavaEnv^.CallIntMethodA (JavaEnv, JavaObjectID, fun_version, nil);
Log.d ('version returns ' + inttostr (ji));
end; // if
// try to access the start function from the midi_output class
fun_start := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'start', '()V');
if not assigned (fun_start) then
begin
Log.d ('?fun_start not supported');
end else
begin
JavaEnv^.CallVoidMethodA (JavaEnv, JavaObjectID, fun_start, nil);
Log.d ('fun_start found');
end; // if
// try to access the putShort function from the midi_output class
fun_put_short := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'putShort','(III)V');
if not assigned (fun_put_short) then
begin
Log.d ('?putShort not supported');
end else
begin
Log.d (Format (' ### putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
put_short ($90, 60, 127);
end; // if
// try to access the playSong function from the midi_output class
fun_play_song := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'playSong', '(Ljava/lang/String)V');
if not assigned (fun_play_song) then
begin
Log.d ('?playSong not supported');
end else
begin
Log.d (' ### playSong found');
end; // if
end else
begin
Log.d ('?Could not derive ILOCALOBJECT');
end;
end else Log.d ('?'+javaClassname+' not found')
end; // setup_midi_output //
procedure TMIDI_Output_Device.put_short (status, data_1, data_2: integer);
var
jiStatus, jiData_1, jiData_2: JNIValue;
x: array of JNIOBJECT;
begin
jiStatus.i := status;
jiData_1.i := data_1;
jiData_2.i := data_2;
setLength (x, 3);
x [0] := jiStatus.l;
x [1] := jiData_1.l;
x [2] := jiData_2.l;
Log.d (Format ('putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_put_short, x);
end; // put_short //
procedure TMIDI_Output_Device.play_song (file_name: string);
var
x: array of JNIObject;
begin
SetLength (x, 1);
x [0] := StringToJNIString (JavaEnv, file_name);
Log.d ('playSong (' + file_name + ')');
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_play_song, x);
end; // playSong //
end. // Unit: MIDI_Output_Device //
Delphi now knows where to find the Java classes. In theory it should now be able to find libmidi.so because an Android package is a .zip file containing the necessary files to run the Java package. If you open MIDI_Output.apk with WinZip or WinRar then you see these files. In the archive you'll find a directory lib which contains libmidi.so for the ARM 5 and 7 platforms. When launching the program and having adb -d logcat running in a command window adb says so much as unpacking MIDI_Output.apk. Well, it might do so, but libmidi.so will not be found.
Libmidi.so should be added to the \usr\lib somewhere under the \platforms directory of the Android SDK. The complete link in my case is: C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\android-ndk-r8e\platforms\android-14\arch-arm\usr\lib. This should help as I found out some time ago.
Using the call chain as I have shown here one may call MIDI functions in Delphi generated Android code. There are some questions regarding this technique:
Wouldn't it be easier to call the NDK function directly? It is
possible to call NDK functions directly from Delphi in the same
way as DLL's. However, class MidiDriver adds a lot of functionality
which I do not understand at this moment. This functionality must be
programmed in C or Pascal when call the NDK functions directly.
In the code from Bill Farmer he uses the MediaPlayer to play MIDI
files. Alas the MediaPlayer can only be accessed from an Activity and
I do not know how to transfer the Delphi MainActivity to a JNI Java
function. So this functionality does not work as of yet.
Native libraries are packed into the .apk but not unpacked in such a
way that the JavaVM detects it. Now the libmidi.so has to be put
manually into \usr\lib.
Even worse is that a hard link must be added to the .apk package. The
package should be deployed automatically to the /data/app-lib of the
application, else creating an app with JNI classes and installing it
from the Play Store seems impossible.
Another way would have been to use a native Android version of BASS plus BASSMIDI plugin. There is good sample code coming with it. And Ian's support is excellent. You can find both of them here:
BASS lib for Android: http://www.un4seen.com/forum/?topic=13225
BASSMIDI plugin (d/l): http://www.un4seen.com/download.php?bassmidi24-linux
There is a .NET version on his site as well, and a 3rd party project on Sourceforge that exposes the API to Java. I'm not allowed to post more than two links (yet) but you can find it with a quick search for nativebass
Fairly late answer but it might still help someone else looking for a quicker way or one that works in Delphi alone.
Related
I am using JNI with Android Studio 1.5.1 targeting Android API 18 and my question is:
Q) Without using a tool or changing/modifying the Dalvik VM source code, how can I find the memory address of a Java local variable on the Dalvik Stack from native code?
For example, I try to use the following code (adapted from the Internet) to find the memory address of a Java local variable magicNumber = 0x23420023 but I am getting segmentation fault errors.
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("MyLibrary");
}
public native boolean findMagicNumber(int pid, int tid);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int magicNumber = 0x23420023 ;
int pid = android.os.Process.myPid();
int tid = android.os.Process.myTid();
findMagicNumber(pid, tid);
}
}
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "com_example_magicnumber2_MainActivity.h"
#include <unistd.h>
#include <memory.h>
#define ENOENT 2 /* No such file or directory */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define EINVAL 22 /* Invalid argument */
jboolean validAddress(char* address)
{
if ((access(address, F_OK)==-1) && !(errno == ENOENT) && !(errno == ENAMETOOLONG))
return JNI_FALSE;
else if ((access(address, F_OK)==-1) && (errno == ENOMEM) ||
(access(address, F_OK)==-1) && (errno == EACCES) ||
(access(address, F_OK)==-1) && (errno == EFAULT) ||
(access(address, F_OK)==-1) && (errno == EINVAL))
return JNI_FALSE;
else if (address == NULL)
return JNI_FALSE;
else
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL Java_com_example_magicnumber2_MainActivity_findMagicNumber(JNIEnv *env, jobject obj, jint pid, jint tid) {
long long startaddr, endaddr, size, offset, inode;
char permissions[8], device[8], filename[200], line[250];
char *start, *end, *candidate;
int result, i = 0;
char filepath[100];
sprintf(filepath,"/proc/%d/task/%d", pid, tid);
FILE* file = fopen(filepath, "r");
jboolean found = JNI_FALSE;
while (fgets(line, sizeof(line), file) && !found) {
sscanf(line,"%llx-%llx %s %llx %s %llx", &startaddr, &endaddr, permissions, &offset, device, &inode);
start = startaddr;
end = endaddr;
mprotect( (void*)start , (end-start), PROT_READ);
candidate = memchr( start, 0x14, (end-start));
while( validAddress(candidate) && !found){
if ((validAddress(candidate[2]) && (candidate[2]== 0x23)) &&
(validAddress(candidate[3]) && (candidate[3] == 0x00)) &&
(validAddress(candidate[4]) && (candidate[4] == 0x42)) &&
(validAddress(candidate[5]) && (candidate[5] == 0x23))){
__android_log_print(ANDROID_LOG_DEBUG,"***","Location=%p WE FOUND IT!", candidate);
found = JNI_TRUE;
break;
return JNI_TRUE;
}
else if ((validAddress(candidate)) &&
validAddress(candidate=memchr(candidate+1, 0x14, (end-candidate)))){;
}
}
}
}
This is an update:
The previous code that I posted was not the latest one, here is the latest one:
The Java Code:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("MyLibrary");
}
public native boolean findMagicNumber(int pid, int tid);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int magicNumber = 0x23420023 ;
int pid = android.os.Process.myPid();
int tid = android.os.Process.myTid();
findMagicNumber(pid, tid);
System.out.println("magicNumber = " + magicNumber );
}
}
The native Code:
JNIEXPORT jboolean JNICALL Java_com_example_magicnumber2_MainActivity_findMagicNumber(JNIEnv *env, jobject obj, jint pid, jint tid) {
long long startaddr, endaddr, size, offset, inode;
char permissions[8], device[8], filename[200], line[250];
char *start, *end, *candidate;
int result, i = 0;
char filepath[100];
sprintf(filepath,"/proc/%d/task/%d/maps", pid, tid);
FILE* file = fopen(filepath, "r");
jboolean found = JNI_FALSE;
while (fgets(line, sizeof(line), file) && !found) {
sscanf(line,"%llx-%llx %s %llx %s %llx %s", &startaddr, &endaddr, permissions, &offset, device, &inode, filename);
if (((strstr(filename, "apk#classes.dex")))==NULL) {
continue;
}
__android_log_print(ANDROID_LOG_DEBUG, "*****************", "%llx-%llx %s %llx %s %llx %s",
startaddr, endaddr, permissions, offset, device, inode, filename);
start = startaddr;
end = endaddr;
candidate = memchr( start, 0x14, (end-start));
while( candidate !=0 && !found){
if ((candidate[2]== 0x23) &&
(candidate[3] == 0x00) &&
(candidate[4] == 0x42) &&
(candidate[5] == 0x23)){
__android_log_print(ANDROID_LOG_DEBUG,"********************************************************************","WE FOUND IT at %p!!!", candidate);
found = JNI_TRUE;
break;
}
else
candidate=memchr(candidate+1, 0x14, (end-candidate));
}
}
}
This code is working and it can find the magic number but it finds it in the memory region mapped to /data/dalvik-cache/data#app#com.example.magicnumber2-1.apk#classes.dex which is not the Dalvik stack.
However, by running the above code and by looking at these two papers : paper1 (appendix B, only the egg-hunting code, I do not need to change any Dalvik code, so skip the code changing part) and paper2, we can notice the following (also to comments on fadden's remarks below):
(1) It seems that the int value magicNumber is stored in one Dalvik register. Also it seems that it is stored in the Dalvik stack and it is not on the native code stack because the int variable magicNumber is declared and assigned a value in the Java code section.
(2) According to the paper1, this answer and as evidence by running the attached latest code, we are not searching for 0x14 using the memchr function but we want to make sure we are at the beginning of a memory cell that store the int in ARM CPUs.
(3) I do not need to call the findMagicNumber function again. I just need to find the memory address of the magic number in the Dalvik stack
(4) I do not need to find nearby variables to the MagicNumber, so this is not a problem in my case.
(5) The project is targeting only Dalvik, so ART is not a problem
(6) I agree, using mprotect() is not a good idea and was not necessary in my case.
(7) If you refer to paper2, you can see that access() can be used for something that it is not designed for, checking if a virtual memory address is valid or not. I’m not using access() for any file related operations, although it was written for that purpose
(8) I do not need to change the variable. I just need the address of the variable magicNumber on the Dalvik stack programmatically without using any tool or changing the Dalvik source code
I want to know which of the memory regions of /proc/pid/task/tid/maps Dalvik uses to store its stack.
As you can see in paper1, the authors in section B.2, line #4, did not explain which memory region from procs/maps they used to assign the start and end variables their values.
It looks like you're trying to open /proc/[pid]/task/[tid]/maps, walk through the map, and manually scan through every address range for a magic number. Setting aside the fact that you're opening the task directory rather than the maps magic file in that directory, this approach has a few problems:
You're not guaranteed that the value is unique. If it appears somewhere else in memory -- perhaps because the value was stored in two different Dalvik registers -- you'll be in the wrong place. If the JIT compiler has compiled this stretch of code, you don't know if the "active" value will be on the managed stack or in a spilled register on the native stack.
You're searching for 0x14, which isn't part of your magic number.
You're scanning for the local variable on the thread that created it. Even if you find it and can change it, once the findMagicNumber method returns, the stack-allocated variable will disappear. It's not guaranteed to be in the same place if the method is called again.
If you were hoping to find related variables nearby, you will again have trouble if the JIT compiler rearranges things.
ART compiles almost everything ahead of time, so this is even less likely to be useful there.
I'm also not sure why you're calling mprotect(), rather than simply skipping the segments that aren't readable. You're forcing the permissions to read-only, disabling write and execute permission, which will cause you to crash when execute permission is disabled for the chunk of code that's executing, or when a thread tries to execute and touches its stack.
The access() system call takes a filename, not a memory address, and reports file permissions. I don't think there's an "is this address valid" call, but since you just sscanf'ed the map entry you shouldn't need one.
The only reliable way to find and change the value of a local variable is to use the JDWP debug interface. The compiler and debugger support work together to make reliable read-write access to local variables possible.
To sum up: the code is seriously broken, the approach is unsound. What problem are you trying to solve?
I have only been able to find solutions dated 2010 and earlier. So I wanted to see if there was a more up-to-date stance on this.
I'd like to avoid using Java and purely use C++, to access files (some less-or-more than 1MB) stored away in the APK. Using AssetManager means I can't access files like every other file on every other operating system (including iOS).
If not, is there a method in C++ where I could somehow map fopen/fread to the AssetManager APIs?
I actually found pretty elegant answer to the problem and blogged about it here.
The summary is:
The AAssetManager API has NDK bindings. This lets you load assets from the APK.
It is possible to combine a set of functions that know how to read/write/seek against anything and disguise them as a file pointer (FILE*).
If we create a function that takes an asset name, uses AssetManager to open it, and then disguises the result as a FILE* then we have something that's very similar to fopen.
If we define a macro named fopen we can replace all uses of that function with ours instead.
My blog has a full write up and all the code you need to implement in pure C. I use this to build lua and libogg for Android.
Short answer
No. AFAIK mapping fread/fopen in C++ to AAssetManager is not possible. And if were it would probably limit you to files in the assets folder. There is however a workaround, but it's not straightforward.
Long Answer
It IS possible to access any file anywhere in the APK using zlib and libzip in C++.
Requirements : some java, zlib and/or libzip (for ease of use, so that's what I settled for). You can get libzip here: http://www.nih.at/libzip/
libzip may need some tinkering to get it to work on android, but nothing serious.
Step 1 : retrieve APK location in Java and pass to JNI/C++
String PathToAPK;
ApplicationInfo appInfo = null;
PackageManager packMgmr = parent.getPackageManager();
try {
appInfo = packMgmr.getApplicationInfo("com.your.application", 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Unable to locate APK...");
}
PathToAPK = appInfo.sourceDir;
Passing PathToAPK to C++/JNI
JNIEXPORT jlong JNICALL Java_com_your_app(JNIEnv *env, jobject obj, jstring PathToAPK)
{
// convert strings
const char *apk_location = env->GetStringUTFChars(PathToAPK, 0);
// Do some assigning, data init, whatever...
// insert code here
//release strings
env->ReleaseStringUTFChars(PathToAPK, apk_location);
return 0;
}
Assuming that you now have a std::string with your APK location and you have zlib on libzip working you can do something like this:
if(apk_open == false)
{
apk_file = zip_open(apk_location.c_str(), 0, NULL);
if(apk_file == NULL)
{
LOGE("Error opening APK!");
result = ASSET_APK_NOT_FOUND_ERROR;
}else
{
apk_open = true;
result = ASSET_NO_ERROR;
}
}
And to read a file from the APK:
if(apk_file != NULL){
// file you wish to read; **any** file from the APK, you're not limited to regular assets
const char *file_name = "path/to/file.png";
int file_index;
zip_file *file;
struct zip_stat file_stat;
file_index = zip_name_locate(apk_file, file_name, 0);
if(file_index == -1)
{
zip_close(apk_file);
apk_open = false;
return;
}
file = zip_fopen_index(apk_file, file_index, 0);
if(file == NULL)
{
zip_close(apk_file);
apk_open = false;
return;
}
// get the file stats
zip_stat_init(&file_stat);
zip_stat(apk_file, file_name, 0, &file_stat);
char *buffer = new char[file_stat.size];
// read the file
int result = zip_fread(file, buffer, file_stat.size);
if(result == -1)
{
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
return;
}
// do something with the file
// code goes here
// delete the buffer, close the file and apk
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
Not exactly fopen/fread but it gets the job done. It should be pretty easy to wrap this to your own file reading function to abstract the zip layer.
I'm starting to work on Android with the NDK and I want to check what Android API level the device is running on my c code. How can I do that?
At first I thought I could use definition __ANDROID_API__ under /android/api-level.h but that was a wrong assumption.
**Note: I'm NOT asking how to check API level via java.
I've just been working on some JNI code and wanted to query the running OS build version as described by Jona. I wanted to do this as early as possible (ie in JNI_OnLoad) so would rather not hand it in from Java as described by FUBUs. Since API Level 4 this information has been available as the int field SDK_INT in android.os.Build.VERSION which is what I'm looking up in this snippet:
static const char* TAG = "testjnjni";
static bool _disableHttpKeepAliveOnBuggyPlatforms(JNIEnv *env)
{
// Based on article here:
// http://android-developers.blogspot.co.uk/2011/09/androids-http-clients.html
// Which references the issue documented here:
// http://code.google.com/p/android/issues/detail?id=2939
// We need to set "http.keepAlive" to "false" if running an OS version earlier than Froyo (API Level 8)
if ((*env)->ExceptionCheck(env))
return false; // already got an exception pending
bool success = true;
// VERSION is a nested class within android.os.Build (hence "$" rather than "/")
jclass versionClass = (*env)->FindClass(env, "android/os/Build$VERSION");
if (NULL == versionClass)
success = false;
jfieldID sdkIntFieldID = NULL;
if (success)
success = (NULL != (sdkIntFieldID = (*env)->GetStaticFieldID(env, versionClass, "SDK_INT", "I")));
jint sdkInt = 0;
if (success)
{
sdkInt = (*env)->GetStaticIntField(env, versionClass, sdkIntFieldID);
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "sdkInt = %d", sdkInt);
}
if (success && sdkInt < 8)
{
jclass systemClass = (*env)->FindClass(env, "java/lang/System");
if (NULL == systemClass)
success = false;
jmethodID setPropertyMethodID = NULL;
if (success)
success = (NULL != (setPropertyMethodID = (*env)->GetStaticMethodID(env, systemClass, "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")));
jstring propString = NULL;
if (success)
success = (NULL != (propString = (*env)->NewStringUTF(env, "http.keepAlive")));
jstring valueString = NULL;
if (success)
success = (NULL != (valueString = (*env)->NewStringUTF(env, "false")));
jobject oldValueString = NULL;
if (success)
{
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "Disabling http.keepAlive");
oldValueString = (*env)->CallStaticObjectMethod(env, systemClass, setPropertyMethodID, propString, valueString);
}
// cleanup
(*env)->DeleteLocalRef(env, propString);
(*env)->DeleteLocalRef(env, valueString);
(*env)->DeleteLocalRef(env, oldValueString);
(*env)->DeleteLocalRef(env, systemClass);
}
// cleanup
(*env)->DeleteLocalRef(env, versionClass);
return success;
}
All the information I needed to write this code is clearly documented in the PDF entitled "The Java Native Interface: Programmer's Guide and Specification" by Sheng Liang which used to be available from Oracle's site here but can also be purchased as a book (e.g. here). JNI is a very powerful technology and I would strongly recommend any developer wanting to get to grips with it reads that PDF as well as the Android Developers' JNI Tips.
Oh, and finally, it cannot be stressed how important it is to understand local and global references. Android's Developers blog has a good article here covering changes in ICS (nothing that veers away from the JNI specification but good points to reiterate!).
There is exists fully native solutions:
Prior Android L you can use __system_property_get("ro.build.version.sdk") from sys/system_properties.h
For Android L and newer you can use popen("getprop ro.build.version.sdk")
Use the compile time toolchain constant check #if __ANDROID_API__ < 21 to switch between those two implementations.
You can pass one time, the API Level what you get in JAVA to the C code and stock it in global variable. For me, is the easier way to do that.
My app is partly written in native app using C/C++. The problem is that whenever C/C++ part crashes for some reason the app dies and then restarts automatically. This causes all kinds of messy problems
Now of course, it should not crash in the native part and I'm trying to weed out all reasons why it would happen. However, if it does happen I'd like to:
Quit gracefully
If it does die, at least not try to restart automatically.
I'm curious as to why this behaviour happens. After some search I tried putting the following line in the main activity element of the AndroidManifest.xml:
android:finishOnTaskLaunch="true"
but the automatic restore still happens.
Anyone knows why this is happening and how to change it?
UPDATE:
I think a more fundamental question is,
Is there something similar to a callback if there is a native crash?
One of the answers suggested 'handling crash signals'. I'd be grateful for any links on how it can be done at an application or module level.
As it stands currently, if there is a crash the app just disappears, there's nothing in logcat, so no debugging is possible.
Try to handle crash signals (SIGSEGV etc.) and send kill to yourself in signal handler. This trick helps me.
Example:
#include <signal.h>
#include <unistd.h>
static void signal_handler(int signal, siginfo_t *info, void *reserved)
{
kill(getpid(),SIGKILL);
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
struct sigaction handler;
memset(&handler, 0, sizeof(handler));
handler.sa_sigaction = signal_handler;
handler.sa_flags = SA_SIGINFO;
sigaction(SIGILL, &handler, NULL);
sigaction(SIGABRT, &handler, NULL);
sigaction(SIGBUS, &handler, NULL);
sigaction(SIGFPE, &handler, NULL);
sigaction(SIGSEGV, &handler, NULL);
sigaction(SIGSTKFLT, &handler, NULL);
return(JNI_VERSION_1_6);
}
UPDATE2
if you want to see crashlog in android logcat you should use this signal handler
static void signal_handler(int signal, siginfo_t *info, void *reserved)
{
struct sockaddr_un addr;
size_t namelen;
socklen_t alen;
int s, err;
char name[] = "android:debuggerd";
namelen = strlen(name);
// Test with length +1 for the *initial* '\0'.
if ((namelen + 1) > sizeof(addr.sun_path)) {
errno = EINVAL;
return;
}
/* This is used for abstract socket namespace, we need
* an initial '\0' at the start of the Unix socket path.
*
* Note: The path in this case is *not* supposed to be
* '\0'-terminated. ("man 7 unix" for the gory details.)
*/
memset (&addr, 0, sizeof addr);
addr.sun_family = AF_LOCAL;
addr.sun_path[0] = 0;
memcpy(addr.sun_path + 1, name, namelen);
alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
s = socket(AF_LOCAL, SOCK_STREAM, 0);
if(s < 0) return;
RETRY_ON_EINTR(err,connect(s, (struct sockaddr *) &addr, alen));
if (err < 0) {
close(s);
s = -1;
}
pid_t tid = gettid();
if(s>=0)
{
/* debugger knows our pid from the credentials on the
* local socket but we need to tell it our tid. It
* is paranoid and will verify that we are giving a tid
* that's actually in our process
*/
int ret;
RETRY_ON_EINTR(ret, write(s, &tid, sizeof(unsigned)));
if (ret == sizeof(unsigned)) {
/* if the write failed, there is no point to read on
* the file descriptor. */
RETRY_ON_EINTR(ret, read(s, &tid, 1));
//notify_gdb_of_libraries();
}
close(s);
}
wait(NULL);
kill(getpid(),SIGKILL);
}
I took it from android source (can't insert link because android.git.kernel.org is down), but I am not sure that it will work in future Android releases
By default your application should not be automatically restarting. Generally one would have to register for this kind of thing, e.g. via the AlarmManager/keep alives.
Do you have a service as part of your application?
I have some problems when using the dynamic loading API (<dlfcn.h>: dlopen(), dlclose(), etc) on Android.
I'm using NDK standalone toolchain (version 8) to compile the applications and libraries.
The Android version is 2.2.1 Froyo.
Here is the source code of the simple shared library.
#include <stdio.h>
int iii = 0;
int *ptr = NULL;
__attribute__((constructor))
static void init()
{
iii = 653;
}
__attribute__((destructor))
static void cleanup()
{
}
int aaa(int i)
{
printf("aaa %d\n", iii);
}
Here is the program source code which uses the mentioned library.
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
void *handle;
typedef int (*func)(int);
func bbb;
printf("start...\n");
handle = dlopen("/data/testt/test.so", RTLD_LAZY);
if (!handle)
{
return 0;
}
bbb = (func)dlsym(handle, "aaa");
if (bbb == NULL)
{
return 0;
}
bbb(1);
dlclose(handle);
printf("exit...\n");
return 0;
}
With these sources everything is working fine, but when I try to use some STL functions or classes, the program crashes with a segmentation fault, when the main() function exits, for example when using this source code for the shared library.
#include <iostream>
using namespace std;
int iii = 0;
int *ptr = NULL;
__attribute__((constructor))
static void init()
{
iii = 653;
}
__attribute__((destructor))
static void cleanup()
{
}
int aaa(int i)
{
cout << iii << endl;
}
With this code, the program crashes with segmentation fault after or the during main() function exit.
I have tried couple of tests and found the following results.
Without using of STL everything is working fine.
When use STL and do not call dlclose() at the end, everything is working fine.
I tried to compile with various compilation flags like -fno-use-cxa-atexit or -fuse-cxa-atexit, the result is the same.
What is wrong in my code that uses the STL?
Looks like I found the reason of the bug. I have tried another example with the following source files:
Here is the source code of the simple class:
myclass.h
class MyClass
{
public:
MyClass();
~MyClass();
void Set();
void Show();
private:
int *pArray;
};
myclass.cpp
#include <stdio.h>
#include <stdlib.h>
#include "myclass.h"
MyClass::MyClass()
{
pArray = (int *)malloc(sizeof(int) * 5);
}
MyClass::~MyClass()
{
free(pArray);
pArray = NULL;
}
void MyClass::Set()
{
if (pArray != NULL)
{
pArray[0] = 0;
pArray[1] = 1;
pArray[2] = 2;
pArray[3] = 3;
pArray[4] = 4;
}
}
void MyClass::Show()
{
if (pArray != NULL)
{
for (int i = 0; i < 5; i++)
{
printf("pArray[%d] = %d\n", i, pArray[i]);
}
}
}
As you can see from the code I did not used any STL related stuff.
Here is the source files of the functions library exports.
func.h
#ifdef __cplusplus
extern "C" {
#endif
int SetBabe(int);
int ShowBabe(int);
#ifdef __cplusplus
}
#endif
func.cpp
#include <stdio.h>
#include "myclass.h"
#include "func.h"
MyClass cls;
__attribute__((constructor))
static void init()
{
}
__attribute__((destructor))
static void cleanup()
{
}
int SetBabe(int i)
{
cls.Set();
return i;
}
int ShowBabe(int i)
{
cls.Show();
return i;
}
And finally this is the source code of the programm that uses the library.
main.cpp
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include "../simple_lib/func.h"
int main()
{
void *handle;
typedef int (*func)(int);
func bbb;
printf("start...\n");
handle = dlopen("/data/testt/test.so", RTLD_LAZY);
if (!handle)
{
printf("%s\n", dlerror());
return 0;
}
bbb = (func)dlsym(handle, "SetBabe");
if (bbb == NULL)
{
printf("%s\n", dlerror());
return 0;
}
bbb(1);
bbb = (func)dlsym(handle, "ShowBabe");
if (bbb == NULL)
{
printf("%s\n", dlerror());
return 0;
}
bbb(1);
dlclose(handle);
printf("exit...\n");
return 0;
}
Again as you can see the program using the library also does not using any STL related stuff, but after run of the program I got the same segmentation fault during main(...) function exit. So the issue is not connected to STL itself, and it is hidden in some other place. Then after some long research I found the bug.
Normally the destructors of static C++ variables are called immediately before main(...) function exit, if they are defined in main program, or if they are defined in some library and you are using it, then the destructors should be called immediately before dlclose(...).
On Android OS all destructors(defined in main program or in some library you are using) of static C++ variables are called during main(...) function exit. So what happens in our case? We have cls static C++ variable defined in library we are using. Then immediately before main(...) function exit we call dlclose(...) function, as a result library closed and cls becomes non valid. But the pointer of cls is stored somewhere and it's destructor should be called during main(...) function exit, and because at the time of call it is already invalid, we get segmentation fault. So the solution is to not call dlclose(...) and everything should be fine. Unfortunately with this solution we cannot use attribute((destructor)) for deinitializing of something we want to deinitialize, because it is called as a result of dlclose(...) call.
I have a general aversion to calling dlclose(). The problem is that you must ensure that nothing will try to execute code in the shared library after it has been unmapped, or you will get a segmentation fault.
The most common way to fail is to create an object whose destructor is defined in or calls code defined in the shared library. If the object still exists after dlclose(), your app will crash when the object is deleted.
If you look at logcat you should see a debuggerd stack trace. If you can decode that with the arm-eabi-addr2line tool you should be able to determine if it's in a destructor, and if so, for what class. Alternatively, take the crash address, strip off the high 12 bits, and use that as an offset into the library that was dlclose()d and try to figure out what code lives at that address.
I encountered the same headache on Linux. A work-around that fixes my segfault is to put these lines in the same file as main(), so that dlclose() is called after main returns:
static void* handle = 0;
void myDLClose(void) __attribute__ ((destructor));
void myDLClose(void)
{
dlclose(handle);
}
int main()
{
handle = dlopen(...);
/* ... real work ... */
return 0;
}
The root cause of dlclose-induced segfault may be that a particular implementation of dlclose() does not clean up the global variables inside the shared object.
You need to compile with -fpic as a compiler flag for the application that is using dlopen() and dlclose(). You should also try error handling via dlerror() and perhaps checking if the assignment of your function pointer is valid, even if it's not NULL the function pointer could be pointing to something invalid from the initialization, dlsym() is not guaranteed to return NULL on android if it cannot find a symbol. Refer to the android documentation opposed to the posix compliant stuff, not everything is posix compliant on android.
You should use extern "C" to declare you function aaa()