ルート情報の変更を通知するJavaのクラスを作りました。

C++用に作成されたクラスを利用してJavaでもルートの変更通知を受取るクラスを作りました。


Javaのクラスとしては、通知処理を司るRouteCheckクラスと、通知をイベントとして受取るRouteCheckListenerクラスを作りました。
RouteCheck.java

package net;

public class RouteCheck {

    static {
        System.loadLibrary("jninet");
        init();
    }

    public RouteCheck() {
    }

    private static native void init();
    public static native void addRouteChangeListener(RouteCheckListener listener);
    public static native void removeRouteChangeListener(RouteCheckListener listener);
    public static native void startLinstening();
    public static native void stopLinstening();
}

RouteCheckListener.java

package net;

public abstract class RouteCheckListener {
    public RouteCheckListener() {
    }

    abstract public void routeModified(RouteInfo[] added, RouteInfo[] removed);
}

RouteCheckListenerクラスで通知を受取った時の処理として、もう一度この中からリスナーを呼び出すような方法でも良かったのですが、同じような処理が重複して出てくるよりも、abstractクラスとして作成し実際に利用する時はこのクラスを継承してもらう方法の方がスマートなような気がしたので、この様な作りになっています。


それで、いつものようにJNIを作成するために、RouteCheckクラスをコンパイルした後にjavahでヘッダファイルを作成し、そしてJNIの実態を作成しました。
jniroutecheck.h

#ifndef _JNI_ROUTECHECK_CLASS
#define _JNI_ROUTECHECK_CLASS

#include <windows.h>

#include <vector>

#include "routecheck.h"

#include "net_RouteCheck.h"


typedef struct _LISTENER {
    jobject base;
    jobject caller;
} LISTENER, *PLISTENER;

typedef std::vector<PLISTENER>  listener_list;
typedef listener_list::iterator ll_iter;

#endif  //  _JNI_ROUTECHECK_CLASS

jniroutecheck.cpp

#include "jniroutecheck.h"
#include "jninet.h"

static listener_list    listener;
static JavaVM           *jvm = NULL;

jobject save_class(JNIEnv *env, jclass ric_class, RouteInfo info) {
    jmethodID ric_ctor = env->GetMethodID(ric_class, "<init>", "()V");
    jobject netifObj = env->NewObject(ric_class, ric_ctor);

    jfieldID ric_indexID = env->GetFieldID(ric_class, "index", "I");
    env->SetIntField(netifObj, ric_indexID, info.getIndex());
    jfieldID ric_destID = env->GetFieldID(ric_class, "dest", "Ljava/lang/String;");
    env->SetObjectField(netifObj, ric_destID, cStringToJString(env, info.getDest()));
    jfieldID ric_maskID = env->GetFieldID(ric_class, "mask", "Ljava/lang/String;");
    env->SetObjectField(netifObj, ric_maskID, cStringToJString(env, info.getMask()));
    jfieldID ric_gatewayID = env->GetFieldID(ric_class, "gateway", "Ljava/lang/String;");
    env->SetObjectField(netifObj, ric_gatewayID, cStringToJString(env, info.getGateway()));
    jfieldID ric_typeID = env->GetFieldID(ric_class, "type", "I");
    env->SetIntField(netifObj, ric_typeID, info.getType());
    jfieldID ric_metricID = env->GetFieldID(ric_class, "metric", "I");
    env->SetIntField(netifObj, ric_metricID, info.getMetric());
    jfieldID ric_defaultGwID = env->GetFieldID(ric_class, "defaultGw", "Z");
    env->SetBooleanField(netifObj, ric_defaultGwID, info.isDefaultGateway());
    jfieldID ric_primaryGwID = env->GetFieldID(ric_class, "primaryGw", "Z");
    env->SetBooleanField(netifObj, ric_primaryGwID, info.isPrimaryGateway());

    return netifObj;
}

void WINAPI route_modified(route_list added_table, route_list removed_table) {
    jobjectArray add_routes = NULL;
    jobjectArray remove_routes = NULL;

    jint err;
    JNIEnv *base_env = NULL;
    JavaVMAttachArgs t_args = {JNI_VERSION_1_6, NULL, NULL};
    if ((err = jvm->AttachCurrentThread((void**)&base_env, &t_args)) != JNI_OK) {
        return;
    }

    jclass ric_class = base_env->FindClass("net/RouteInfo");
    ric_class = (jclass)base_env->NewGlobalRef(ric_class);

    if (!added_table.empty()) {
        jsize rt_count = (jsize)added_table.size();
        if (rt_count > 0) {
            add_routes = base_env->NewObjectArray(rt_count, ric_class, NULL);
            for (jsize i = 0; i < rt_count; i++) {
                RouteInfo info = added_table.at(i);
                jobject one_route = save_class(base_env, ric_class, info);
                base_env->SetObjectArrayElement(add_routes, i, one_route);
            }
        }
    }
    if (!removed_table.empty()) {
        jsize rt_count = (jsize)removed_table.size();
        if (rt_count > 0) {
            remove_routes = base_env->NewObjectArray(rt_count, ric_class, NULL);
            for (jsize i = 0; i < rt_count; i++) {
                RouteInfo info = removed_table.at(i);
                jobject one_route = save_class(base_env, ric_class, info);
                base_env->SetObjectArrayElement(remove_routes, i, one_route);
            }
        }
    }

    for (ll_iter iter = listener.begin(); iter != listener.end(); iter++) {
        jobject rc_obj = (*iter)->caller;
        jclass rc_class = (jclass)base_env->GetObjectClass(rc_obj);
        jmethodID rc_callback = base_env->GetMethodID(rc_class, "routeModified", "([Lnet/RouteInfo;[Lnet/RouteInfo;)V");
        base_env->CallVoidMethod(rc_obj, rc_callback, add_routes, remove_routes, NULL);
    }
    jvm->DetachCurrentThread();
}

JNIEXPORT void JNICALL Java_net_RouteCheck_init(JNIEnv *env, jclass cls) {
    env->GetJavaVM(&jvm);
}

JNIEXPORT void JNICALL Java_net_RouteCheck_addRouteChangeListener(JNIEnv *env, jclass cls, jobject target) {
    PLISTENER obj = new LISTENER;
    obj->base = target;
    obj->caller = env->NewGlobalRef(target);
    listener.push_back(obj);
    if (listener.size() == 1) {
        RouteCheck::addCallBackRoutine(route_modified);
    }
    return ;
}

JNIEXPORT void JNICALL Java_net_RouteCheck_removeRouteChangeListener(JNIEnv *env, jclass cls, jobject target) {
    jobject remove = env->NewGlobalRef(target);
    for (ll_iter iter = listener.begin(); iter != listener.end(); iter++) {
        PLISTENER obj = (*iter);
        if (env->IsSameObject(obj->caller, remove) == JNI_TRUE) {
            env->DeleteGlobalRef(obj->caller);
            if (listener.size() == 1) {
                RouteCheck::removeCallBackRoutine(route_modified);
            }
            listener.erase(iter);
            delete(obj);
            break;
        }
    }
    env->DeleteGlobalRef(remove);
    return ;
}

JNIEXPORT void JNICALL Java_net_RouteCheck_startLinstening(JNIEnv *env, jclass cls) {
    RouteCheck::startRouteModifyCheck();
}

JNIEXPORT void JNICALL Java_net_RouteCheck_stopLinstening(JNIEnv *env, jclass cls) {
    RouteCheck::stopRouteModifyCheck();
}

このロジック中のJava_net_RouteCheck_init処理の中でjvmを保存していますが、これはC++のRoureCheckクラスからroute_modified関数が呼ばれた時に、Javaの環境情報を渡せないためです。


テストでは、過去に使ったIfIntoTestクラスのロジックを以下のように一部修正し、
IfInfoTest.java

・・・・
    RouteCheckTest test = new RouteCheckTest();
    RouteCheck.addRouteChangeListener(test);
    RouteCheck.startLinstening();
    System.out.println("Listening start");

    RouteInfo entry = new RouteInfo(2, "202.239.113.0", "255.255.255.0", "172.16.255.1", 0, 10, false, false);
    RouteInfo.addRoute(entry);
    System.out.println("After add route");
    ris = RouteInfo.getRouteList();
    for (int i = 0; i < ris.length; i++) {
        printRoute(ris[i]);
    }
    RouteInfo.delRoute(entry);
    System.out.println("After delete route");
    ris = RouteInfo.getRouteList();
    for (int i = 0; i < ris.length; i++) {
        printRoute(ris[i]);
    }
    System.out.println("Listening stop");
    RouteCheck.stopLinstening();
    RouteCheck.removeRouteChangeListener(test);
・・・・

新たに通知イベントを受取るRouteCheckTestクラスを作成しました。

package net.test;

import net.RouteCheckListener;
import net.RouteInfo;

public class RouteCheckTest extends RouteCheckListener {
    public RouteCheckTest() {
    }

    public void routeModified(RouteInfo[] added, RouteInfo[] removed) {
        if (added != null) {
            System.out.println("Routing information added.");
            for (int i = 0; i < added.length; i++) {
                print(added[i]);
            }
        }
        if (removed != null) {
            System.out.println("Routing information removed.");
            for (int i = 0; i < removed.length; i++) {
                print(removed[i]);
            }
        }
    }
    
    private void print(RouteInfo ri) {
        System.out.println("Route : "+ri.getDest()+"/"+ri.getMask()+" to "+ri.getGateway()+" "+ri.getMetric()+" "+ri.isDefaultGateway());
    }
}

これで、JavaからもC++と同じようにルートの変更があると、通知を受取ることが出来るようになりました。