What is recommended template set or library to acquire/release JNI resources from C++ code?
"Bad" example:
//C++ code
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
const char *nativeString = env->GetStringUTFChars(javaString, 0);
//Do something with the nativeString
//DON'T FORGET THIS LINE!!!
env->ReleaseStringUTFChars(javaString, nativeString);
}
Obviously, everybody uses a template set, not the code above.
For jstring it calls GetStringUTFChars to acquire resource and ReleaseStringUTFChars to release, when object goes "out of scope".
Gotta be something similar to auto_ptr template, but tailored to JNI to call GetStringUTFChars/ReleaseStringUTFChars for jstring, for example.
You can use std::unique_ptr in C++11, which allows you to set the deleter yourself:
#include <memory>
#include <functional>
typedef std::unique_ptr<char const[], std::function<void(char const*)>> jni_string_ptr;
// the "std::function" is needed to store any callable†
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
jni_string_ptr nativeString(env->GetStringUTFChars(javaString, 0),
[=](char const* p) mutable{ env->ReleaseStringUTFChars(javaString, p); });
// lambda as the deleter
// mutable needed if "ReleaseStringUTFChars" is a non-const method
//Do something with the nativeString
// nativeString automatically calls the passed deleter
}
If you're stuck in C++03 and don't have access to std::unique_ptr, boost::shared_array provides a.. viable alternative.
#include <boost/shared_ptr.hpp>
typedef boost::shared_array<char const*> jni_string_ptr;
struct jni_string_deleter{
jni_string_deleter(JNIEnv* e, jstring js)
: _env(e), _java_string(js) {}
void operator()(char const* p) const{
_env->ReleaseStringUTFChars(_java_string, p);
}
private:
JNIEnv* _env;
jstring _java_string;
};
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
jni_string_ptr nativeString(env->GetStringUTFChars(javaString, 0),
jni_string_deleter(env, javaString));
//Do something with the nativeString
// nativeString automatically calls the passed deleter
}
† You can also use a specific delter type here, as with the shared_array example, to avoid the type-erasure overhead of std::function:
struct jni_string_deleter{
jni_string_deleter(JNIEnv* e, jstring js)
: _env(e), _java_string(js) {}
void operator()(char const* p) const{
_env->ReleaseStringUTFChars(_java_string, p);
}
private:
JNIEnv* _env;
jstring _java_string;
};
typedef std::unique_ptr<char const[], jni_string_deleter> jni_string_ptr;
// create
jni_string_ptr(env->get_the_chars(), jni_string_deleter(env, javaString));
The keyword here is RAII:
class jni_string {
public:
jni_string(JNIEnv *env, jstring javaString)
: env_(env), javaString_(javaString)
{ nativeString_ = env_->GetStringUTFChars(javaString_, 0); }
~jni_string() { env_->ReleaseStringUTFChars(javaString_, nativeString_); }
operator const char *() const { return nativeString_; }
private:
jni_string(const jni_string &x);
jni_string &operator=(const jni_string &x);
JNIEnv *env_;
jstring javaString_;
const char *nativeString_;
};
Usage would be:
//C++ code
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
jni_string nativeString(env, javaString);
//Do something with the nativeString
}
Related
I'm writing a C backend for a C++ library and I want the C code to be able to iterate over the individual items of a forward iterator. In C++ the code which iterates over the items looks like this:
auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
for (auto& cell : rng) {
// do something with "cell"
}
Now I want to export this code so that it is accessible from C. I'd like to be able to iterate over the items from C using a set of functions like this:
void *startiteration(void *cpp_obj);
void *getnextitem(void *cpp_obj, void *iter);
void finishiteration(void *cpp_obj, void *iter);
I'd imagine startiteration() to return an iterator pointer to the C code which would then be passed along with the C++ object pointer for all successive calls to getnextitem() and finishiteration().
But the problem here is that I can easily pass pointers of objects created in C++ using new between C and C++ code but I don't see how I could do the same with iterators since the iterators are returned by a class method and I don't think there's any way to turn the iterator into a pointer that I could pass to C code which would then pass it back to C++ during the iteration. Also, I don't know how I would "free" such an iterator.
Does anybody have some tips how I can iterate over a C++ forward iterator from C code? How should this be implemented?
EDIT
Implementation based on Silvio's suggestions:
struct myiter
{
XLCellIterator begin;
XLCellIterator end;
};
void *xlsx_startiteration(void *handle, int idx)
{
XLDocument *doc = (XLDocument *) handle;
XLWorkbook wb = doc->workbook();
auto wks = wb.worksheet(wb.sheetNames()[idx-1]);
XLCellRange cr = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
myiter *it = new myiter();
it->begin = std::begin(static_cast<XLCellRange*>(cr));
it->end = std::end(static_cast<XLCellRange*>(cr));
return it;
}
auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
for (auto& cell : rng) {
// do something with "cell"
}
I like doing 1:1 relationship between C++ and C. The following code outputs 3 lines var=1 var=2 var=3.
By wrapping the objects inside structures, the C side only forward declarations of structures and pointers. The C++ side sees all the rest. Additionally, C side will get a warning when passing invalid pointer to the wrong function.
#include <cstdio>
#include <vector>
typedef int do_not_know_what_is_the_type;
typedef std::vector<do_not_know_what_is_the_type> XLCellRange;
typedef XLCellRange::iterator XLCellIterator;
// header file
// C side
#ifdef __cplusplus
extern "C" {
#endif
struct wks_range_it_s;
void wks_range_it_inc(struct wks_range_it_s *);
bool wks_range_it_ne(struct wks_range_it_s *, struct wks_range_it_s *);
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *);
void wks_range_it_destruct(struct wks_range_it_s *);
struct wks_range_s;
struct wks_range_s *wks_range_construct();
void wks_range_destruct(struct wks_range_s *);
struct wks_range_it_s *wks_range_begin(struct wks_range_s *);
struct wks_range_it_s *wks_range_end(struct wks_range_s *);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
// C++ side
struct wks_range_s {
XLCellRange rng;
};
struct wks_range_it_s {
XLCellIterator it;
};
extern "C"
void wks_range_it_inc(struct wks_range_it_s *t) {
++t->it;
}
extern "C"
bool wks_range_it_ne(struct wks_range_it_s *a, struct wks_range_it_s *b) {
return a->it != b->it;
}
extern "C"
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *t) {
return *t->it;
}
extern "C"
void wks_range_it_destruct(struct wks_range_it_s *t) {
delete t;
}
extern "C"
struct wks_range_s *wks_range_construct() {
// return new struct wks_range_s(wks.range(XLCellReference("A1"), XLCellReference("Q1")));
return new wks_range_s{{1,2,3}};
}
extern "C"
void wks_range_destruct(struct wks_range_s *t) {
delete t;
}
extern "C"
struct wks_range_it_s *wks_range_begin(struct wks_range_s *t) {
return new wks_range_it_s{t->rng.begin()};
}
extern "C"
struct wks_range_it_s *wks_range_end(struct wks_range_s *t) {
return new wks_range_it_s{t->rng.end()};
}
#endif
int main() {
// C code example
struct wks_range_s *rng = wks_range_construct();
// Almost 1:1 relationship with C++ range loop.
for(struct wks_range_it_s *begin = wks_range_begin(rng),
*end = wks_range_end(rng);
wks_range_it_ne(begin, end) ? 1 : (
// Yes, I'm sneaky.
wks_range_it_destruct(begin),
wks_range_it_destruct(end),
0);
wks_range_it_inc(begin)
) {
do_not_know_what_is_the_type var = wks_range_it_deref(begin);
// do something with var here?
printf("var=%d\n", var);
}
wks_range_destruct(rng);
}
Forward iterators in C++ are regular, which means they can be copied. In fact, in our case, we would only need to move them. Specifically, assuming the type of the iterator in question is I, then startiteration could do something like this.
struct Range {
I begin;
I end;
};
void* startiteration(void* cpp_obj) {
Range* my_range = new Range();
my_range->begin = std::begin(*static_cast<MyCppType*>(cpp_object));
my_range->end = std::end(*static_cast<MyCppType*>(cpp_object));
return my_range;
}
Just because the forward iterator is originally returned by value doesn't prohibit us from putting it into a pointer, either directly or indirectly. In our case, we grab both the beginning and the end and store them in a (heap-allocated) structure. Then finishiteration, of course, would be responsible for deleteing the constructed range object.
im trying to get the value of an int which is non static
java code
public int valuex = 500;
public native int intfromjni();
native code
JNIEXPORT jint JNICALL Java_com_samplejni_cls_intfromjni(JNIEnv* env, jobject thiz)
{
jclass x = env->GetObjectClass(thiz);
jfieldID f = env->GetFieldID(x, "valuex", "I");
int i = env->GetIntField(x, f);
return i;
}
but it throws crash with log
JNI DETECTED ERROR IN APPLICATION: jfieldID int com.samplejni.cls.valuex not valid for an object of class java.lang.Class<com.samplejni.cls>
here
int i = env->GetIntField(x, f);
you trying to use jclass to get a particular object's value. It would work for static fields.
For non-static firelds you should use jobject:
int i = env->GetIntField(thiz, f);
I have following code which accepts int value at JAVA level
dataObj.property(20);
This is the definition of property function
void property(Object value){
// JNI called
}
JNI function definition
Java_com_XXX_XXXX_JNIProperty(JNIEnv *a_pEnv, jclass, **jobject a_oPropertyValue**) {
// how to convert jobject to Integer object and then get the intValue
jclass cls = a_pEnv->FindClass("java/lang/Integer");
jmethodID getVal = a_pEnv->GetMethodID(cls, "intValue", "()I");
int i = a_pEnv->CallIntMethod(value, getVal);
}
How to convert jobject which was of type Object in Java to Integer object in JNI and parse int value ?
For a class in Java
public class PassObject {
public static native void displayInt(Object obj);
public static void property(int value) {
PassObject.displayInt(value);
}
public static void main(String[] args) {
PassObject.property(44);
// make sure to be careful in your case,
// as you make assumption that "Object" will be of type "Int"
// It's really easy to be very nasty.
PassObject.displayInt("44");
}
}
you can use following JNI code
JNIEXPORT void JNICALL Java_recipeNo055_PassObject_displayInt
(JNIEnv * env, jclass obj, jobject objarg) {
jclass cls = (*env)->GetObjectClass (env, objarg);
jmethodID integerToInt = (*env)->GetMethodID(env, cls, "intValue", "()I");
jint result = (*env)->CallIntMethod(env, objarg, integerToInt);
printf("Passed value: %d\n", result);
}
if you want to be extra careful, you can always do
jclass cls = (*env)->GetObjectClass (env, objarg);
if( (*env)->IsInstanceOf(env, objarg, (*env)->FindClass(env, "java/lang/Integer")) ) {
jmethodID intValue = (*env)->GetMethodID(env, cls, "intValue", "()I");
jint result = (*env)->CallIntMethod(env, objarg, intValue);
printf("%-22s: %d\n", "Passed value (int)", result);
} else {
// error
}
JNIEXPORT jstring JNICALL Java_com_xxx_xpdf_PdfToText_getTextOnly(JNIEnv *env, jclass obj,
jstring pdf_path) {
const char *pdf = env->GetStringUTFChars(pdf_path, 0);
std::string content;
unsigned int i = getTextFromPDF(pdf, &content);
env->ReleaseStringUTFChars(pdf_path, pdf);
const char *result = content.c_str();
jstring str = env->NewStringUTF(result);
return str;
}
Do I need to relase str and content here ? And why ?
No. str refers to a Java object which continues to exist beyond this JNI method, as it is the return value. content is a C++ local object which is auto-destroyed when its declaring scope exits.
I need to add a bool to an object map, my bool is defined as a primitive, and I need to convert it to an object.
How do I do that ?
You can convert your bool to a jboolean then box the jboolean into a Boolean jobject with JNI calls like this:
jboolean value = true;
jclass booleanClass = env->FindClass("java/lang/Boolean");
jmethodID methodID = env->GetMethodID(booleanClass, "<init>", "(Z)V", false);
jobject booleanObject = env->NewObject(booleanClass, methodID, value);