So I'm creating an Android app that uses Unity ... I am getting some assetbundle from Unity but I do not know the url before Unity starts. Thus Unity needs to call a function on the Native (Android) side to get the url.
I have been lost for awhile on how to do this (the Unity documentation is quite terrible imho). I decided to use the NDK to help me out. Without Unity everything about my library file works out... now the issue is calling these C functions in Unity.
Here is my lib:
#include <jni.h>
#include <string.h>
#include <android/log.h>
#define DEBUG_TAG "NDK_blahy"
extern "C" {
jstring Java_com_blah_blah_getURL(JNIEnv * env, jobject this);
void Java_com_blah_blah_setURL(JNIEnv * env, jobject this, jstring URL);
}
jstring url;
void Java_com_blah_blah_setURL(JNIEnv * env, jobject this, jstring URL)
{
url = URL;
jboolean isCopy;
const char * szLogThis = (*env)->GetStringUTFChars(env, URL, &isCopy);
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK:LC: [%s]", szLogThis);
(*env)->ReleaseStringUTFChars(env, URL, szLogThis);
}
jstring Java_com_lyfelotto_blah_blah_getURL(JNIEnv * env, jobject this)
{
return url;
}
My unity code loads the library just fine (using [DllImport ("libname")]).
Now, if I load the function "correctly" like this private static extern jstring Java_com_lyfelotto_blah_blah_getURL(JNIEnv * env, jobject this) bad things happen
Have I gone about this the wrong way? (Like I said, all I need to do is get a string). Any ideas?
I suggest ditch the NDK. Use the Android java plugin.
In YourPlugin.cs:
using UnityEngine;
using System.Collections;
using System.IO;
#if UNITY_ANDROID
public class UnityUrlPlugin {
// Logs the user out and invalidates the token
public static string getUrl() {
if( Application.platform != RuntimePlatform.Android )
return;
var pluginClass = new AndroidJavaClass("com.you.UnityUrlPlugin") ;
AndroidJavaObject plugin = pluginClass.CallStatic<AndroidJavaObject>("instance");
return plugin.Call<string>("getURL");
}
}
#endif
Then, in UnityUrlPlugin.java:
package com.you;
import android.content.ContentValues;
import android.content.Intent;
import android.os.Environment;
public class UnityUrlPlugin {
private static UnityUrlPlugin m_instance;
public static UnityUrlPlugin instance() {
if(m_instance == null)
m_instance = new UnityUrlPlugin();
return m_instance;
}
private UnityUrlPlugin(){
}
public String getURL() {
return "http://blah.com";
}
}
And throw UnityUrlPlugin.jar in to Assets/Plugins/Android folder.
No need for NDK!
You could likely send the 'dynamic' url in your call to getURL. Do something like:
return plugin.Call<string>("getURL", new object[] {"http://thisIsMyUrl.com"});
and then your java looking like this:
public String getURL(Object newUrl) {
return newUrl.ToString();
}
Related
I'm using a open source project in Gitgub.
I have to change package name of it.
Primary part of archArm.c function code source is :
jstring
Java_com_github_hiteshsondhi88_libffmpeg_ArmArchHelper_cpuArchFromJNI(JNIEnv* env, jobject obj)
And primary ArchArmHelper.java is :
package com.github.hiteshsondhi88.libffmpeg;
class ArmArchHelper {
static {
System.loadLibrary("ARM_ARCH");
}
native String cpuArchFromJNI();
boolean isARM_v7_CPU(String cpuInfoString) {
return cpuInfoString.contains("v7");
}
boolean isNeonSupported(String cpuInfoString) {
// check cpu arch for loading correct ffmpeg lib
return cpuInfoString.contains("-neon");
}
}
Now I edited part of archArm.c function to :
jstring
Java_com_emad_amerian_libffmpeg_ArmArchHelper_cpuArchFromJNI(JNIEnv* env, jobject obj)
And I edited my ArchArmHelper.java to :
package com.emad.amerian.libffmpeg;
class ArmArchHelper {
static {
System.loadLibrary("ARM_ARCH");
}
native String cpuArchFromJNI();
boolean isARM_v7_CPU(String cpuInfoString) {
return cpuInfoString.contains("v7");
}
boolean isNeonSupported(String cpuInfoString) {
// check cpu arch for loading correct ffmpeg lib
return cpuInfoString.contains("-neon");
}
}
I can to build my APK file successfully but when run Launch my project return error :
02-28 23:29:20.001 15094-15094/com.emad.amerian.sampleffmpeg E/art: No implementation found for java.lang.String com.emad.amerian.libffmpeg.ArmArchHelper.cpuArchFromJNI() (tried Java_com_emad_amerian_libffmpeg_ArmArchHelper_cpuArchFromJNI and Java_com_emad_amerian_libffmpeg_ArmArchHelper_cpuArchFromJNI__)
You need to export the function using the JNIEXPORT and JNICALL macros:
JNIEXPORT jstring JNICALL Java_com_emad_amerian_libffmpeg_ArmArchHelper_cpuArchFromJNI(JNIEnv* env, jobject obj)
This makes the function visible in the symbols table of the library so that the Java VM can link it.
I'm trying to use a python interpreter in my Android app to run SymPy. I already compiled Python for arm using this guide. http://mdqinc.com/blog/2011/09/cross-compiling-python-for-android/
This got me a libpython2.7.so file which I put into jniLibs/armeabi/.
In my app I load it as follows:
public class PythonTest {
static {
System.loadLibrary("python2.7");
}
public static native void Py_Initialize();
public static native void Py_Finalize();
public static native int PyRun_SimpleString(String s);
}
I am trying to use the methods from the headers located in the include directory which can also be found here: https://docs.python.org/2/c-api/
When I run the app on my device I get the following error:
No implementation found for void com.example.dorian.testapplication.PythonTest.Py_Initialize() (tried Java_com_example_dorian_testapplication_PythonTest_Py_1Initialize and Java_com_example_dorian_testapplication_PythonTest_Py_1Initialize__)
So to me this seems like the library loaded but it seems to look for the JNIEXPORT functions. But shouldn't I be able to use this library without writing specific C++ files? And if not, how would I accomplish this. Might there be a tool to generate wrapper files or something similar?
You need a JNI wrapper lib that will serve as a bridge between your Java code and libpython2.7.so. It may be enough for straters to have the three functions wrapped according to the JNI convention, e.g.
JNIEXPORT jint JNICALL com_example_dorian_testapplication_PythonTest_PyRun_1SimpleString
(JNIEnv *env, jclass jc, jstring js)
{
char* cs = env->GetStringUTFChars(js, 0);
std::string s = new std::string(cs);
env->ReleaseStringUTFChars(env, js, cs);
return PyRun_SimpleString(s.c_str());
}
If something is not clear, please read the tutorial at http://joaoventura.net/blog/2014/python-android-2.
Note that you can use any package name for the PythonTest class, not necessarily related to your Android app package name, e.g.
package com.python27;
class Python {
static {
System.loadLibrary("python2.7");
}
public static native void Initialize();
public static native void Finalize();
public static native int Run(String s);
}
will expect the JNI wrappers
JNIEXPORT void JNICALL com_python27_Python_Initialize(JNIEnv *env, jclass jc);
JNIEXPORT void JNICALL com_python27_Python_Finalize(JNIEnv *env, jclass jc);
JNIEXPORT jint JNICALL com_python27_Python_Run(JNIEnv *env, jclass jc, jstring js);
I am new to jni and very confused if I can use jni to achieve what I need done. I want to make a java api what will use jdbc to update database, but this particular api will be called from C++ program.
So I think I probably should write jni code which access the database via jdbc (is that even possible?), create C++ code and generate dll so other C++ programs can just call the dll to update database. Is this all possible? If so, how do I really call jdbc in jni?
If this dll is finally made, can Fortran call it as well?
My other thought is maybe I should make a regular java program to update the database, then use say ikvm to wrap the java class into C++ dll?
The thing is I have to use access database using Java. Our C++ programs will not access database at all, and it would be better if this java api can be accessed via system call.
Or is there any better way to do it?
I hope I explained it well. I am not all familiar with what I am assigned here and cannot find much relevant reference.
Thank you so much!!
UPDATED:
The problem is not all computers have C++ postgresql driver installed but they do have Java postgresql driver installed. We don't want to force everyone to install the C++ db driver and no major changes in those C++ program will be made. So it will make sense to come up something in Java to access database. The java system service (preferred, like dll?) /API basically is called to record start time and end time of a C++ program. C++ program will make a "function" call (with pass-in parameter and returned value) to this system service/Java API to record start/end time.
I'm not going to lecture you on the right or wrong way to approach what you are trying to do. However, if you are trying to invoke Java code (JDBC.jar) then the following is for you.. Otherwise feel free to downvote.
JVM.hpp:
#ifndef JVM_HPP_INCLUDED
#define JVM_HPP_INCLUDED
#include "../java/jni.h"
#include <windows.h>
#include <iostream>
#include <stdexcept>
#include <algorithm>
class Jvm
{
private:
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs jvm_args;
jclass systemClassLoader;
public:
Jvm(std::string ClassPath = ".");
~Jvm();
inline JavaVM* GetJVM() const {return jvm;}
inline JNIEnv* GetENV() const {return env;}
inline jclass GetSystemClassLoader() const {return systemClassLoader;}
void DestroyJVM();
void PrintStackTrace();
jclass DefineClass(const char* FullClassName, const void* ClassBuffer, std::uint32_t BufferLength);
jclass DefineClass(const char* FullClassName, jobject ClassLoader, const void* ClassBuffer, std::uint32_t BufferLength);
void RegisterNativeMethod(const char* MethodName, const char* MethodSignature, void* func_ptr);
void RegisterNativeMethod(jobject ClassLoader, const char* MethodName, const char* MethodSignature, void* func_ptr);
void RegisterNativeMethods(JNINativeMethod* Methods, std::uint32_t MethodCount);
void RegisterNativeMethods(jobject ClassLoader, JNINativeMethod* Methods, std::uint32_t MethodCount);
protected:
void InitClassLoader();
};
#endif // JVM_HPP_INCLUDED
JVM.cpp:
#include "JVM.hpp"
Jvm::~Jvm()
{
env->DeleteGlobalRef(this->systemClassLoader);
jvm->DestroyJavaVM();
}
Jvm::Jvm(std::string ClassPath) : jvm(NULL), env(NULL), jvm_args(), systemClassLoader(NULL)
{
JavaVMOption* options = new JavaVMOption[2];
jvm_args.version = JNI_VERSION_1_6;
JNI_GetDefaultJavaVMInitArgs(&jvm_args);
options[0].optionString = const_cast<char*>("-Djava.compiler=NONE");
options[1].optionString = const_cast<char*>(("-Djava.class.path=" + ClassPath).c_str());
jvm_args.nOptions = 2;
jvm_args.options = options;
jvm_args.ignoreUnrecognized = false;
if (JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &jvm_args))
{
delete[] options;
throw std::runtime_error("Failed To Create JVM Instance.");
}
delete[] options;
}
void Jvm::InitClassLoader()
{
if (!this->systemClassLoader)
{
jclass classloader = env->FindClass("Ljava/lang/ClassLoader;");
if (!classloader)
{
throw std::runtime_error("Failed To find ClassLoader.");
}
jmethodID SystemLoaderMethod = env->GetStaticMethodID(classloader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
jobject loader = env->CallStaticObjectMethod(classloader, SystemLoaderMethod);
this->systemClassLoader = reinterpret_cast<jclass>(env->NewGlobalRef(loader));
}
}
void Jvm::PrintStackTrace()
{
if (env->ExceptionOccurred())
{
env->ExceptionDescribe();
env->ExceptionClear();
}
}
jclass Jvm::DefineClass(const char* FullClassName, const void* ClassBuffer, std::uint32_t BufferLength)
{
this->InitClassLoader();
return this->DefineClass(FullClassName, this->systemClassLoader, ClassBuffer, BufferLength);
}
jclass Jvm::DefineClass(const char* FullClassName, jobject ClassLoader, const void* ClassBuffer, std::uint32_t BufferLength)
{
return ClassLoader ? env->DefineClass(FullClassName, ClassLoader, static_cast<const jbyte*>(ClassBuffer), BufferLength) : NULL;
}
void Jvm::RegisterNativeMethod(const char* MethodName, const char* MethodSignature, void* func_ptr)
{
JNINativeMethod method;
method.name = const_cast<char*>(MethodName);
method.signature = const_cast<char*>(MethodSignature);
method.fnPtr = func_ptr;
this->RegisterNativeMethods(&method, 1);
}
void Jvm::RegisterNativeMethod(jobject ClassLoader, const char* MethodName, const char* MethodSignature, void* func_ptr)
{
JNINativeMethod method;
method.name = const_cast<char*>(MethodName);
method.signature = const_cast<char*>(MethodSignature);
method.fnPtr = func_ptr;
this->RegisterNativeMethods(ClassLoader, &method, 1);
}
void Jvm::RegisterNativeMethods(JNINativeMethod* Methods, std::uint32_t MethodCount)
{
this->InitClassLoader();
this->RegisterNativeMethods(this->systemClassLoader, Methods, MethodCount);
}
void Jvm::RegisterNativeMethods(jobject ClassLoader, JNINativeMethod* Methods, std::uint32_t MethodCount)
{
if (ClassLoader)
{
env->RegisterNatives(static_cast<jclass>(ClassLoader), Methods, MethodCount);
}
}
You can then create an instance of it that loads your jar.
int main()
{
Jvm VM("C:/Users/Brandon/IdeaProjects/Eos/out/production/Eos/Bot.jar");
jclass jMain = VM.GetENV()->FindClass("eos/Main");
if (jMain != nullptr)
{
jmethodID mainMethod = env->GetStaticMethodID(jMain, "main", "([Ljava/lang/String;)V");
jclass StringClass = env->FindClass("java/lang/String");
jobjectArray Args = env->NewObjectArray(0, StringClass, 0);
env->CallStaticVoidMethod(jMain, MainMethod, Args);
}
}
Now this just shows how to run a jar with a Main Method. However, you can access ANY class from the jar and invoke it with however many parameters needed. It doesn't need a main.
Now it IS a lot MORE work to do this, but I won't lecture you. The question was whether or not is was possible and the answer is YES.. it is. So long as you create an instance of the "JVM". After that, it's a matter of accessing the class via the "Package/Class" NOT "Package.Class" as done in Java.. then invoking whatever methods you want.
i am trying to create a Plugin for an Application. The Plugin needs to be written in c++. I want to use the Plugin on Windows and on Mac, so it would be great to write the Plugin in Java.
My Problem, there is an other Plugin using Java. Since they are using the same main Application there is already an running JavaVM.
JavaVM *jvm = NULL;
jsize jvm_count = 0;
jint res=0;
res = JNI_GetCreatedJavaVMs (&jvm, 1, &jvm_count);
My Problem:
How can i change / modify the Classpath of the existing JavaVM? Or how can i create a new / second JavaVM?
I've tryed to load my jar file via JNI:
/* URL CLASS */
jclass URLcls;
URLcls = env->FindClass("java/net/URL");
/* URL CLASS CONSTRUCTOR*/
jmethodID URLclsMid;
URLclsMid = env->GetMethodID(URLcls, "<init>","(Ljava/lang/String;)V");
/* URL OBJECT */
jobject URLobj;
jstr = env->NewStringUTF("file:/path/to/test/file/test.jar");
URLobj = env->NewObject(URLcls, URLclsMid, jstr);
/* URL Array */
jobjectArray URLArray;
URLArray = env->NewObjectArray(1, URLcls, URLobj);
/*Thread Class*/
jclass ThreadCLS;
ThreadCLS = env->FindClass("java/lang/Thread");
/*Static Method currentThread*/
jmethodID ThreadCLS_currentThread;
ThreadCLS_currentThread = env->GetStaticMethodID(ThreadCLS, "currentThread","()Ljava/lang/Thread;");
/*get current Thread Object*/
jobject currentThread;
currentThread = env->CallStaticObjectMethod(ThreadCLS, ThreadCLS_currentThread);
/* getContextClassLoader method */
jmethodID currentThread_getContextClassLoader;
currentThread_getContextClassLoader = env->GetMethodID(ThreadCLS, "getContextClassLoader","()Ljava/lang/ClassLoader;");
/* ClassLoader Class */
jclass ClassLoaderCLS;
ClassLoaderCLS = env->FindClass("java/lang/ClassLoader");
/* get ClassLoader Object */
jobject classLoader = env->CallObjectMethod(currentThread, currentThread_getContextClassLoader);
/* URLClassLoader Class */
jclass URLClassLoaderCLS;
URLClassLoaderCLS = env->FindClass("java/net/URLClassLoader");
/* Static Method newInstance */
jmethodID URLClassLoaderCLS_newInstance;
URLClassLoaderCLS_newInstance = env->GetStaticMethodID(URLClassLoaderCLS, "newInstance","([Ljava/net/URL;Ljava/lang/ClassLoader;)Ljava/net/URLClassLoader;");
/* get new URLClassLoader Instance */
jobject myURLClassLoaderInstance;
myURLClassLoaderInstance = env->CallStaticObjectMethod(URLClassLoaderCLS, URLClassLoaderCLS_newInstance, URLArray, classLoader);
/* get setContextClassLoader Method */
jmethodID currentThread_setContextClassLoader;
currentThread_setContextClassLoader = env->GetMethodID(ThreadCLS, "setContextClassLoader","(Ljava/lang/ClassLoader;)V");
/* trying to set the ClassLoader from the current Thread */
env->CallVoidMethod(currentThread, currentThread_setContextClassLoader, myURLClassLoaderInstance);
/* get loadClass Method */
jmethodID loadClass;
loadClass = env->GetMethodID(URLClassLoaderCLS, "loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
/* get a Class from my test.jar*/
jstring methodString = env->NewStringUTF("de.domain.sayHello");
jclass myClass = (jclass)env->CallObjectMethod(myURLClassLoaderInstance, loadClass, methodString);
/*working till here*/
jmethodID myClassMethod;
myClassMethod = env->GetMethodID(myClass, "doIt","()Ljava/lang/String;");
if (myClassMethod == NULL) {
// Method of Class "de.domain.sayHello" not found
}
What can i do to change the Classpath? or to load my jar File?
Thanks
Lisa
I've solved my Problem. I did not manage to set or influence the Classpath in a way so that i can use:
jmethodID myClassMethod;
myClassMethod = env->GetMethodID(myClass, "doIt","()Ljava/lang/String;");
if (myClassMethod == NULL) {
// Method of Class "de.domain.sayHello" not found
}
Instead of this i've created an Object of my Class using Java Reflection.
Find my Java Class: (after "/working till here/" from the code of my question)
/* Class CLASS */
jclass Classcls = env->FindClass("java/lang/Class");
/* String CLASS */
jclass StringCls = env->FindClass("java/lang/String");
jstring methodString = env->NewStringUTF("de.domain.sayHello");
jclass myJavaClass = (jclass)env->CallObjectMethod(MyURLClassLoaderInstance, URLClassLoaderCLS_loadClass, methodString);
Get an Object from my Java Class:
/* CLASS newInstance Method*/
jmethodID ClassNewInstanceMid = env->GetMethodID(Classcls, "newInstance","()Ljava/lang/Object;");
jobject myObject = env->CallObjectMethod(myJavaClass, ClassNewInstanceMid);
with this i got an object from my Java Class. (The Default Constructor was called)
With the jobject i was able to call a method of my object via java reflection. I passed a Sring with the Application path to my Java Method.
On the Java side i loaded the Application:
import java.io.File;
public class sayHello {
public static native void sayHi(String message, int times);
public sayHello()
{
System.out.println("object created :) ");
}
public void doIt(String test)
{
File myfile = new File(test);
if(myfile.exists())
System.load(myfile.getAbsolutePath());
else
System.out.println("Something went wrong :( ");
sayHi("C++ Funktion Call out of Java",5);
}
}
In my c++ Plugin i implemented this function:
JNIEXPORT void JNICALL Java_de_domain_sayHello_sayHi
(JNIEnv *localEnv, jclass clazz, jstring message, jint times)
{
int myT = times;
for(int i=0;i<myT;i++)
fprintf(stderr,"%s\n", localEnv->GetStringUTFChars(message, NULL));
}
An Interessting thing:
Inside the JNICALL Method (Java_de_domain_sayHello_sayHi) i am able to find my Call via JNI:
jclass myCLS = env->FindClass("de/domain/sayHello");
if(myCLS != NULL){
//class found
}
Inside this function it dosen't matter whether i use the JNIEnv "localEnv" from the function or "env" (global variable)
Thanks
Lisa
We currently have some image processing software written in c++ which is being used by our IOS application. I am trying to integrate this image processing code into the android project that I created using the Android NDK.
I have my android project and the sdk all setup and ready to go. I also have the ndk setup and ready to go.
I was following through on this tutorial (which is awesome), and I got stumped at the part that he defined the code for native.c because it had a function name like this,
void Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_helloLog(JNIEnv * env, jobject this, jstring logThis)
It almost looks to me like I have to go through all of my existing c++ functions and alter the code in order for the NDK to recognize it.
So here are my questions,
Do I have to alter my existing c++ code in order for it to work with the ndk builder? And if so, what are the things I need to change in my code for this work?
Is there a way to have the Android.mk file build an entire directory? I have a lot of files and I did not want to have to list out every single one of them in order to get them built.
1) You should be able to build without alteration, but you will need to write some of those JNI wrapper functions to call it from Java. Hopefully you have a small number of top-level classes and you will only need to wrap those. E.g. Here's what I have for a game I'm (slowly) writing:
// android.cpp
#include "game.h"
#include <jni.h>
namespace {
Game* toGame(jlong gamePtr) {
return reinterpret_cast<Game*>(gamePtr);
}
}
extern "C" {
jlong Java_com_rarepebble_game3_Game_create(JNIEnv* env, jobject jobj) {
Game* g = new Game();
return reinterpret_cast<jlong>(g);
}
void Java_com_rarepebble_game3_Game_destroy(JNIEnv* env, jobject jobj, jlong gamePtr) {
delete toGame(gamePtr);
}
void Java_com_rarepebble_game3_Game_update(JNIEnv* env, jobject jobj, jlong gamePtr, jboolean isTouching, jfloat touchX, jfloat touchY) {
toGame(gamePtr)->update(isTouching, touchX, touchY);
}
void Java_com_rarepebble_game3_Game_render(JNIEnv* env, jobject jobj, jlong gamePtr) {
toGame(gamePtr)->render();
}
// ... and a few others. Only one class, though.
}
On the Java side this lets me declare those functions in my com.rarepebble.game3.Game class and call them at the appropriate times in the app's lifecycle. (Note how the Java package, class and function names correspond to the function names in C++):
//Game.java
package com.rarepebble.game3;
// (imports)
public class Game {
static {
System.loadLibrary("game3lib");
}
// These are the functions you defined in C++
private native long create();
private native void destroy(long gamePtr);
private native void update(long gamePtr, boolean isTouched, float x, float y);
private native void render(long gamePtr);
private long gamePtr = 0;
Game() {
gamePtr = create();
}
#Override
protected void finalize() throws Throwable {
if (gamePtr != 0) {
destroy(gamePtr);
}
super.finalize();
}
// etc...
}
2) You can use
LOCAL_SRC_FILES := $(wildcard *.cpp) $(wildcard subdirectory/*.cpp) #etc...
Edit: As requested, my C++ Game class header, "game.h":
// game.h
#ifndef GAME_INCLUDED
#define GAME_INCLUDED
// Various game and standard library includes.
// *NO* JNI or Android includes.
namespace game {
class Game {
public:
Game();
~Game();
void update(bool isTouching, float touchX, float touchY);
void render();
// other funcs...
private:
// etc...
};
}
#endif //def GAME_INCLUDED
Similarly, "game.cpp" doesn't include any JNI stuff either.