27 12月 2013

[Java] 透過SWIG 從JNI (C/C++) callback 回 Java

最近寫 Android,一般 Java 層可以透過 JNI 呼叫 C/C++ 撰寫的 .so 檔,但是要從 C/C++ 呼叫回 Java 層呢!? 一般需要 JNIEnv 這個特殊的變數,才可以透過它來呼叫到 Java Library。

不過今天這邊要介紹的是另外一個方法 - 透過 SWIG 的 directors 的 feature
SWIG 可以將 C++ 的 class 包裝成一個 Java layer 的 class ,而 Java layer 可以去繼承 Java wrap C++ 的 class,然後 override 它的 virtual function 後,C/C++ layer 呼叫到這個 virtual function 時,就會被轉 call 到 Java layer了。



以下是簡單的 sample code

MyLib.h
在 Callback function 前加上 virtual 
#ifndef __MY_LIB_H
#define __MY_LIB_H
class Base
{
public:
     Base(){}
     virtual ~Base(){}
     void CallIn(int num1, int num2);
     virtual void Callback(int num1, int num2); //setting in virtual for override
};
#endif


MyLib.cpp
#include "MyLib.h"
#include <android/log.h>
void Base::CallIn(int num1, int num2)
{
    Callback(num1+1, num2+1); // add the number 
}
void Base::Callback(int num1, int num2)
{
    // If override this function in Java layer, you will not see the log
    __android_log_print(ANDROID_LOG_DEBUG, 
                        "MyLib", 
                        "Base::Callback() num1=%d, num2=%d", num1, num2);
}

MyLib.i
在 .i 檔中,加上 %feature("director") 後面加上 想處理的 class name
這樣 SWIG 就會自動處理 class name 底下所有的 virtual function
若不指定的話,SWIG 會處理所有的 class 的 virtual function
也可以加上 %feature("nodirector") Foo::Bar; 將 disable 指定的 virtual function
%module(directors="1") MyLib
%{
  #include "MyLib.h"
%}

%feature("director") Base;
%include "MyLib.h"

%pragma(java) jniclasscode=%{
    static {
        try {
            java.lang.System.loadLibrary("MyLib");
        } catch (UnsatisfiedLinkError e) {
            java.lang.System.err.println("JNI error: " + e);
            java.lang.System.exit(1);
        }
    }
%}

Android.mk
務必要加上-fexceptions -frtti,這樣 SWIG 才有能力處理 class override
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := MyLib
LOCAL_SRC_FILES := MyLib_wrap.cpp MyLib.cpp
LOCAL_LDLIBS := -llog
LOCAL_CFLAGS := -fexceptions -frtti #for SWIG director feature
include $(BUILD_SHARED_LIBRARY)


MainActivity.java
import com.yourapp.Base
//... other import

public class MainActivity extends Activity
{
    class JDerived extends Base
    {
        @Override
        public void Callback(int num1, int num2)
        {
            String msg = String.format(
                             "Derived::Callback() num1=%d, num2=%d",
                             num1, num2 );
            System.out.println(msg);
        }
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        JDerived d = new JDerived();
        d.CallIn(1,2);
    }
}

最後輸出的 output 結果就是
>>> Derived::Callback() num1=2, num2=3

最後,由於 SWIG 在處理 virtual function 時,需要多 wrapper 好幾層的 function,因而增加了一些 function call 的 overhead,所以盡量指定想被 override 的 virtual function 即可。

沒有留言: