I have a Qt application which has file associations defined in the AndroidManifest.xml so when I select a file in my browser I get a list of associated applications. My application is in the list, but when I select it the file path is not passed in the argv list in my main() method. How is the path passed to the application and how can I have it in Qt/C++?
After some research I came out with the following working solution:
void loadAndroidFile()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
QAndroidJniObject intent = activity.callObjectMethod("getIntent", "()Landroid/content/Intent;");
if (intent.isValid()) {
QAndroidJniObject data = intent.callObjectMethod("getData", "()Landroid/net/Uri;");
if (data.isValid()) {
QAndroidJniObject path = data.callObjectMethod("getPath", "()Ljava/lang/String;");
if (path.isValid())
// Here path.toString() returns the path of the input file
QMetaObject::invokeMethod(rootComponent, "setSourcePath", Q_ARG(QVariant, QVariant("file://" + path.toString())));
}
}
}
#endif
}
Related
I have a simple C# class library, I wrapped it in a C++/CLI class library and loaded it into a simple Java console application. But when I run the Java application, I get the following error:
Process is terminated due to StackOverflowException.
Process finished with exit code -1073741571 (0xC00000FD)
My C# library has a Class1.cs file:
using System;
namespace CSharpClassLibrary1
{
public class Class1
{
public Class1() { }
public void Print()
{
Console.WriteLine("Hello World From C#!");
}
}
}
My C++/CLI wrapper has two files: HelloWorld.h and CppClassLibrary1.cpp.
HelloWorld.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_print
(JNIEnv*, jobject);
#ifdef __cplusplus
}
#endif
#endif
I got it by running the command of the following form in my Java application: javac -h <directory> -d <directory> <source files>.
CppClassLibrary1.cpp:
#include "pch.h"
#include "HelloWorld.h"
using namespace CSharpClassLibrary1;
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv* env, jobject obj)
{
Class1^ cls = gcnew Class1();
cls->Print();
}
I added the CSharpClassLibrary1 as reference to the C++/CLI wrapper.
I added to the "Include directories" setting the following paths:
C:\Users\ns16\AppData\Local\JetBrains\Toolbox\apps\IDEA-C\ch-0\192.6817.14\jbr\include
C:\Users\ns16\AppData\Local\JetBrains\Toolbox\apps\IDEA-C\ch-0\192.6817.14\jbr\include\win32
My Java application has a HelloWorld.java file:
public class HelloWorld {
public native void print();
static {
try {
System.loadLibrary("CppClassLibrary1");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
}
}
public static void main(String[] args) {
HelloWorld hw = new HelloWorld();
hw.print();
}
}
I specified the CppClassLibrary1 library path in the java.library.path variable.
Please help me! What am I doing wrong?
Update. A study of the problem showed the following:
In the Java application error appears in the hw.print(); line. If you comment it out, the application will start successfully.
If in the C++/CLI wrapper in the CppClassLibrary1.cpp file you replace Java_HelloWorld_print function body to the std::cout << "Hello World From C++/CLI!"; line, the Java application will start successfully and print the Hello World From C++/CLI! string.
If you create C# console application, add to it the C# library as references and add into Main method the Class1 cls = new Class1(); cls.print(); code, the application will start successfully and print the Hello World From C#! string.
I copied the C# library assembly in the directory with the java.exe file (in my system it located in C:\Users\ns16\AppData\Local\JetBrains\Toolbox\apps\IDEA-C\ch-0\192.6817.14\jbr\bin) and now the Java application works! I would not want to move the assembly out of the project directory, but this is the only solution I found.
I have a .cpp file to use with java on android:
#include<iostream>
#include<jni.h>
jint Java_com_example_gatsj_tutorjatek_MainActivity_Sum(JNIEnv* env, jobject obj)
{
return 5;
}
I use it here:
package com.example.gatsj.tutorjatek;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity
{
public native int Sum();
static
{
System.loadLibrary("TestCPP");
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
int x = Sum();//IF I REMOVE THIS LINE THE APP DOESN'T CRASH
}
}
I build it in Android Studio using Gradle and this CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
add_library( # Specifies the name of the library.
TestCPP
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/TestCPP.cpp )
When I start the app on my phone it crashes. But if I remove the "int x = Sum();" line, the app can start.
The "loadLibrary" and "native" method part is still in the code but without the "int x = Sum();" line, the app doesn't crash.
How can I use the Sum() method? What causes the problem?
Since C++ is being used instead of C, you should wrap the defination of your native methods inside extern "C" in your cpp file.
extern "C" {
// your native method definations.
}
I found a couple of solutions how to do that in Java, but did not find how can I do it in QML or Qt. I know that first I should set the WAKE_LOCK permission in AndroidManifest.xml. What should I do to make it possible to turn on and off the screen locking from Qt in runtime?
Use window.callMethod<void> instead of window.callObjectMethod
Run on GUI thread with QtAndroid::runOnAndroidThread
Clear exceptions afterwards
To disable always on behaviour, use clearFlags
This is tested Qt 5.7 code:
void keep_screen_on(bool on) {
QtAndroid::runOnAndroidThread([on]{
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
QAndroidJniObject window =
activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_KEEP_SCREEN_ON = 128;
if (on) {
window.callMethod<void>("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
} else {
window.callMethod<void>("clearFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
}
}
}
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionClear();
}
});
}
You can use the Qt Android Extras module and use JNI to call the relevant Java function from C++. Something like :
void keepScreenOn()
{
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_KEEP_SCREEN_ON = 128;
window.callObjectMethod("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
}
}
}
You can achieve this by editing the java file used by qt itself. In installation path under src in android path you will find QtActivity.java file. In the onCreate function add the below line
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
WAKE_LOCK permission in AndroidManifest.xml also should be added.
Build the project, it will work fine.
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.