swig中的director是一个由c/c++往上层语言调用的封装。以下笔者将以java为上层语言讲解director的内存管理。
Demo源码
先列举代码:
my.h
#ifndef MY_H
#define MY_H
#include <string>
class My
{
public:
virtual ~My(){}
virtual std::string text(const std::string& str) = 0;
};
void doIt(My* my);
#endif
my.i
%module(directors="1") "my.i"
%{
#include "my.h"
%}
%include "std_string.i"
%feature("director", assumeoverride=1) My;
%include "my.h"
这是一个比较简单的代码:通过swig导给java一个doIt
函数,并要求传入一个回调类My
,My
要做的事情就是当text被调用时,返回一个string。
ownership
swig中针对director设计了一个ownership的概念。在生成的java代码中有swigTakeOwnership/swigReleaseOwnership管理其ownership。
需要明确的是ownership是针对java而言的,即jvm是否拥有其内存。
构造后的ownership
那么,当java代码new一个director的时候,默认是否ownership为true呢?是的。看以下代码:
public class My {
private transient long swigCPtr;
protected transient boolean swigCMemOwn;
protected My(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}
...
public My() {
this(myJNI.new_My(), true);
myJNI.My_director_connect(this, swigCPtr, swigCMemOwn, true);
}
}
public构造函数指定其swigCMemOwn为true, 并调用My_director_connect指定weak_global为true。接着My_director_connect -> Java_com_cy_swig_myJNI_My_1director_1connect -> director::swig_connect_director -> swig_set_self -> swig_self_.set
。
看下swig_self_.set:
bool set(JNIEnv *jenv, jobject jobj, bool mem_own, bool weak_global) {
if (!jthis_) {
weak_global_ = weak_global || !mem_own; // hold as weak global if explicitly requested or not owned
if (jobj)
jthis_ = weak_global_ ? jenv->NewWeakGlobalRef(jobj) : jenv->NewGlobalRef(jobj);
return true;
} else {
return false;
}
}
jthis_一开始为空,因为mem_own=true, weak_global=true, 所以weak_global_=true,所以创建了一个weak global reference。由于是弱引用,所以内存管理是java来管的。
swigReleaseOwnership
刚才提到构造后java默认拥有内存,那么先看看swigReleaseOwnership后,内存是如何移交给c++的。
调用链:swigReleaseOwnership -> myJNI.My_change_ownership(this, swigCPtr, false); -> Java_com_cy_swig_myJNI_My_1change_1ownership -> director::swig_java_change_ownership -> swig_self_.java_change_ownership
那么来看下java_change_ownership:
/* Java proxy releases ownership of C++ object, C++ object is now
responsible for destruction (creates NewGlobalRef to pin Java proxy) */
void java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
if (take_or_release) { /* Java takes ownership of C++ object's lifetime. */
......
} else {
/* Java releases ownership of C++ object's lifetime */
if (weak_global_) {
jenv->DeleteWeakGlobalRef((jweak)jthis_);
jthis_ = jenv->NewGlobalRef(jself);
weak_global_ = false;
}
}
}
首先看注释,C++ object is now responsible for destruction,意思是内存的释放要c++来做。再看如何实现的,由于take_or_release=false,进入else分支,构造时weak_global_=true,所以将弱引用删除,构造强引用,weak_global_=false。这样就完成了内存管理的移交。
但是,还有一个问题,c++ 是如何responsible for destruction呢?
实际上这个责任是doIt要做的,也就是回调My的“库”要在不再需要回调它的时候delete。比如这个doIt实现:
void doIt(My* my)
{
my->text("hello");
delete my;
}
但是,还是有一个问题,之前创建的强引用,何时释放呢?
实际上,swig很聪明地利用多继承,自动了生成了一个SwigDirector_My:class SwigDirector_My : public My, public Swig::Director
,并将这个类的指针保存在java中的My.swigCPtr
。当doIt拿到My指针时,实际上是SwigDirector_My,所以delete my同时调用了Director的析构。
看看Director析构是怎么做的:
virtual ~Director() {
JNIEnvWrapper jnienv(this) ;
JNIEnv *jenv = jnienv.getJNIEnv() ;
swig_self_.release(jenv);
}
//swig_self_
void release(JNIEnv *jenv) {
if (jthis_) {
if (weak_global_) {
if (jenv->IsSameObject(jthis_, NULL) == JNI_FALSE)
jenv->DeleteWeakGlobalRef((jweak)jthis_);
} else
jenv->DeleteGlobalRef(jthis_);
}
jthis_ = NULL;
weak_global_ = true;
}
很明显,release中不论强弱引用,都正确释放了。
swigTakeOwnership
调用链类似,直接看java_change_ownership
void java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
if (take_or_release) { /* Java takes ownership of C++ object's lifetime. */
if (!weak_global_) {
jenv->DeleteGlobalRef(jthis_);
jthis_ = jenv->NewWeakGlobalRef(jself);
weak_global_ = true;
}
} else {
......
}
}
take_or_release=true,weak_global_=false(假设先release了ownership,再take,如果构造完马上调这个函数就等于什么也没做),所以删除强引用,创建弱引用。
弱引用的释放呢?
java“析构函数”finalize调用了delete,接着delete_My -> Java_com_cy_swig_myJNI_delete_1My
:
SWIGEXPORT void JNICALL Java_com_cy_swig_myJNI_delete_1My(JNIEnv *jenv, jclass jcls, jlong jarg1) {
My *arg1 = (My *) 0 ;
(void)jenv;
(void)jcls;
arg1 = *(My **)&jarg1;
delete arg1;
}
没错,聪明的读者已经想到了,这里的My实际上是SwigDirector_My,delete arg1
会触发Director的析构。
至此,director的内存管理核心部分就分析完了。
要不要ownership
根据上面的分析,很容易得出一条原则:要不要ownership,取决于谁负责销毁内存。如果c++在使用完回调后会自己销毁内存,那java创建好director就releaseOwnership,否则,持有ownership。