Custom transferable-ownership classes

M

mrts

First, a bit of high-level context:

I'm using JNI in a C++ project. Each JNI resource
should be accessed via a reference to the resource
object inside the Java virtual machine (JVM)
environment. That reference needs to be released
with when the resource is no longer needed.

E.g. to create a string, one would call

jstring str_ref = env_->NewStringUTF("foo");

where env_ is the pointer to the JVM environment,
and to release the resources of the string,

_env->DeleteLocalRef(str_ref);

This is a classic case of RAII. A smart "handle"
that would release the reference in it's
destructor would be a perfect, exception-safe
casing for the naked JVM references.

So far, so good.

In the spirit of proper encapsulation, all of the
JNI machinery is designed to be hidden behind a
high-level wrapper class, JNIWrapper. This is a
singleton class that owns all the JVM-related
resources, including the JVM environment pointer.

New resources should be created according to the
source/sink idiom [1], hiding the private
environment pointer:

JVMRef<jstring> JNIWrapper::newString(const char* str)
{
// env_ is a private member of the JNIWrapper
// instance
JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
return ret;
}

where JVMRef should be an auto_ptr like
transferable-ownership class that needs a custom
deleter that has access to the environment. (Note
that I cannot use C++0X's std::unique_ptr.)

Here's a simple, auto_ptr-like first stab at
JVMRef:

template <typename T>
class JVMRef {
public:
JVMRef(JNIEnv *env, T ref) :
env_(env), ref_(ref) { }

JVMRef(JVMRef& other) :
env_(other.get_env()), ref_(other.release()) { }

JVMRef& operator=(JVMRef &other)
{ reset(other.release()); return *this; }

~JVMRef() { reset(NULL); }

T get()
{ return ref_; }

JNIEnv *get_env()
{ return env_; }

T release()
{ T ret = ref_; ref_ = NULL; return ret; }

void reset(T other = NULL) {
if (ref_ != other) {
env_->DeleteLocalRef(ref_);
ref_ = other;
}
}

private:
JNIEnv *env_;
T ref_;
};

The "source" function is given in
JNIWrapper::newString() above. Attempts to
use it in a "sink" function to construct a new
JVMRef to a Java string as follows:

// jni is the instance of JNIWrapper
JVMRef<jstring> jfoo(jni.newString("foo"));

fail as follows with g++ 4.4.1:

---

error: no matching function for call to
‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

note: candidates are:
JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
[with T = _jstring*]

note:
JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
[with T = _jstring*]

---

That's probably because the temporary that's created
when the "source" returns can't be used by reference.

How should I amend JVMRef to make it appease
the compiler? (Btw, I'm aware of Boost's shared_ptr that
supports deleters, but it isn't a neat fit.)

[1] http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
 
V

Vladimir Jovic

mrts said:
template <typename T>
class JVMRef {
public:
JVMRef(JNIEnv *env, T ref) :
env_(env), ref_(ref) { }

JVMRef(JVMRef& other) :
env_(other.get_env()), ref_(other.release()) { }

JVMRef& operator=(JVMRef &other)
{ reset(other.release()); return *this; }

~JVMRef() { reset(NULL); }

T get()
{ return ref_; }

JNIEnv *get_env()
{ return env_; }

T release()
{ T ret = ref_; ref_ = NULL; return ret; }

void reset(T other = NULL) {
if (ref_ != other) {
env_->DeleteLocalRef(ref_);
ref_ = other;
}
}

private:
JNIEnv *env_;
T ref_;
};

The "source" function is given in
JNIWrapper::newString() above. Attempts to
use it in a "sink" function to construct a new
JVMRef to a Java string as follows:

// jni is the instance of JNIWrapper
JVMRef<jstring> jfoo(jni.newString("foo"));

fail as follows with g++ 4.4.1:

---

error: no matching function for call to
‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

note: candidates are:
JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
[with T = _jstring*]

note:
JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
[with T = _jstring*]

Read what the compiler is telling you. You do not have constructor with
next signature :

JVMRef(SNI::JVMRef<_jstring*>);

your constructors are :
JVMRef(JNIEnv *env, T ref)
JVMRef(JVMRef& other) <<< copy constructor

What is the return of jni.newString("foo") ?
 
M

mrts

mrts said:
template <typename T>
class JVMRef {
public:
    JVMRef(JNIEnv *env, T ref) :
        env_(env), ref_(ref) { }
    JVMRef(JVMRef& other) :
        env_(other.get_env()), ref_(other.release()) { }
    JVMRef& operator=(JVMRef &other)
    { reset(other.release()); return *this; }
    ~JVMRef() { reset(NULL); }
    T get()
    { return ref_; }
    JNIEnv *get_env()
    { return env_; }
    T release()
    { T ret = ref_; ref_ = NULL; return ret; }
    void reset(T other = NULL) {
        if (ref_ != other) {
            env_->DeleteLocalRef(ref_);
            ref_ = other;
        }
    }
private:
    JNIEnv *env_;
    T ref_;
};
The "source" function is given in
JNIWrapper::newString() above. Attempts to
use it in a "sink" function to construct a new
JVMRef to a Java string as follows:
    // jni is the instance of JNIWrapper
    JVMRef<jstring> jfoo(jni.newString("foo"));
fail as follows with g++ 4.4.1:

error: no matching function for call to
‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’
note: candidates are:
JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
[with T = _jstring*]
note:
JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
[with T = _jstring*]

Read what the compiler is telling you. You do not have constructor with
next signature :

JVMRef(SNI::JVMRef<_jstring*>);

your constructors are :
JVMRef(JNIEnv *env, T ref)
JVMRef(JVMRef& other)       <<< copy constructor

What is the return of jni.newString("foo") ?

A temporary, as shown above:

JVMRef<jstring> JNIWrapper::newString(const char* str)
{
// env_ is a private member of the JNIWrapper
// instance
JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
return ret;
}

---

I do understand that the temporary is the culprit here
and that there is no constructor that accepts JVMRef
by value.

I've examined the definition of auto_ptr meanwhile
and found the trickery of auto_ptr_ref that makes
auto_ptr work correctly in the source/sink scenario.

So, sorry for the fuss and for not thinking with
the required rigour :). I've settled for the following
to provide the functionality needed:

---

template <typename T>
struct jvmref_ref
{
explicit jvmref_ref(JNIEnv *e, T p) : _env(e), _ptr(p) { }

JNIEnv *_env;
T _ptr;
};

template <typename T>
class JVMRef {
public:
JVMRef(JNIEnv *env, T ref) :
_env(env), _ref(ref) { }

JVMRef(JVMRef& other) :
_env(other.get_env()), _ref(other.release()) { }

JVMRef(jvmref_ref<T> ref) : _env(ref._env), _ref(ref._ptr) { }

operator jvmref_ref<T>()
{ return jvmref_ref<T>(this->get_env(), this->release()); }

~JVMRef() { reset(NULL); }

T get()
{ return _ref; }

JNIEnv* get_env()
{ return _env; }

T release()
{ T ret = _ref; _ref = NULL; return ret; }

void reset(T other = 0)
{
if (_ref != other) {
_env->DeleteLocalRef(_ref);
_ref = other;
}
}

private:
JVMRef& operator=(const JVMRef&);
JNIEnv *_env;
T _ref;
};

---

That solves the general problem of how should one handle factory
functions that would otherwise use auto_ptr, but need a custom deleter
and can not use std::unique_ptr or boost::shared_ptr.

(Any insight on the wrongs of the above is of course much welcome.)
 
V

Vladimir Jovic

mrts said:
JVMRef<jstring> JNIWrapper::newString(const char* str)
{
// env_ is a private member of the JNIWrapper
// instance
JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
return ret;
}
The "source" function is given in
JNIWrapper::newString() above. Attempts to
use it in a "sink" function to construct a new
JVMRef to a Java string as follows:

// jni is the instance of JNIWrapper
JVMRef<jstring> jfoo(jni.newString("foo"));

fail as follows with g++ 4.4.1:

---

error: no matching function for call to
‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

note: candidates are:
JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
[with T = _jstring*]

note:
JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
[with T = _jstring*]

Sorry to be PITA again, but the compiler complains about
JVMRef<_jstring*> type, and your bits and pieces of the example are
showing you used JVMRef<jstring>.

If you copy and paste the fully compilable example (including main), you
will get much better response :
http://www.parashift.com/c++-faq-lite/how-to-post.html#faq-5.8

That's probably because the temporary that's created
when the "source" returns can't be used by reference.

Is that legal?
 
M

mrts

mrts said:
JVMRef<jstring> JNIWrapper::newString(const char* str)
{
    // env_ is a private member of the JNIWrapper
    // instance
    JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
    return ret;
}
The "source" function is given in
JNIWrapper::newString() above. Attempts to
use it in a "sink" function to construct a new
JVMRef to a Java string as follows:
    // jni is the instance of JNIWrapper
    JVMRef<jstring> jfoo(jni.newString("foo"));
fail as follows with g++ 4.4.1:

error: no matching function for call to
‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’
note: candidates are:
JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
[with T = _jstring*]
note:
JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
[with T = _jstring*]

Sorry to be PITA again, but the compiler complains about
JVMRef<_jstring*> type, and your bits and pieces of the example are
showing you used JVMRef<jstring>.

If you copy and paste the fully compilable example (including main), you
will get much better response :http://www.parashift.com/c++-faq-lite/how-to-post.html#faq-5.8

Yes, sorry for that... as for jstring, it's defined as follows in
Sun's jni.h:

class _jstring : public _jobject {};
typedef _jstring *jstring;
Is that legal?

No, and that was exactly the problem. To overcome this, the
struct jvmref_ref trick is needed (the trick lies in defining
`JVMRef(jvmref_ref<T> ref)` and `operator jvmref_ref<T>()`
and it is a shameless copy from std::auto_ptr).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top