Invoking native methods from JAVA when creating the JVM from C++? - java

I have a situation of a C++ executable which creates a JVM using JNI_CreateJavaVM, and that java method wants to invoke a native method back:
//In Java:
private native void myNativeMethod();
//In C++:
JNIEXPORT void JNICALL
Java_SomeClass_myNativeMethod(JNIEnv *env, jobject instance) {
..
}
But it doesn't work (linking error).
BUT, if I move the method into a c++ library and load the library from Java using System.loadLibrary, it does work.
Any way of enabling this behaviour with only a c++ executable without going through the trouble of having 3 pieces (main c++ executable => starting a JVM and running a jar => loading a c++ library) but instead keeping it at two?
Thanks!

When loading a native library in Java using System.loadLibrary, it is stored internally in a list that is attached to the current ClassLoader, and then this list of libraries is searched when looking up the native method. So, it will indeed not see the native method you have defined in the same executable.
You should be able to make this work using the RegisterNatives JNI API, which allows explicitly registering native functions for a particular class as pointers.
JNIEnv env = ...
jclass cSomeClass = env->FindClass("SomeClass");
JNINativeMethod natives[] = {
{(char*) "myNativeMethod", (char*) "()V", (void*) &Java_SomeClass_myNativeMethod},
};
env->RegisterNatives(cSomeClass, natives, 1);

Related

Can I invoke a Java program with database call from a cpp program

I have written a java program that does some logic check which involves a database call.
I was able to call a simple cpp program from java using JNI stackoverflow question.
How can I do the opposite, i.e Is there a way to call java (with database) from a cpp program?
I have PostgreSQL database. Any direction would be a great starting point for me.
Thank you for your time. :)
Edit
Seems like a similar question has a very nice answer here link
JNI is working from the other way as well cpp -> java http://java.sun.com/javase/6/docs/technotes/guides/jni/index.html
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);

Jni and shared libraries

I have made a program in Java that calls to some functions in native language C. Made a shared library of that C function file and made a shared library, and all worked perfectly.
My problem is when I try to call other functions for example in PBC (Pairing Based Cryptography) library. The C files that are in the shared library include the required .h files for knowing the functions in PBC but I can't use them, I don't know why. What should I do? How can I call functions that are in another libraries?
Java code for loading the libraries.
static {
System.loadLibrary("myLibrary");
System.loadLibrary("pbc");
}
Error when executing my own Java program:
undefined symbol: pairing_init_set_buf
Make sure to link your JNI code with shared library you want to use.
You can take a look at sample code here:
https://github.com/mkowsiak/jnicookbook/tree/master/recipes/recipeNo023
In this sample you have JNI function:
JNIEXPORT void JNICALL Java_recipeNo023_HelloWorld_displayMessage
(JNIEnv *env, jclass obj) {
printf("Hello world!\n");
/* We are calling function from another source */
anotherFunction();
}
that calls function from some external shared library
void anotherFunction() {
// we are printing message from another C file
printf("Hello from another function!\n");
}
You have to make sure that your JNI library is linked with the library you want to use:
cc -g -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/$(ARCH) c/recipeNo023_HelloWorld.c -L./lib -lAnotherFunction -o lib/libHelloWorld.$(EXT)
In this sample
-L./lib -lAnotherFunction
tells compiler to use this "other" library that contains symbols not available inside library that contains JNI code.

Create iOS static library from robovm project (BAD_ACCESS in JNI)

I have a large amount of Java code (only calculation functions, no UI) that I want to reuse as a static library in iOS. My approach was to use robovm and follow the unofficial way to create a static library described in the two articles in the robovm forum: 1 Basic way and 2 Refined version
Trying to follow the steps exactly as described I got stuck unfortunately after creating the shared library with the script, linking the library (.a) in Xcode and building the project successfully.
During runtime I see that my C++ bridge code is called but the JNI calls back to the library fail with a BAD_ACCESS. For example the following line crashes:
jclass myJavaClass = jniEnv->FindClass("com/test/robovm/bridge/MyJavaRoboCode");
in this method:
void callSomethingInJava(const char* arg) {
// To call into java from your native app, use JNI
Env* rvmEnv = rvmGetEnv();
JNIEnv* jniEnv = &(rvmEnv->jni);
jclass myJavaClass = jniEnv->FindClass("com/test/robovm/bridge/MyJavaRoboCode");
jmethodID myJavaMethod = jniEnv->GetStaticMethodID(myJavaClass, "callJava", "(Ljava/lang/String;)V");
jstring argAsJavaString = jniEnv->NewStringUTF(arg);
jniEnv->CallStaticVoidMethod(myJavaClass, myJavaMethod, argAsJavaString);
}
The same is true if I try to use the rvmXX methods directly instead of JNI and try to access something in my "Java" classes. It looks like the rvmEnv is not fully initialized. (I double checked for package name errors or typos).
It would be great if someone already succeeded with the creation of a shared static library from a robovm project and could share the experience here or point me in the right direction to resolve the issue.
As you mentioned, you probably haven't finished initialising robovm.
You'll need to create a method, say initRoboVM(), to somewhat mirror bc.c's main method. This will be called by your code when you want to initialise robovm. You'll need to pass the app path in, which you can hardcode when you're testing.
initRoboVM() will need some modifications, namely it should not call your Java app's main method, well, at least, that's what well behaving libraries should not do IMO. It should also not call rvmShutdown.

android ndk wrapped c++ file get android system file path

I have some code written in c++ by android JNI.
in one export function
"extern "C" JNIEXPORT int JNICALL Java_com_android_mymapsforge_create(JNIEnv *env, jobject obj)
{
...
ifstream infile("C:\\Users\\li\\Documents\\Visual Studio 2010\\Projects\\importANN\\Debug\\nodes.csv");
string line;
int index = 0;
while( getline( infile, line ))
{
....
}
}
when i using those original c++ code, i test under vs2010. Either i assign the infile() parameters with a fixed file path, or using some c++ get_file_path function, it's a thing related windows system, there is no problem. both work.
But now I want to using these code under JNI using by android. So the file path system change to android and some function need be adjusted.
My problem arise: because the code is using at last by my android java code under a android app, so the "C:\Users\li\Documents\Visual Studio 2010\Projects\importANN\Debug\nodes.csv", no longer take effect, it may be change to "/mnt/sdcard/somefolder/nodes.csv"
but i have experience when i in android app give a fixed file path it tell my some error, so under android jave envi, i using below code to get file path, similar code here:
String mpath ;
mpath = Environment.getExternalStorageDirectory().getPath();
mapView.setMapFile(new File(mpath+"/"+"berlin.map"));
(omit the filename , i just want to tell the method i use)
But these android code must can not use in my c++ wrapping function Java_com_android_mymapsforge_create,
So my question is :
how to cope with the file(android system) path things under the c++ export function? For example, i put the "nodes.csv" under my sdcard root folder, and want to do some processing under the JNI exported c++ function?
The easiest way is to pass the path as an argument of the function.
extern "C" JNIEXPORT int
JNICALL Java_com_android_mymapsforge_create(JNIEnv *env, jobject, jstring path)
Then you can turn jstring path to char* using JNI converting jstring to char *.
The second way is to call Android Environment.getExternalStorageDirectory().getPath() from JNI function like described here - Call a static java method of another package from native code.

How to use the same C++ code for Android and iOS?

Android with NDK has support to C/C++ code and iOS with Objective-C++ has support too, so how can I write applications with native C/C++ code shared between Android and iOS?
Update.
This answer is quite popular even four years after I write it, in this four years a lot of things has changed, so I decided to update my answer to fit better our current reality. The answer idea does not change; the implementation has changed a little. My English also has changed, it has improved a lot, so the answer is more understandable to everyone now.
Please take a look at the repo so you can download and run the code I'll show below.
The Answer
Before I show the code, please take a lot on the following diagram.
Each OS has its UI and peculiarities, so we intend to write specific code to each platform in this regard. In other hands, all logic code, business rules, and things that can be shared we intend to write using C++, so we can compile the same code to each platform.
In the diagram, you can see the C++ layer at the lowest level. All shared code is in this segment. The highest level is regular Obj-C / Java / Kotlin code, no news here, the hard part is the middle layer.
The middle layer to iOS side is simple; you only need to configure your project to build using a variant of Obj-c know as Objective-C++ and it is all, you have access to C++ code.
The thing became harder on the Android side, both languages, Java and Kotlin, on Android, run under a Java Virtual Machine. So the only way to access C++ code is using JNI, please take time to read the basics of JNI. Fortunately, today's Android Studio IDE has vast improvements on JNI side, and a lot of problems are shown to you while you edit your code.
The code by steps
Our sample is a simple app that you send a text to CPP, and it converts that text to something else and returns it. The idea is, iOS will send "Obj-C" and Android will send "Java" from their respective languages, and the CPP code will create a text as a follow "cpp says hello to << text received >>".
Shared CPP code
First of all, we are going to create the shared CPP code, doing it we have a simple header file with the method declaration that receives the desired text:
#include <iostream>
const char *concatenateMyStringWithCppString(const char *myString);
And the CPP implementation:
#include <string.h>
#include "Core.h"
const char *CPP_BASE_STRING = "cpp says hello to %s";
const char *concatenateMyStringWithCppString(const char *myString) {
char *concatenatedString = new char[strlen(CPP_BASE_STRING) + strlen(myString)];
sprintf(concatenatedString, CPP_BASE_STRING, myString);
return concatenatedString;
}
Unix
An interesting bonus is, we can also use the same code for Linux and Mac as well as other Unix systems. This possibility is especially useful because we can test our shared code faster, so we are going to create a Main.cpp as follow to execute it from our machine and see if the shared code is working.
#include <iostream>
#include <string>
#include "../CPP/Core.h"
int main() {
std::string textFromCppCore = concatenateMyStringWithCppString("Unix");
std::cout << textFromCppCore << '\n';
return 0;
}
To build the code, you need to execute:
$ g++ Main.cpp Core.cpp -o main
$ ./main
cpp says hello to Unix
iOS
It is time to implement on the mobile side. As far as iOS has a simple integration we are starting with it. Our iOS app is a typical Obj-c app with only one difference; the files are .mm and not .m. i.e. It is an Obj-C++ app, not an Obj-C app.
To a better organization, we create the CoreWrapper.mm as follow:
#import "CoreWrapper.h"
#implementation CoreWrapper
+ (NSString*) concatenateMyStringWithCppString:(NSString*)myString {
const char *utfString = [myString UTF8String];
const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
NSString *objcString = [NSString stringWithUTF8String:textFromCppCore];
return objcString;
}
#end
This class has the responsibility to convert CPP types and calls to Obj-C types and calls. It is not mandatory once you can call CPP code on any file you want on Obj-C, but it helps to keep the organisation, and outside your wrapper files you maintain a complete Obj-C styled code, only the wrappers file become CPP styled.
Once your wrapper is connected to the CPP code, you can use it as a standard Obj-C code, e.g. ViewController"
#import "ViewController.h"
#import "CoreWrapper.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *label;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString* textFromCppCore = [CoreWrapper concatenateMyStringWithCppString:#"Obj-C++"];
[_label setText:textFromCppCore];
}
#end
Take a look of how the app looks:
Android
Now it is time for Android integration. Android uses Gradle as the build system, and to C/C++ code it uses CMake. So the first thing we need to do is to configure the CMake on gradle file:
android {
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
}
}
...
}
And the second step is to add the CMakeLists.txt file:
cmake_minimum_required(VERSION 3.4.1)
include_directories (
../../CPP/
)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
../../CPP/Core.h
../../CPP/Core.cpp
)
find_library(
log-lib
log
)
target_link_libraries(
native-lib
${log-lib}
)
The CMake file is where you need to add the CPP files and header folders you will use on the project, on our example, we are adding the CPP folder and the Core.h/.cpp files. To know more about C/C++ configuration please read it.
Now the core code is part of our app it is time to create the bridge, to make the things more simple and organized we create a specific class named CoreWrapper to be our wrapper between JVM and CPP:
public class CoreWrapper {
public native String concatenateMyStringWithCppString(String myString);
static {
System.loadLibrary("native-lib");
}
}
Note this class has a native method and loads a native library named native-lib. This library is the one we create, in the end, the CPP code will become a shared object .so File embed in our APK, and the loadLibrary will load it. Finally, when you call the native method, the JVM will delegate the call to the loaded library.
Now the most strange part of Android integration is the JNI; We need a cpp file as follow, in our case "native-lib.cpp":
extern "C" {
JNIEXPORT jstring JNICALL Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString(JNIEnv *env, jobject /* this */, jstring myString) {
const char *utfString = env->GetStringUTFChars(myString, 0);
const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
jstring javaString = env->NewStringUTF(textFromCppCore);
return javaString;
}
}
The first thing you will notice is the extern "C" this part is necessary to JNI work correctly with our CPP code and method linkages. You will also see some symbols JNI uses to works with JVM as JNIEXPORT and JNICALL. To you understand the meaning of those things, it is necessary to take a time and read it, for this tutorial purposes just consider these things as boilerplate.
One significant thing and usually the root of a lot of problems is the name of the method; it needs to follow the pattern "Java_package_class_method". Currently, Android studio has excellent support for it so it can generate this boilerplate automatically and show to you when it is correct or not named. On our example our method is named "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" it is because "ademar.androidioscppexample" is our package, so we replace the "." by "_", CoreWrapper is the class where we are linking the native method and "concatenateMyStringWithCppString" is the method name itself.
As we have the method correctly declared it is time to analyze the arguments, the first parameter is a pointer of JNIEnv it is the way we have access to JNI stuff, it is crucial to we make our conversions as you will see soon. The second is a jobject it is the instance of the object you had used to call this method. You can think it as the java "this", on our example we do not need to use it, but we still need to declare it. After this jobject, we are going to receive the arguments of the method. Because our method has only one argument - a String "myString", we have only a "jstring" with the same name. Also notice that our return type is also a jstring. It is because our Java method returns a String, for more information about Java/JNI types please read it.
The final step is to convert the JNI types to the types we use on CPP side. On our example, we are transforming the jstring to a const char * sending it converted to the CPP, getting the result and converting back to jstring. As all other steps on JNI, it is not hard; it is only boilerplated, all the work is done by the JNIEnv* argument we receive when we call the GetStringUTFChars and NewStringUTF. After it our code is ready to run on Android devices, lets take a look.
Approach described in the excellent answer above can be completely automated by Scapix Language Bridge which generates wrapper code on the fly directly from C++ headers. Here is an example:
Define your class in C++:
#include <scapix/bridge/object.h>
class contact : public scapix::bridge::object<contact>
{
public:
std::string name();
void send_message(const std::string& msg, std::shared_ptr<contact> from);
void add_tags(const std::vector<std::string>& tags);
void add_friends(std::vector<std::shared_ptr<contact>> friends);
};
And call it from Swift:
class ViewController: UIViewController {
func send(friend: Contact) {
let c = Contact()
contact.sendMessage("Hello", friend)
contact.addTags(["a","b","c"])
contact.addFriends([friend])
}
}
And from Java:
class View {
private contact = new Contact;
public void send(Contact friend) {
contact.sendMessage("Hello", friend);
contact.addTags({"a","b","c"});
contact.addFriends({friend});
}
}

Categories

Resources