Say I have a Java class,
public static String helloWorld() {
return "hello world!";
}
How, in Qt, do I get what this function returned? The example for notifications has the following:
QAndroidJniObject javaNotification = QAndroidJniObject::fromString(m_notification);
QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/notification/NotificationClient",
"notify",
"(Ljava/lang/String;)V",
javaNotification.object<jstring>());
Should by method descriptor be ()Ljava/lang/String;? What should be in the chevrons after callStaticMethod?
Edit. Fixed and I don't know how. I didn't have the chevrons, and the descriptor was correct.
QAndroidJniObject string = QAndroidJniObject::callStaticObjectMethod<jstring>("com/your/project/YourClass", "helloWorld");
Is equivalent to:
QAndroidJniObject string = QAndroidJniObject::callStaticObjectMethod("com/your/project/YourClass", "helloWorld", "()Ljava/lang/String;");
For trivial signatures, that is, signatures that takes no arguments and returns one of the know jni types, you can write the short version by supplying the template type.
Related
I've been turning in circle for hours on this one... Im porting an old android app to 11 and try to open a URL from C. Previously I was opening URL using:
if( fork() == 0 )
{ execvp( "/system/bin/am", cmd ); }
But now it seems we cannot run commands using execvp directly anymore, so the idea is to call from C a Java function to open a browser and launch the URL. So now I got the following:
[Utils.java]
package com.android.MyApp;
public class Utils {
public static void open_url(String url) {
// Test it out...
System.out.println(url);
}
}
[main.c]
void test( void ) {
jclass cls = (*g_env)->FindClass( g_env, "com/android/MyApp/Utils" );
jmethodID open_url = (*g_env)->GetStaticMethodID( g_env, cls, "open_url", "(Ljava/lang/String;)V");
(*g_env)->CallStaticVoidMethod( g_env, cls, open_url, (jstring)"https://stackoverflow.com/" );
}
Everything compile but Android Studio report on the CallStaticVoidMethod line: JVM object referenced by 'cls' is of type 'Class<MyApp.Utils>' and it does not have access to static method 'open_url(String)' declared in class 'Utils'.
And of course calling CallStaticVoidMethod even if g_env and cls got an address crash... How can I fix this problem?
In a C++ file, I want to convert a const char* to KString, so that I can then pass the KString to a Kotlin file using Kotlin/Native.
I believe the answer lies in the function
OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength)
that I found in KString.cpp. But even though I discovered the used define statements in Memory.h, I have not yet managed to properly call the function utf8ToUtf16 from my own C++ file to get a KString. Any help is appreciated.
It depends on how you want to interact with Kotlin code. If you produce dynamic library with -produce dynamic, then string are converted automatically, see for example https://github.com/JetBrains/kotlin-native/blob/adf8614889e8cf5038a79960aa9651ca7d45e409/samples/python_extension/src/main/c/kotlin_bridge.c#L72.
So no additional magic is required at all. Same with Objective-C strings and -produce framework. And for other cases, there shall be no need to pass strings C -> Kotlin (callbacks produced with staticCFunction also do autoconversion).
I ended up taking the pieces to write my own function:
KString getKString(const char* rawString) {
size_t rawStringLength = strlen(rawString);
ObjHeader** OBJ_RESULT;
uint32_t charCount = utf8::unchecked::distance(rawString, rawString + rawStringLength);
ArrayHeader* result = AllocArrayInstance(theStringTypeInfo, charCount, OBJ_RESULT)->array();
KChar* rawResult = CharArrayAddressOfElementAt(result, 0);
auto convertResult =
utf8::unchecked::utf8to16(rawString, rawString + rawStringLength, rawResult);
ObjHeader* obj = result->obj();
UpdateReturnRef(OBJ_RESULT, obj);
return (const ArrayHeader*)obj;
}
In my test code (C++), I use it like this:
...
RuntimeState* state = InitRuntime();
KString inMessage;
{
ObjHolder args;
inMessage = getKString("Hello from C++");
}
...
DeinitRuntime(state);
and include Memory.h, Natives.h, Runtime.h, KString.h, utf8.h, stdlib.h, and string. You may be able to get rid of some of these.
As a side remark, you may realize how AllocArrayInstance is used in the function. It would be nice, if one simply could do the same thing for getting a KString, something like:
ObjHeader** OBJ_RESULT;
KString kstr = utf8ToUtf16(rawString, rawStringLength, OBJ_RESULT);
This did not work from my function, since utf8ToUtf16 was not found. I believe the reason is, that (at the time of writing) the respective function in KString.cpp is inside a namespace {...} block, such that it cannot be used from another file. That's why I ended up mimicking the function as shown above.
I have a relative large size std::string. And I want to pass it to Java without copying. And then pass back to another JNI lib. What is the best approach?
jlong some_jni_call() {
string str = createLargeString(); // say this is from 3rd lib only returns string
string* strInHeap = new string(str); // this should just increase the reference count?
jlong handle = (long)strInHeap;
return handle;
}
Then I can pass back to JNI:
void use_string(jlong handle) {
string* str = (string*)handle;
// use the str...
delete str; // doesn't look so nice, since people can forget to delete
}
Is this a good approach?
This is surely a feasible approach; you can even use similar trick to pass function pointers back and forth.
I have seen many examples in Delphi and not one a C ++ builder. I tried to recreate the code in C ++, but it flies only exception. How to use the Intent to ะก++?
void __fastcall TForm1::Button1Click(TObject *Sender)
{
callEmail("russia#gmail.com", "Application");
}
//---------------------------------------------------------------------------
void TForm1::callEmail(const String address, const String Subject){
JIntent* intent;
TJIntent* intentTwo;
intent = intentTwo->Create();
intent->setAction(intentTwo->JavaClass->ACTION_SEND);
intent->setFlags(intentTwo->JavaClass->FLAG_ACTIVITY_NEW_TASK);
intent->putExtra(intentTwo->JavaClass->EXTRA_EMAIL, StringToJString(address));
intent->putExtra(intentTwo->JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString('vnd.android.cursor.dir/email'));
SharedActivity()->startActivity(intent);
}
I thnik, maybe I think maybe something needs to change in androidmanifest or user-permission?
Your code is crashing because you are not constructing the Intent object correctly.
Create() is a constructor in Delphi. intent := TJIntent.Create in Delphi would be intent = new TJIntent in C++.
Also, Embarcadero uses interfaces for its iOS/Android bridge frameworks, so you should use the provided DelphiInterface<T> typedefs, such as _di_JIntent instead of JIntent* directly.
Also, JavaClass (and OCClass in iOS) is a static class property. You do not need an object instance to access it, just the class type.
Also, C++ uses single-quotes for character literals and double-quotes for string literals, whereas Delphi uses single-quotes for both. 'vnd.android.cursor.dir/email' in C++ is not a string literal, it is a multi-byte character literal instead, which is not what you want here. Use double-quotes instead.
Also, EXTRA_EMAIL must be expressed as an array of strings.
Try something more like this:
void TForm1::callEmail(const String address, const String Subject)
{
_di_JIntent intent;
intent = new TJIntent; // or: intent = TJIntent::JavaClass->init();
intent->setAction(TJIntent::JavaClass->ACTION_SEND);
// or: intent = TJIntent::JavaClass->init(TJIntent::JavaClass->ACTION_SEND);
intent->setFlags(TJIntent::JavaClass->FLAG_ACTIVITY_NEW_TASK);
TJavaObjectArray__1<_di_JString> *Recipients = new TJavaObjectArray__1<_di_JString>(1);
Recipients->Items[0] = StringToJString(address);
intent->putExtra(TJIntent::JavaClass->EXTRA_EMAIL, Recipients);
intent->putExtra(TJIntent::JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString(L"vnd.android.cursor.dir/email"));
SharedActivity()->startActivity(intent);
}
Now, that said, you really should not be using vnd.android.cursor.dir/email as the intent's MIME type. Use message/rfc822 instead, or even plain/text. But those do not limit the intent to just email clients, other apps might also support those types. To send an email using only a true email client, use ACTION_SENDTO with a mailto: URI instead. For 1 recipient, you can put the address directly in the URI and not use EXTRA_EMAIL at all. For 2+ recipients, use a mailto: URI with no address in it and use EXTRA_EMAIL for the addresses. This is mentioned in the Android documentation:
Common Intents | Email
For example:
void TForm1::callEmail(const String address, const String Subject)
{
_di_JIntent intent;
intent = new TJIntent; // or: intent = TJIntent::JavaClass->init();
intent->setAction(TJIntent::JavaClass->ACTION_SENDTO);
intent->setData(StrToJURI(L"mailto:" + address));
// or: intent = TJIntent::JavaClass->init(TJIntent::JavaClass->ACTION_SENDTO, StrToJURI(L"mailto:" + address));
intent->setFlags(TJIntent::JavaClass->FLAG_ACTIVITY_NEW_TASK);
intent->putExtra(TJIntent::JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString(L"message/rfc822"));
SharedActivity()->startActivity(intent);
}
Considering this Java class with the static method:
public class TestClass{
public string str;
public TestClass() {
str = "Test From Java";
}
public static String staticMethod() {
return "Test From Java";
}
}
I have written these lines of code in c++ file:
QAndroidJniObject str = QAndroidJniObject::callStaticObjectMethod(
"org/.../TestClass"
,"staticMethod"
,"(V)Ljava/lang/String;");
It seems everything is working but I don't know how can I use the str Object. I tried to convert it to a QString object using str.tostring() method but it always returns an empty string.
Why it does not work as expected? I've also tested ()Ljava/lang/String; for method signature but no success!
Thanks in advance.
You should specify the returned JNI type in <...> when calling the method :
QAndroidJniObject str = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/.../TestClass"
,"staticMethod");
QString string = str.toString();
Here there is no need to define the signature since your function has no argument.