ルート情報が変更されると通知するクラスを作りました

必要にせまられてルート情報の変更を取得したいということで、IPHLPAPIを使ってルート情報の変更を通知するクラスを作りました。


NotifyRouteChange()関数には、同期タイプと非同期タイプの両方あるのですが、今回は非同期モードにしてWaitForSingleObject()関数で通知をチェックする方法にしました。
routecheck.h

#ifndef _ROUTE_CHECK_CLASS
#define _ROUTE_CHECK_CLASS

#include <windows.h>

#include <vector>

#include "routeinfo.h"

typedef void (WINAPI *lpRouteCallBackFunc)(route_list added_table, route_list removed_table);
typedef std::vector<lpRouteCallBackFunc>    callback_list;
typedef callback_list::iterator             cl_iter;

class RouteCheck {
private:
    static DWORD WINAPI     NotifyThread(void *lpParameter);
    static void             notify(route_list added_table, route_list removed_table);

public:
    static void     addCallBackRoutine(lpRouteCallBackFunc func);
    static void     removeCallBackRoutine(lpRouteCallBackFunc func);
    static bool     startRouteModifyCheck();
    static bool     stopRouteModifyCheck();
};

#endif  //  _ROUTE_CHECK_CLASS

routecheck.cpp

#include <winsock2.h>
#include "routecheck.h"

static bool             loopctl = true;
static bool             active_check = false;
static callback_list    callback_routines;
static HANDLE           hThread;
static OVERLAPPED       overlap;

/*
 *  ルート情報が変更された時点でWindowsシステムから呼び出される
 *  param   : パラメータ(voidポインター)
 *
 *  return  : DWORD
 */
DWORD  WINAPI RouteCheck::NotifyThread(void *lpParameter) {
    HANDLE hand = NULL;
    memset(&overlap, 0, sizeof(overlap));
    overlap.hEvent = WSACreateEvent();

    route_list org_route_list = RouteInfo::getRouteList();

    loopctl = true;
    while (loopctl) {
        /*
         *  ルート情報に変更があったか場合に通知する
         *  return  : NO_ERROR = 処理が成功した
         *            その他 = エラー
         *  参照:http://msdn.microsoft.com/en-us/library/aa366332(VS.85).aspx
         */
        unsigned long ret = NotifyRouteChange(&hand, &overlap);
        if (ret != NO_ERROR) {
            if (WSAGetLastError() != WSA_IO_PENDING) {
                active_check = false;
                return 0;
            }
        }
        active_check = true;
        if (WaitForSingleObject(overlap.hEvent, INFINITE) == WAIT_OBJECT_0) {
            if (loopctl) {
                route_list new_route_list = RouteInfo::getRouteList();
                route_list added_route_list = route_list();
                for (size_t new_row = 0; new_row < new_route_list.size(); new_row++) {
                    RouteInfo new_entry = new_route_list.at(new_row);
                    bool found = false;
                    for (route_list::iterator org_row = org_route_list.begin(); org_row != org_route_list.end(); org_row++) {
                        RouteInfo org_entry = *org_row;
                        if ((new_entry.getDest() == org_entry.getDest())
                        && (new_entry.getMask() == org_entry.getMask())
                        && (new_entry.getGateway() == org_entry.getGateway())) {
                            found = true;
                            org_route_list.erase(org_row);
                            break;
                        }
                    }
                    if (!found) {
                        added_route_list.push_back(new_entry);
                    }
                }
                notify(added_route_list, org_route_list);
                org_route_list.clear();
                org_route_list = new_route_list;
            }
        }
    }
    org_route_list.clear();
    active_check = false;
    return 0;
}

/*
 *  ルート情報が変更された時点で登録されたプログラムを呼び出す
 *  param   : 追加されたルート情報(route_list)
 *            削除されたルート情報(route_list)
 *
 *  return  : 無し
 */
void RouteCheck::notify(route_list added_table, route_list removed_table) {
    for (size_t i = 0; i < callback_routines.size(); i++) {
        lpRouteCallBackFunc callbackfunc = callback_routines.at(i);
        callbackfunc(added_table, removed_table);
    }
}

/*
 *  呼び出すプログラムの登録をする
 *  param   : 登録するプログラムアドレス(RouteCallBackFuncポインター)
 *
 *  return  : 無し
 */
void RouteCheck::addCallBackRoutine(lpRouteCallBackFunc func) {
    callback_routines.push_back(func);
}

/*
 *  呼び出すプログラムの登録解除をする
 *  param   : 登録解除するプログラムアドレス(RouteCallBackFuncポインター)
 *
 *  return  : 無し
 */
void RouteCheck::removeCallBackRoutine(lpRouteCallBackFunc func) {
    for (cl_iter func_it = callback_routines.begin(); func_it != callback_routines.end(); func_it) {
        if (*func_it == func) {
            callback_routines.erase(func_it);
            break;
        }
    }
}

/*
 *  ルート情報変更のチェックを開始する
 *  param   : 無し
 *
 *  return  : bool
 *            true=開始が完了した
 *            false=開始に失敗した
 */
bool RouteCheck::startRouteModifyCheck() {
    DWORD threadid = 0;
    hThread = CreateThread(NULL, 0, NotifyThread, NULL, 0, &threadid);
    if (hThread != NULL) {
        int loop = 100;
        while (!active_check && (loop > 0)) {Sleep(100); loop--;}
        if (loop > 0)
            return true;
    }
    return false;
}

/*
 *  ルート情報変更のチェックを停止する
 *  param   : 無し
 *
 *  return  : bool
 *            true=停止が完了した
 *            false=停止に失敗した
 */
bool RouteCheck::stopRouteModifyCheck() {
    loopctl = false;
    SetEvent(overlap.hEvent);
    WSACloseEvent(overlap.hEvent);
    CloseHandle(hThread);
    int loop = 100;
    while (active_check && (loop > 0)) {Sleep(100); loop--;}
    if (loop > 0)
        return true;
    return false;
}

テストプログラムは、以前作ったnetinfo.cppのgetRoute()関数にロジックを追加しました。
netinfo.cpp

void WINAPI callback_func1(route_list added_table, route_list removed_table) {
    printf("Callback routine execute 1\n");
    for (size_t i = 0; i < added_table.size(); i++) {
        RouteInfo route = added_table.at(i);
        COUT << _T("Added route : ") << route.getDest() << _T(" ") << route.getMask() << _T(" ") << route.getGateway() << endl;
    }
    for (size_t i = 0; i < removed_table.size(); i++) {
        RouteInfo route = removed_table.at(i);
        COUT << _T("Removed route : ") << route.getDest() << _T(" ") << route.getMask() << _T(" ") << route.getGateway() << endl;
    }
}

void WINAPI callback_func2(route_list added_table, route_list removed_table) {
    COUT << _T("Callback routine execute 2") << endl;
}

void getRoute() {
    route_list list = RouteInfo::getRouteList();
    for (size_t i = 0; i < list.size(); i++) {
        RouteInfo info = list.at(i);
        COUT << _T("RouteInfo ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;
    }

    RouteInfo info = RouteInfo::getBestRoute(String(_T("202.239.113.18")));
    COUT << _T("Gateway ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;

    RouteCheck::addCallBackRoutine(callback_func1);
    RouteCheck::addCallBackRoutine(callback_func2);
    if (RouteCheck::startRouteModifyCheck()) {
        info = RouteInfo(0, String(_T("202.239.113.18")), String(_T("255.255.255.255")), String(_T("172.16.255.1")), 3, 10, false, false);
        RouteInfo::addRouteEntry(info);
        Sleep(500);
        COUT << _T("After add") << endl;
        list.clear();
        list = RouteInfo::getRouteList();
        for (size_t i = 0; i < list.size(); i++) {
            RouteInfo info = list.at(i);
            COUT << _T("RouteInfo ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;
        }
        RouteInfo::deleteRouteEntry(info);
        Sleep(500);
        COUT << _T("After delete") << endl;
        list.clear();
        list = RouteInfo::getRouteList();
        for (size_t i = 0; i < list.size(); i++) {
            RouteInfo info = list.at(i);
            COUT << _T("RouteInfo ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;
        }
        RouteCheck::stopRouteModifyCheck();
    }
}

Sleepが入っているのは、ルート情報の通知を受けたcallback_func1とcallback_func2での出力処理と、ルート情報一覧のロジックで正しく同期が取れていないので、表示がおかしくなるのを防ぐためです。


今日はこのクラスをJavaからも利用できるように実装できればいいかな。