I am trying to write a native library for my application so that i can do all file operation in the native code. I read that getExternalStorageDirectory() give the path of the external storage of directory.
My question is how can i access the same without hard-coding the location to some string? Is there any function in android ndk that can give the same function as getExternalStorageDirectory() of java in C++ code?
JNI is your friend, and this isn't too complicated, as getExternalStorageDirectory is a static method. This function gets the value, and changes the working directory to it, for good measure.
#include <jni.h>
#include <unistd.h> // chdir()
#include <sys/param.h> // MAXPATHLEN
// To call Java methods when running native code inside an Android activity,
// a reference is needed to the JavaVM.
static JavaVM *gJavaVM;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
gJavaVM = vm;
return JNI_VERSION_1_6;
}
int cdToExtStorage(void) {
// Make JNI calls to get the external storage directory, and cd to it.
// To begin, get a reference to the env and attach to it.
JNIEnv *env;
int isAttached = 0;
int ret = 0;
jthrowable exception;
if (((*gJavaVM)->GetEnv(gJavaVM, (void**)&env, JNI_VERSION_1_6)) < 0) {
// Couldn't get JNI environment, so this thread is native.
if (((*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL)) < 0) {
fprintf(stderr, "Error: Couldn't attach to Java VM.\n");
return (-1);
}
isAttached = 1;
}
// Get File object for the external storage directory.
jclass classEnvironment = (*env)->FindClass(env, "android/os/Environment");
if (!classEnvironment) goto bailAndroid;
jmethodID methodIDgetExternalStorageDirectory = (*env)->GetStaticMethodID(env, classEnvironment, "getExternalStorageDirectory", "()Ljava/io/File;"); // public static File getExternalStorageDirectory ()
if (!methodIDgetExternalStorageDirectory) goto bailAndroid;
jobject objectFile = (*env)->CallStaticObjectMethod(env, classEnvironment, methodIDgetExternalStorageDirectory);
exception = (*env)->ExceptionOccurred(env);
if (exception) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
// Call method on File object to retrieve String object.
jclass classFile = (*env)->GetObjectClass(env, objectFile);
if (!classFile) goto bailAndroid;
jmethodID methodIDgetAbsolutePath = (*env)->GetMethodID(env, classFile, "getAbsolutePath", "()Ljava/lang/String;");
if (!methodIDgetAbsolutePath) goto bailAndroid;
jstring stringPath = (*env)->CallObjectMethod(env, objectFile, methodIDgetAbsolutePath);
exception = (*env)->ExceptionOccurred(env);
if (exception) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
// Extract a C string from the String object, and chdir() to it.
const char *wpath3 = (*env)->GetStringUTFChars(env, stringPath, NULL);
if (chdir(wpath3) != 0) {
fprintf(stderr, "Error: Unable to change working directory to %s.\n", wpath3);
perror(NULL);
} else if (path) {
if (chdir(path) != 0) {
fprintf(stderr, "Error: Unable to change working directory to %s.\n", path);
perror(NULL);
}
}
(*env)->ReleaseStringUTFChars(env, stringPath, wpath3);
goto retAndroid;
bailAndroid:
fprintf(stderr, "Error: JNI call failure.\n");
ret = -1;
retAndroid:
if (isAttached) (*gJavaVM)->DetachCurrentThread(gJavaVM); // Clean up.
return (ret);
}
I'm not sure the existence of that function, but I think you can achieve it by reading /proc/mounts then get info of external storage,e.g /storage/sdcardx on JellyBean, mnt/sdcardx on older versions. You can check in *.rc file, maybe it can be defined a symlink for backward compatiblility. There exist another environment variable which is used to define external storage, EXTERNAL_STORAGE so you can try getenv(EXTERNAL_STORAGE) to get mount point. Hope it can help some ways.
Related
I am trying to have Java call a C function in Android and some caveats that I am met with is the documentation for callbacks in general seem very unclear and most answers on SO are just talking about theory and posting broken links with no actual code. So please post code in your answer.
I am trying to create a joystick in java and have it send input to the NDK to perform actions.
if I can just have Java call a C function and pass a int to it ,
that would be good. but I think that wont work because C in running
it's own main loop.
perhaps I need to use C to call a java
function to get controller state and execute code from there.
I did manage to find some code Here but it seems the code while it does compile by itself, but doesn't appear to work when moved to modern code .
How should I implement call backs in a Java C relationship on Android? It seems that from the code example above that it is C that is calling Java functions and these need to be used like getters functions.
Here is some code that I have and I appear to be missing somethings because it is unclear to me how I should have these call backs performed. I will mark this code with function name and where is it located, if java or C++ (CPP) .
I think there are two ways that I would like this to work.
My Javafunction that I want to call
public int Cat(){
Log.e("JniHandler", "CAT WAS CALLED!!!");
return 333;
}
Structure information CPP . Not sure if it is needed.
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include <assert.h>
typedef struct tick_context {
JavaVM *javaVM;
jclass jniHelperClz;
jobject jniHelperObj;
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock;
int done;
} TickContext;
TickContext g_ctx;
This section works. It looks that as soon as the JVM is launched, this will initialize.
JNI_OnLoad CPP
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
memset(&g_ctx, 0, sizeof(g_ctx));
SDL_Log(" DEBUG -- JNI INIT!!!");
g_ctx.javaVM = vm;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz = env->FindClass("org/foo/bar/mainactivity");
g_ctx.jniHelperClz = (jclass)env->NewGlobalRef(clz); // hack
jmethodID jniHelperCtor = env->GetMethodID(g_ctx.jniHelperClz,"<init>", "()V");
jobject handler = env->NewObject(g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = env->NewGlobalRef(handler);
//queryRuntimeInfo(env, g_ctx.jniHelperObj); // This when getting version returns null.
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
queryRuntimeInfo CPP
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
SDL_Log("DEBUG QUERYRUNTIME INTO");
// Find out which OS we are running on. It does not matter for this app
// just to demo how to call static functions.
// Our java JniHelper class id and instance are initialized when this
// shared lib got loaded, we just directly use them
// static function does not need instance, so we just need to feed
// class and method id to JNI
jmethodID versionFunc = env->GetStaticMethodID(g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;"); // This returns null.
if (!versionFunc) {
SDL_Log("DEBUG versionFunc INTO");
//LOGE("Failed to retrieve getBuildVersion() methodID # line %d",__LINE__);
return;
}
SDL_Log("DEBUG BUILD VERSION INTO");
jstring buildVersion = (jstring)env->CallStaticObjectMethod(g_ctx.jniHelperClz, versionFunc); // hack
const char *version = env->GetStringUTFChars(buildVersion, NULL);
if (!version) {
SDL_Log("DEBUG buildVersion INTO");
//LOGE("Unable to get version string # line %d", __LINE__);
return;
}
//LOGI("Android Version - %s", version);
SDL_Log("DEBUG ANDROID VERSION %s", version);
env->ReleaseStringUTFChars(buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
env->DeleteLocalRef(buildVersion);
// Query available memory size from a non-static public function
// we need use an instance of JniHelper class to call JNI
jmethodID memFunc = env->GetMethodID(g_ctx.jniHelperClz, "getRuntimeMemorySize", "()J");
if (!memFunc) {
//LOGE("Failed to retrieve getRuntimeMemorySize() methodID # line %d",__LINE__);
SDL_Log("DEBUG Failed to retrieve memory size");
return;
}
jlong result = env->CallLongMethod(instance, memFunc);
//LOGI("Runtime free memory size: %" PRId64, result);
SDL_Log("DEBUG RUN TIME FREE MEMORY SIZE");
(void)result; // silence the compiler warning
}
This function isn't ever called and I am not sure who is the caller. CPP
extern "C" JNIEXPORT void JNICALL InvokeFunction(JNIEnv *env, jobject instance) {
SDL_Log("JNI CALL START TICKET");
pthread_t threadInfo_;
pthread_attr_t threadAttr_;
pthread_attr_init(&threadAttr_);
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = env->GetObjectClass(instance);
g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz); // hack
g_ctx.mainActivityObj = env->NewGlobalRef(instance);
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
assert(result == 0);
pthread_attr_destroy(&threadAttr_);
(void)result;
}
updateticks CPP // This code is never actually called.
void* UpdateTicks(void* context) {
SDL_Log("DEBUG -- UPDATE TICKS IS CALLED");
TickContext *pctx = (TickContext*) context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;
jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = javaVM->AttachCurrentThread(&env, NULL);
if (JNI_OK != res) {
//LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
SDL_Log("FAILED TO ATTACH THREAD");
return NULL;
}
}
jmethodID statusId = env->GetMethodID(pctx->jniHelperClz, "updateStatus", "(Ljava/lang/String;)V");
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: initializing...");
// get mainActivity updateTimer function
jmethodID timerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V");
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1,
(__kernel_suseconds_t) 0
};
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: start ticking ...");
while(1) {
gettimeofday(&beginTime, NULL);
pthread_mutex_lock(&pctx->lock);
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);
if (done) {
break;
}
env->CallVoidMethod(pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);
timersub(&curTime, &beginTime, &usedTime);
timersub(&kOneSecond, &usedTime, &leftTime);
struct timespec sleepTime;
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000;
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);
} else {
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread error: processing too long!");
}
}
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: ticking stopped");
javaVM->DetachCurrentThread();
return context;
}
I am using the latest version of Android Studio (2.2.3) and I have loaded up the HelloGL2 sample project.
I now want to add a file (any type of file) to my app, and then be able to open it and read it in the c++ code using something like c's fopen etc (any direct file access api is fine)
How do I do this?
There are two options, it will depend on your target.
If your file is a basic text configuration file, you can use both cases, but if your file is a 3D object such as (.obj, .max, .dae) you should use AssetManager class.
First option: (store your files in res raw (You can use fopen())).
Create a folder called raw inside res directory (res->raw).
Write your files in the apk private directory.
In Java:
public void writeFileToPrivateStorage(int fromFile, String toFile)
{
InputStream is = mContext.getResources().openRawResource(fromFile);
int bytes_read;
byte[] buffer = new byte[4096];
try
{
FileOutputStream fos = mContext.openFileOutput(toFile, Context.MODE_PRIVATE);
while ((bytes_read = is.read(buffer)) != -1)
fos.write(buffer, 0, bytes_read); // write
fos.close();
is.close();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
Then, call to your function:
writeFileToPrivateStorage(R.raw.your_file,"your_output_file.txt");
Get your private path
path=mContext.getApplicationContext().getFilesDir().toString();
Define your JNI funcion in Java:
public static native void setconfiguration(String yourpath);
Implement it in C/C++:
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_setconfiguration(JNIEnv * env, jobject obj, jstring path)
{
//convert your string into std::string.
const char *nativeString = env->GetStringUTFChars(config_path, 0);
//make here your fopen.
fopen(nativeString,"r");
}
Second option (use assetManager, usually for opengl resources).
The parameter, in this case, is not the path of the directory is the asset manager.
Store your files in the asset directory.
Define your native function in C/C++
public static native void yourfunction(AssetManager assetManager);
Call in java to this function:
loadYourFile(m_context.getAssets());
Create your jni function in C/C++
JNIEXPORT void Java_com_android_gl2jni_GL2JNILib_(JNIEnv * env, jobject obj,jobject java_asset_manager)
{
AAssetManager* mgr = AAssetManager_fromJava(env,java_asset_manager);
AAsset* asset = AAssetManager_open(mgr, (const char *) js, AASSET_MODE_UNKNOWN);
if (NULL == asset) {
__android_log_print(ANDROID_LOG_ERROR, NF_LOG_TAG, "_ASSET_NOT_FOUND_");
return JNI_FALSE;
}
long size = AAsset_getLength(asset);
char* buffer = (char*) malloc (sizeof(char)*size);
AAsset_read (asset,buffer,size);
__android_log_print(ANDROID_LOG_ERROR, NF_LOG_TAG, buffer);
AAsset_close(asset);
}
Note: Do not forget to add the permissions in your AndroidManifest.xml.
Note II: Do not forget to add:
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
I hope this answer helps you.
I have a.so which defines void a() and b.so which defines void b(). They are both put in the .apk so they are available to the Android application.
Now suppose that I'm calling a() through JNI. Is it possible to call b() from a() while completely bypassing JNI?
Can I do it this way in Android (the code is only for illustration, so it might have some errors)?
void a() {
void *handle = dlopen("b.so", RTLD_LAZY);
void (*b)() = dlsym(handle, "b");
b();
}
Would I need to add the fully qualified path, or is b.so already in LD_LIBRARY_PATH for the app?
You can do it this way on Android, though take care of where the shared library has been put in Android folders. It can change from a version to another.
On api 17 for example, it remains in /data/app-lib/. You can hardwrite it, but the best is to make calls to Java (through JNI) to know where the libraries should be.
We're doing something like this in our project :
JNIEnv* env;
const char* temp;
jobject oActivity = state->activity->clazz;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
strcpy(libpath, temp);
strcat(libpath, "/");
Then you push your dlopen + dlsym combo in the fight and it should work.
As mentioned here : How do I load a shared object in C++?
There are two ways of loading shared objects in C++
For either of these methods you would always need the header file for the object you want to use. The header will contain the definitions of the classes or objects you want to use in your code.
#include "blah.h"
int main()
{
ClassFromBlah a;
a.DoSomething();
}
gcc yourfile.cpp -lblah
Dynamically (In Linux):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("libm.so", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
exit(1);
}
dlerror(); /* Clear any existing error */
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
exit(1);
}
printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}
PS : for the dynamic approach, it depends on platform : on Linux, you use dlopen, on windows, you use LoadLibrary.
I have some code:
AAsset* pAsset = AAssetManager_open(pAssetManager, "asset_test.txt", AASSET_MODE_STREAMING);
DebugPrint(pAsset?"pAsset not NULL\n":"pAsset NULL");
if (pAsset)
{
char buf[1024];
AAsset_read(pAsset, buf, sizeof(buf));
DebugPrint(buf);
AAsset_close(pAsset);
}
This code always prints "pAsset NULL" in logcat.
I put the asset_test.txt file in my assets directory, and I looked in the .apk to make sure it exists by renaming the .apk to .zip and opening it with 7zip.
I have some more code:
AAssetDir* pAssetDir = AAssetManager_openDir(pAssetManager, sDirectory.c_str());
if (!pAssetDir)
{
DebugPrint("pAssetDir NULL\n");
return;
}
const char* pszDir;
while ((pszDir = AAssetDir_getNextFileName(pAssetDir)) != NULL)
{
DebugPrint(pszDir);
}
AAssetDir_close(pAssetDir);
This code prints nothing. In other words, no files are ever found in the assets directory, regardless of what paths I pass into it.
Note: DebugPrint is just a prettier looking wrapper around __android_log_print().
I passed the Activity into AAssetManager_fromJava(), while I should have passed the AssetManager in. If you pass the wrong class into AAssetManager_fromJava() it will fail without printing anything to logcat.
How to get the asset manager with JNI:
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();
jclass activity_class = env->GetObjectClass(activity);
jmethodID activity_class_getAssets = env->GetMethodID(activity_class, "getAssets", "()Landroid/content/res/AssetManager;");
jobject asset_manager = env->CallObjectMethod(activity, activity_class_getAssets); // activity.getAssets();
global_asset_manager = env->NewGlobalRef(asset_manager);
pAssetManager = AAssetManager_fromJava(env, global_asset_manager);
Stash that asset manager pointer somewhere and use it for all your AAssetManager_*() functions from now on.
I have a function in c++ and I want to call it it java for android.
I write it and build its library but the problem is I can not find file in emulator.
I use adb push text.txt sdcard to copy text.txt to sdcard of emulator.
but I could not find the file with the C function.
I call Simple("hello");
This is my C file :
jint Java_X_XX_XXX_wipeActivity_Simple(JNIEnv* env, jobject javaThis,jstring jstr)
{
jboolean iscopy;
const char *address = (*env)->GetStringUTFChars(env, jstr, &iscopy);
return replaceZero("/sdcard/text.txt");
//remove(address);
}
jint replaceZero(const char *address)
{
FILE *fp;
fp = fopen(address,"r+");
if(fp == 0)
{
printf("can not find!!");
return -1;
}
else
{
//do sth with file
return 0;
}
}
and I will see -1 in main program.
I found the answer - I needed to add this to manifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
I cannot open it without permission.