文字化け・・・WMIも修正

文字コードの問題が理解できたので、始めに作ったwmiも作り直してみました。


wmi.cppのgetString(VARIANT *value)メソッドでVT_BSTRを別ロジックにしていたのを、VT_BOOL以外同じロジックに集約しました。

/*
 *  VARIANTデータを文字列値に変換し取得する
 *  param   : データ(VARIANTポインター)
 *
 *  return  : String
 */
String WmiResult::getString(VARIANT *value) {
    String result;
    if (value != NULL) {
        if (isArray(value)) {
            string_vector list = getStringArray(value);
            if (!list.empty())
                result = list.at(0);
        }
        else {
            if (getType(value) == VT_BOOL) {
                BSTR bool_str;
                /*
                 *  VT_BOOL型のデータをVT_BSTR型のデータに変換
                 *  return  : S_OK = 正常に変換できた(ここでは結果はチェックしない)
                 *            その他 = エラー
                 *  参照:http://msdn.microsoft.com/en-us/library/ms221338.aspx
                 */
                VarBstrFromBool(((isByRef(value)) ? *value->pboolVal : value->boolVal), 0, LOCALE_NOUSEROVERRIDE, &bool_str);
                result.append(_bstr_t(bool_str));
            }
            else {
                result.append((_TCHAR *)_bstr_t(*value));
            }
        }
    }
    return result;
}

これに会わせて、JNIのjniwmiのload_classを以下のように修正しました。

/*
 *  WmiCallからの結果をJavaのクラスに展開する処理
 */
jobject load_class(JNIEnv *env, WmiResult results) {
    /*
     *  WmiResultオブジェクトの生成
     */
    jobject wmiObj = env->NewObject(wr_class, wr_ctor);

    /*
     *  プロパティ値の設定
     */
    size_t prop_count = results.items();
    jobjectArray properties = env->NewObjectArray((jsize)prop_count, str_class, NULL);
    for (size_t i = 0; i < prop_count; i++) {
        _bstr_t prop = _bstr_t((_TCHAR *)results.getProperty(i).c_str());
        jobject prop_str = env->NewString((const jchar *)((wchar_t *)prop), (jsize)prop.length());
        env->SetObjectArrayElement(properties, (jsize)i, prop_str);
    }
    env->SetObjectField(wmiObj, wr_propertiesID, properties);

    /*
     *  戻り値の設定
     */
    size_t row = results.size();
    jobjectArray result = env->NewObjectArray((jsize)row, vector_class, NULL);
    for (size_t i = 0; i < row; i++) {
        jobject vector = env->NewObject(vector_class, vector_init);
        for (size_t j = 0; j < prop_count; j++) {
            String prop = results.getProperty(j);
            String entry_value = results.getString(prop, i);
            _bstr_t entry = _bstr_t((_TCHAR *)entry_value.c_str());
            jobject entry_str = env->NewString((const jchar *)((wchar_t *)entry), (jsize)entry.length());
            jboolean res = env->CallBooleanMethod(vector, vector_add, entry_str);
        }
        env->SetObjectArrayElement(result, (jsize)i, vector);
    }
    env->SetObjectField(wmiObj, wr_resultID, result);
    return wmiObj;
}

これで少しは綺麗なロジックになったかな。

文字化け

色々と調べていくと、Javaでの表示とC++での表示で文字表示が一致していないのは、内部的なロケールの処理の問題でした。


IfInfo.cppの中のgetInterfaceList()メソッドとloadInterfaceInfo(IfInfo& if_table)を書き換えて

/*
 *  システムで利用可能なインターフェースの一覧を取得する
 *  param   : 無し
 *
 *  return  : interface_list
 */
interface_list IfInfo::getInterfaceList() {
    interface_list if_list;

    long retCode = NO_ERROR;
    DWORD dwSize = 0;

    GetIfTable(NULL, &dwSize, true);
    PMIB_IFTABLE pIfTable = (MIB_IFTABLE *)malloc(dwSize);
    memset(pIfTable, 0, dwSize);
    retCode = GetIfTable(pIfTable, &dwSize, true);
    if (retCode == NO_ERROR) {
        for (u_int i = 0; i < pIfTable->dwNumEntries; i++) {
            IfInfo if_table = IfInfo();
            MIB_IFROW pInterface = pIfTable->table[i];
            if_table.ifIndex = pInterface.dwIndex;
            if_table.ifName = if_table.description = String((_TCHAR *)_bstr_t((char *)pInterface.bDescr));
            if_table.ifType = pInterface.dwType;
            if_table.opStatus = pInterface.dwOperStatus;
            if_list.push_back(if_table);
        }
    }
    free(pIfTable);

    return if_list;
}

/*
 *  インターフェースの詳細情報を設定する
 *  param   : インターフェーステーブル(IfInfoポインター)
 *
 *  return  : 無し
 */
void IfInfo::loadInterfaceInfo(IfInfo& if_table) {
    DWORD dwSize = 0;
    long retCode = NO_ERROR;
    if (GetAdaptersInfo(NULL, &dwSize) == ERROR_BUFFER_OVERFLOW) {
        PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(dwSize);
        memset(pAdapterInfo, 0, dwSize);
        retCode = GetAdaptersInfo(pAdapterInfo, &dwSize);
        if (retCode == NO_ERROR) {
            PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
            while (pAdapter) {
                if (pAdapter->Index == if_table.ifIndex) {
                    if_table.adapterName = String((_TCHAR *)_bstr_t(pAdapter->AdapterName));
                    if_table.ifAddress = getMacAddress(pAdapter->Address, pAdapter->AddressLength);
                    if_table.ipAddressList = AddrInfo::setAddressList(if_table.ifIndex);
                    if_table.dhcpEnabled = pAdapter->DhcpEnabled ? true : false;
                    HKEY hkey = NULL;
                    _bstr_t adapter = _bstr_t(_T("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\"));
                    adapter += _bstr_t((char *)pAdapter->AdapterName);
                    adapter += _bstr_t(_T("\\Connection"));
                    int rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (_TCHAR *)adapter, 0, KEY_READ, &hkey);
                    if (rc == ERROR_SUCCESS) {
                        DWORD valueType = 0;
                        DWORD nameLen = 0;
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, NULL, &nameLen);
                        _TCHAR *buff = (_TCHAR *)calloc(nameLen+1, sizeof(char));
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, (LPBYTE)buff, &nameLen);
                        if_table.ifName = String(buff);
                        RegCloseKey(hkey);
                        free(buff);
                    }
                }
                pAdapter = pAdapter->Next;
            }
            free(pAdapterInfo);
        }
    }
}

テストプログラムのメインの部分にロケールを設定する行を追加しました。

int _tmain(int argc, _TCHAR* argv[]) {
    COUT.imbue(locale("japanese"));

    getAddress();
    getInterface();

    key_wait();
    return 0;
}

この変更で、JavaC++の両方で問題なくデータを取得表示できました。

ネットワークインターフェース情報の取得(2)

Javaのクラスは、取りあえずこんな感じにしてみました。
アドレスを持つクラスとして、
AddrInfo.java

package net;

public class AddrInfo {
    public static int IPADDR_PRIMARY      = 0x0001; // Primary ipaddr
    public static int IPADDR_DYNAMIC      = 0x0004; // Dynamic ipaddr
    public static int IPADDR_DISCONNECTED = 0x0008; // Address is on disconnected interface
    public static int IPADDR_DELETED      = 0x0040; // Address being deleted
    public static int IPADDR_TRANSIENT    = 0x0080; // Transient address
    public static int IPADDR_DNS_ELIGIBLE = 0X0100; // Address is published in DNS.

    private String  ipAddress = null;
    private String  netMask = null;
    private String  bcastAddr = null;
    private int     ipType = 0;

    public AddrInfo() {
    }

    public String getIpAddress() {
        return ipAddress;
    }
    
    public String getNetMask() {
        return netMask;
    }
    
    public String getBroadcastAddress() {
        return bcastAddr;
    }
    
    public int getIpType() {
        return ipType;
    }
    
    public boolean isPrimaryAddress() {
        if  ((ipType & IPADDR_PRIMARY) > 0)
            return true;
        return false;
    }
}

それから、インターフェース情報を保存する基本となるクラスとして
IfInfo.java

package net;

public class IfInfo {
    public static int       IP_ADAPTER_DHCP_ENABLED = 0x0004;
    public static int       IF_OPER_STATUS_OPERATIONAL = 5;

    private int             index = 0;
    private String          name = null;
    private String          adapter = null;
    private String          description = null;
    private String          mac = null;
    private int             type = 0;
    private int             status = 0;
    private boolean         dhcp = false;
    private AddrInfo[]      address = null;

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

    public IfInfo() {
    }

    public int getIndex() {
        return index;
    }

    public String getName() {
        return name;
    }

    public String getAdapterName() {
        return adapter;
    }

    public String getDescription() {
        return description;
    }

    public String getMacAddress() {
        return mac;
    }

    public int getType() {
        return type;
    }

    public AddrInfo[] getAddressList() {
        return address;
    }

    public int getOpStatus() {
        return status;
    }

    public boolean isRunning() {
        if (status == IF_OPER_STATUS_OPERATIONAL)
            return true;
        return false;
    }

    public boolean isDhcpEnable() {
        return dhcp;
    }

    private static native void init();
    public static native IfInfo[] getInterfaces();
}

テストのクラスはこんな感じで
IfInfoTest.java

package net.test;

import net.AddrInfo;
import net.IfInfo;

public class IfInfoTest {
    public IfInfoTest() {
        IfInfo[] nis = IfInfo.getInterfaces();
        for (int i = 0; i < nis.length; i++) {
            print(nis[i]);
        }
    }
    
    private void print(IfInfo ni) {
        if (ni != null) {
            System.out.println("Nic : "+ni.getIndex());
            System.out.println("  Name : "+ni.getName());
            System.out.println("  AdapterName : "+ni.getAdapterName());
            System.out.println("  Description : "+ni.getDescription());
            System.out.println("  MacAddress : "+ni.getMacAddress());
            System.out.println("  Type : "+ni.getType());
            System.out.println("  Running : "+ni.isRunning());
            System.out.println("  DHCP : "+ni.isDhcpEnable());
            AddrInfo[] ais = ni.getAddressList();
            if (ais != null) {
                for (int j = 0; j < ais.length; j++) {
                    AddrInfo ai = ais[j];
                    if (ai != null) {
                        System.out.println("  Address : "+ai.getIpAddress()+"/"+ai.getNetMask()+" --- "+ai.getBroadcastAddress());
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        new IfInfoTest();
    }
}

IfInfo.javaコンパイルして、JNI用のヘッダファイルを生成してから、
C++でJNIのロジックを作成ました。

#include "jninet.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved) {
    return TRUE;
}

jobject load_address(JNIEnv *env, AddrInfo addr) {
    jobject addrifObj = env->NewObject(ai_class, ai_ctor);

    String address_value = addr.getIpAddress();
    _bstr_t address_str = _bstr_t((_TCHAR *)address_value.c_str());
    jobject address = env->NewString((const jchar *)((wchar_t *)address_str), (jsize)address_str.length());
    env->SetObjectField(addrifObj, ai_ipAddressID, address);

    String mask_value = addr.getNetMask();
    _bstr_t mask_str = _bstr_t((_TCHAR *)mask_value.c_str());
    jobject mask = env->NewString((const jchar *)((wchar_t *)mask_str), (jsize)mask_str.length());
    env->SetObjectField(addrifObj, ai_netMaskID, mask);

    String bcast_value = addr.getBroadcastAddress();
    _bstr_t bcast_str = _bstr_t((_TCHAR *)bcast_value.c_str());
    jobject bcast = env->NewString((const jchar *)((wchar_t *)bcast_str), (jsize)bcast_str.length());
    env->SetObjectField(addrifObj, ai_bcastAddrID, bcast);

    env->SetIntField(addrifObj, ai_ipTypeID, addr.getIpType());

    return addrifObj;
}

jobject load_class(JNIEnv *env, IfInfo info) {
    int bLen = 0;

    jobject netifObj = env->NewObject(if_class, if_ctor);

    env->SetIntField(netifObj, if_indexID, info.getIndex());

    String name_value = info.getName();
    _bstr_t name_str = _bstr_t((_TCHAR *)name_value.c_str());
    jobject name = env->NewString((const jchar *)((wchar_t *)name_str), (jsize)name_str.length());
    env->SetObjectField(netifObj, if_nameID, name);

    _bstr_t adapter_str = _bstr_t((_TCHAR *)info.getAdapterName().c_str());
    jobject adapter = env->NewString((const jchar *)((wchar_t *)adapter_str), (jsize)adapter_str.length());
    env->SetObjectField(netifObj, if_adapterID, adapter);

    _bstr_t description_str = _bstr_t((_TCHAR *)info.getDescription().c_str());
    jobject description = env->NewString((const jchar *)((wchar_t *)description_str), (jsize)description_str.length());
    env->SetObjectField(netifObj, if_descriptionID, description);

    _bstr_t mac_str = _bstr_t((_TCHAR *)info.getMacAddress().c_str());
    jobject mac = env->NewString((const jchar *)((wchar_t *)mac_str), (jsize)mac_str.length());
    env->SetObjectField(netifObj, if_macID, mac);

    env->SetIntField(netifObj, if_typeID, info.getType());

    address_list addrs = info.getIpAddressList();
    if (!addrs.empty()) {
        jsize ad_count = (jsize)addrs.size();
        jobjectArray addr_info = env->NewObjectArray(ad_count, ai_class, NULL);
        for (jsize j = 0; j < ad_count; j++) {
            AddrInfo addr = addrs.at(j);
            jobject one_address = load_address(env, addr);
            env->SetObjectArrayElement(addr_info, j, one_address);
        }
        env->SetObjectField(netifObj, if_addressID, addr_info);
    }
    else {
        env->SetObjectField(netifObj, if_addressID, NULL);
    }

    env->SetIntField(netifObj, if_statusID, info.getOpStatus());

    env->SetBooleanField(netifObj, if_dhcpID, info.isDhcpEnabled());

    return netifObj;
}

JNIEXPORT void JNICALL Java_net_IfInfo_init(JNIEnv *env, jclass cls) {
    if_class = (jclass)env->NewGlobalRef(cls);
    if_indexID = env->GetFieldID(if_class, "index", "I");
    if_nameID = env->GetFieldID(if_class, "name", "Ljava/lang/String;");
    if_adapterID = env->GetFieldID(if_class, "adapter", "Ljava/lang/String;");
    if_descriptionID = env->GetFieldID(if_class, "description", "Ljava/lang/String;");
    if_macID = env->GetFieldID(if_class, "mac", "Ljava/lang/String;");
    if_typeID = env->GetFieldID(if_class, "type", "I");
    if_statusID = env->GetFieldID(if_class, "status", "I");
    if_dhcpID = env->GetFieldID(if_class, "dhcp", "Z");
    if_addressID = env->GetFieldID(if_class, "address", "[Lnet/AddrInfo;");
    if_ctor = env->GetMethodID(if_class, "<init>", "()V");

    ai_class = env->FindClass("net/AddrInfo");
    ai_class = (jclass)env->NewGlobalRef(ai_class);
    ai_ipAddressID = env->GetFieldID(ai_class, "ipAddress", "Ljava/lang/String;");
    ai_netMaskID = env->GetFieldID(ai_class, "netMask", "Ljava/lang/String;");
    ai_bcastAddrID = env->GetFieldID(ai_class, "bcastAddr", "Ljava/lang/String;");
    ai_ipTypeID = env->GetFieldID(ai_class, "ipType", "I");
    ai_ctor = env->GetMethodID(ai_class, "<init>", "()V");

    jclass c = env->FindClass("java/lang/String");
    str_class = (jclass)env->NewGlobalRef(c);
    env->DeleteLocalRef(c);
}

JNIEXPORT jobjectArray JNICALL Java_net_IfInfo_getInterfaces(JNIEnv *env, jclass cls) {
    jobjectArray if_list = NULL;

    interface_list list = IfInfo::getIfList();
    if (!list.empty()) {
        jsize if_count = (jsize)list.size();
        if (if_count > 0) {
            if_list = env->NewObjectArray(if_count, if_class, NULL);
            for (jsize i = 0; i < if_count; i++) {
                IfInfo info = list.at(i);
                jobject one_interface = load_class(env, info);
                env->SetObjectArrayElement(if_list, i, one_interface);
            }
        }
        list.clear();
    }
    return if_list;
}

ただテストプログラムを実行すると、AdapterNameとDescriptionで文字化けが発生しちゃいました。
この問題、実は以前のWMIの処理の時にも発生していて、WMIの時はJNIからgetString()メソッドを呼ぶんじゃなくって、getValue()メソッドを呼び出した結果を直接処理するように変更をしています。


どの部分が問題なのかは分かっているんですが、どうして・・・がまだ分かっていないんですよね。
問題なのは、基本となるifinfo.cppの中で文字列を処理する部分があって、C++のテストプログラムで漢字が入った文字列を表示させる時に正しくないんで組み込んだ、

    basic_stringstream<_TCHAR> buf;
    buf << _bstr_t((char *)pInterface.bDescr);

に起因してるんだとは思うんだけど・・・。
この辺りをこれから調査です。

ネットワークインターフェース情報の取得

JavaのNetworkInterfaceでgetName()メソッドを呼び出してもXP環境では文字化けして正しく表示できないばかりでなく、getByName()メソッドでは、通常Windowsで利用される"ローカル エリア接続"等では探すことが出来ません。
それで、自前でアドレス情報を取得したりインターフェースを探せるクラスを作ってみようと思います。


まず、C++でインターフェース情報を取得するクラスの作成から。
IPアドレスを保存するクラスとして
addrinfo.h

#include <windows.h>
#include <tchar.h>
#include <comdef.h>

#include <iphlpapi.h>
#define LOOPBACK_INTERFACE  IF_TYPE_SOFTWARE_LOOPBACK
#define IPADDR_PRIMARY      MIB_IPADDR_PRIMARY
#define IPADDR_DYNAMIC      MIB_IPADDR_DYNAMIC
#define DIRECT_ROUTE        MIB_IPROUTE_TYPE_DIRECT
#define INDIRECT_ROUTE      MIB_IPROUTE_TYPE_INDIRECT

#include <vector>
#include <string>
using namespace std;

typedef basic_string<_TCHAR>    String;

class AddrInfo;
typedef vector<AddrInfo>            address_list;

extern const String HOST_MASK;
extern const String GATEWAY_NET;
extern const String GATEWAY_MASK;

class AddrInfo {
private:
    static  String          getBcastAddr(DWORD ip, DWORD mask, DWORD type);

public:
    AddrInfo(const AddrInfo& info);
    AddrInfo(String address, String mask = HOST_MASK, String bcast = String(_T("")), int type = 0);
    AddrInfo();
    ~AddrInfo();

    String  getIpAddress();
    String  getNetMask();
    String  getBroadcastAddress();
    int     getIpType();
    bool    isPrimaryAddress();

    static  address_list    setAddressList(int ifIndex);

private:
    String  ipAddress;
    String  netMask;
    String  bcastAddr;
    int     ipType;
};

addrinfo.cpp

#include "addrinfo.h"

const String HOST_MASK =    String(_T("255.255.255.255"));
const String GATEWAY_NET =  String(_T("0.0.0.0"));
const String GATEWAY_MASK = String(_T("0.0.0.0"));

//  Copy Constructor
AddrInfo::AddrInfo(const AddrInfo& info) {
    ipAddress = info.ipAddress;
    netMask = info.netMask;
    bcastAddr = info.bcastAddr;
    ipType = info.ipType;
}

//  Constructor
AddrInfo::AddrInfo(String address, String mask, String bcast, int type) {
    ipAddress = address;
    netMask = mask;
    if (!bcast.empty()) {
        bcastAddr = bcast;
    }
    else {
        bcastAddr = address;
    }
    ipType = type;
}

//  Constructor
AddrInfo::AddrInfo() {
    ipType = 0;
}

//  Destructor
AddrInfo::~AddrInfo() {
}

/*
 *  IPアドレスを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String AddrInfo::getIpAddress() {
    return ipAddress;
}

/*
 *  ネットワークマスクを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String AddrInfo::getNetMask() {
    return netMask;
}

/*
 *  Broadcastアドレスを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String AddrInfo::getBroadcastAddress() {
    return bcastAddr;
}

/*
 *  IPアドレスのタイプを取得する
 *  param   : 無し
 *
 *  return  : int
 */
int AddrInfo::getIpType() {
    return ipType;
}

/*
 *  IPアドレスがプライマリアドレスか確認する
 *  param   : 無し
 *
 *  return  : bool
 *            true  = プライマリアドレスである
 *            false = プライマリアドレスでない
 */
bool AddrInfo::isPrimaryAddress() {
    return ((ipType & IPADDR_PRIMARY) > 0) ? true : false;
}

/*
 *  IPアドレスとネットマスクからブロードキャストアドレスを取得する
 *  param   : IPアドレス(DWORD)
 *            ネットマスク(DWORD)
 *            IPタイプ(DWORD)
 *
 *  return  : String
 */
String AddrInfo::getBcastAddr(DWORD ip, DWORD mask, DWORD type) {
    DWORD bcast = (type == 1) ? 0xffffffff : 0x00000000;
    bcast ^= mask;
    DWORD network = ~bcast;
    network &= ip;
    bcast |= network;
    return String((_TCHAR*)_bstr_t(inet_ntoa(*(struct in_addr *)&bcast)));
}

/*
 *  指定したインターフェースに設定されたアドレスを取得する
 *  param   : インターフェースの番号(int)
 *
 *  return  : address_list
 */
address_list AddrInfo::setAddressList(int ifIndex) {
    address_list result;
    String ipaddress;
    String netmask;
    String bcastaddress;

    long retCode = NO_ERROR;
    DWORD dwSize = 0;
    GetIpAddrTable(NULL, &dwSize, 0);
    PMIB_IPADDRTABLE pIPAddrTable = (MIB_IPADDRTABLE *)malloc(dwSize);
    memset(pIPAddrTable, 0, dwSize);
    if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == NO_ERROR) {
        for (unsigned int num = 0; num < pIPAddrTable->dwNumEntries; num++) {
            if (ifIndex == pIPAddrTable->table[num].dwIndex) {
                ipaddress = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr)));
                netmask = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwMask)));
                bcastaddress = getBcastAddr(pIPAddrTable->table[num].dwAddr, pIPAddrTable->table[num].dwMask, pIPAddrTable->table[num].dwBCastAddr);
                AddrInfo info = AddrInfo(ipaddress,
                                            netmask,
                                            bcastaddress,
                                            pIPAddrTable->table[num].wType);
                result.push_back(info);
            }
        }
    }
    free(pIPAddrTable);

    return result;
}

インターフェース情報を保存するクラスとして
ifinfo.h

#include <vector>
#include <string>
#include <sstream>
using namespace std;

#include "addrinfo.h"

typedef basic_string<_TCHAR>    String;

class IfInfo;
typedef vector<IfInfo>              interface_list;
typedef interface_list::iterator    if_iter;

class IfInfo {
private:
    static  interface_list  getInterfaceList();
    static  void            loadInterfaceInfo(IfInfo *if_table);
    static  String          getMacAddress(unsigned char *value, unsigned long len);

public:
    IfInfo(const IfInfo& info);
    IfInfo();
    ~IfInfo();

    unsigned long   getIndex();
    String          getName();
    String          getAdapterName();
    String          getDescription();
    String          getMacAddress();
    int             getType();
    int             getOpStatus();
    address_list    getIpAddressList();
    bool            isDhcpEnabled();
    bool            isRunning();

    static  interface_list      getIfList();
    static  IfInfo              findInterfaceByIndex(unsigned long index);
    static  IfInfo              findInterfaceByName(String ifname);

private:
    unsigned long   ifIndex;
    String          ifName;
    String          adapterName;
    String          description;
    String          ifAddress;
    int             ifType;
    int             opStatus;
    address_list    ipAddressList;
    bool            dhcpEnabled;
};

ifinfo.cpp

#include "ifinfo.h"
/*
 *  システムで利用可能なインターフェースの一覧を取得する
 *  param   : 無し
 *
 *  return  : interface_list
 */
interface_list IfInfo::getInterfaceList() {
    interface_list if_list;

    long retCode = NO_ERROR;
    DWORD dwSize = 0;

    GetIfTable(NULL, &dwSize, true);
    PMIB_IFTABLE pIfTable = (MIB_IFTABLE *)malloc(dwSize);
    memset(pIfTable, 0, dwSize);
    retCode = GetIfTable(pIfTable, &dwSize, true);
    if (retCode == NO_ERROR) {
        for (u_int i = 0; i < pIfTable->dwNumEntries; i++) {
            IfInfo if_table = IfInfo();
            MIB_IFROW pInterface = pIfTable->table[i];
            if_table.ifIndex = pInterface.dwIndex;
            basic_stringstream<_TCHAR> buf;
            buf << _bstr_t((char *)pInterface.bDescr);
            if_table.ifName = if_table.description = buf.str();
            if_table.ifType = pInterface.dwType;
            if_table.opStatus = pInterface.dwOperStatus;
            if_list.push_back(if_table);
        }
    }
    free(pIfTable);

    return if_list;
}

String IfInfo::getMacAddress(unsigned char *value, unsigned long len) {
    _bstr_t mac;
    for (u_int i = 0; i < len; i++) {
        if (i > 0)
            mac += _T(":");
        char code[3];
        sprintf(code, "%02x", value[i]);
        mac += code;
    }
    return String((_TCHAR *)mac);
}

//  interface_info
//  Copy Constructor
IfInfo::IfInfo(const IfInfo& info) {
    ifIndex = info.ifIndex;
    ifName = info.ifName;
    adapterName = info.adapterName;
    description = info.description;
    ifAddress = info.ifAddress;
    ifType = info.ifType;
    opStatus = info.opStatus;
    dhcpEnabled = info.dhcpEnabled;
    if (info.ipAddressList.size() > 0) {
        for (size_t i = 0; i < info.ipAddressList.size(); i++) {
            ipAddressList.push_back(info.ipAddressList[i]);
        }
    }
}

//  Constructor
IfInfo::IfInfo() {
}

//  Destructor
IfInfo::~IfInfo() {
}

/*
 *  インターフェースの詳細情報を設定する
 *  param   : インターフェーステーブル(IfInfoポインター)
 *
 *  return  : 無し
 */
void IfInfo::loadInterfaceInfo(IfInfo *if_table) {
    DWORD dwSize = 0;
    long retCode = NO_ERROR;
    if (GetAdaptersInfo(NULL, &dwSize) == ERROR_BUFFER_OVERFLOW) {
        PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(dwSize);
        memset(pAdapterInfo, 0, dwSize);
        retCode = GetAdaptersInfo(pAdapterInfo, &dwSize);
        if (retCode == NO_ERROR) {
            PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
            while (pAdapter) {
                if (pAdapter->Index == if_table->ifIndex) {
                    if_table->adapterName = String((_TCHAR *)_bstr_t(pAdapter->AdapterName));
                    if_table->ifAddress = getMacAddress(pAdapter->Address, pAdapter->AddressLength);
                    if_table->ipAddressList = AddrInfo::setAddressList(if_table->ifIndex);
                    if_table->dhcpEnabled = pAdapter->DhcpEnabled ? true : false;
                    HKEY hkey = NULL;
                    _bstr_t adapter = _bstr_t(_T("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\"));
                    adapter += _bstr_t((char *)pAdapter->AdapterName);
                    adapter += _bstr_t(_T("\\Connection"));
                    int rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (_TCHAR *)adapter, 0, KEY_READ, &hkey);
                    if (rc == ERROR_SUCCESS) {
                        DWORD valueType = 0;
                        DWORD nameLen = 0;
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, NULL, &nameLen);
                        nameLen++;
                        char *buff = (char *)calloc(nameLen, sizeof(char));
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, (LPBYTE)buff, &nameLen);
                        RegCloseKey(hkey);
                        basic_stringstream<_TCHAR> buf;
                        buf << _bstr_t((_TCHAR *)buff);
                        if_table->ifName = buf.str();
                        free(buff);
                    }
                }
                pAdapter = pAdapter->Next;
            }
            free(pAdapterInfo);
        }
    }
}

/*
 *  インターフェースの名前を取得する
 *  param   : 無し
 *
 *  return  : String
 */
String IfInfo::getName() {
    return ifName;
}

/*
 *  インターフェースのアダプタ名を取得する
 *  param   : 無し
 *
 *  return  : String
 */
String IfInfo::getAdapterName() {
    return adapterName;
}

/*
 *  インターフェースの説明を取得する
 *  param   : 無し
 *
 *  return  : String
 */
String IfInfo::getDescription() {
    return description;
}

/*
 *  インターフェースのMACアドレスを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String IfInfo::getMacAddress() {
    return ifAddress;
}

/*
 *  インターフェースの番号を取得する
 *  param   : 無し
 *
 *  return  : u_long
 */
u_long IfInfo::getIndex() {
    return ifIndex;
}

/*
 *  インターフェースのタイプを取得する
 *  param   : 無し
 *
 *  return  : int
 */
int IfInfo::getType() {
    return ifType;
}

/*
 *  インターフェースのステータスを取得する
 *  param   : 無し
 *
 *  return  : int
 */
int IfInfo::getOpStatus() {
    return opStatus;
}

/*
 *  インターフェースのアドレスリストを取得する
 *  param   : 無し
 *
 *  return  : address_list
 */
address_list IfInfo::getIpAddressList() {
    return ipAddressList;
}

/*
 *  インターフェースがDHCPに設定されているか確認する
 *  param   : 無し
 *
 *  return  : bool
 *            true  = DHCPである
 *            false = DHCPでない
 */
bool IfInfo::isDhcpEnabled() {
    return dhcpEnabled;
}

/*
 *  インターフェースが動作中か確認する
 *  param   : 無し
 *
 *  return  : bool
 *            true  = 動作中である
 *            false = 動作中でない
 */
bool IfInfo::isRunning() {
    return (opStatus == IF_OPER_STATUS_OPERATIONAL) ?  true : false;
}

/*
 *  システムで利用可能なインターフェースの一覧を取得する
 *  param   : 無し
 *
 *  return  : interface_list
 */
interface_list IfInfo::getIfList() {
    interface_list net_list = getInterfaceList();
    for (if_iter if_ent = net_list.begin(); if_ent != net_list.end(); if_ent++) {
        IfInfo::loadInterfaceInfo(&(*if_ent));
    }

    return net_list;
}

/*
 *  インターフェースの一覧からインターフェース番号の情報を取得する
 *  param   : インターフェース番号(u_long)
 *
 *  return  : IfInfo
 */
IfInfo IfInfo::findInterfaceByIndex(unsigned long index) {
    IfInfo if_table;

    interface_list net_list = getInterfaceList();
    for (if_iter if_ent = net_list.begin(); if_ent != net_list.end(); if_ent++) {
        if ((*if_ent).getIndex() == index) {
            IfInfo::loadInterfaceInfo(&(*if_ent));
            return *if_ent;
        }
    }

    return if_table;
}

/*
 *  インターフェースの一覧からインターフェース名の情報を取得する
 *  param   : インターフェース名(charポインター)
 *
 *  return  : IfInfo
 */
IfInfo IfInfo::findInterfaceByName(String ifname) {
    IfInfo if_table;

    interface_list net_list = getIfList();
    for (if_iter if_ent = net_list.begin(); if_ent != net_list.end(); if_ent++) {
        if ((*if_ent).getName().compare(ifname) == 0) {
            IfInfo::loadInterfaceInfo(&(*if_ent));
            return *if_ent;
        }
    }
    return if_table;
}

テストの為に次のプログラムを実行して確認。

#include <windows.h>

#include <string>
#include <iostream>
using namespace std;

#include <addrinfo.h>
#include <ifinfo.h>
using namespace penguin;

#ifdef  _UNICODE
#define COUT    wcout
#define CIN     wcin
#else
#define COUT    cout
#define CIN     cin
#endif  //  _UNICODE

void getAddress() {
    for (int i = 0; i < 256; i++) {
        address_list list = AddrInfo::setAddressList(i);
        for (size_t j = 0; j < list.size(); j++) {
            AddrInfo info = list.at(j);
            COUT << _T("Index : ") << i << _T("-") << info.getIpAddress() << endl;
        }
    }
}

void getInterface() {
    interface_list list = IfInfo::getIfList();
    for (size_t j = 0; j < list.size(); j++) {
        IfInfo info = list.at(j);
        COUT << _T("Index : ") << info.getIndex() << _T("-") << info.getDescription() << _T("-") << info.getName() << endl;
    }
    IfInfo intInfo = IfInfo::findInterfaceByName(String(_T("ローカル エリア接続 3")));
    COUT << _T("Index : ") << intInfo.getIndex() << _T("-") << intInfo.getDescription() << _T("-") << intInfo.getName() << endl;
}

int _tmain(int argc, _TCHAR* argv[]) {

    getAddress();
    getInterface();

    return 0;
}

このプログラムをベースにJNIでJavaからのアクセスクラスを作成します。

SerialNumberの取得(3)

色々やっていると欲が出てくるもので、VistaとXPに関しては昨日のロジックでなんら問題は無かったのですが、今度はWindows2000で表示できないというのが発覚しました。
最近ではWindows2000なんて殆ど見ることもなかったのですが、仕事の関係でWindows2000のサポートは無視するわけにはいかず、とにかくネットで検索して
http://www.usefullcode.net/2007/02/if.html
なんかを参考にして必要最低限まで削った関数を作っちゃいました。

#include <winioctl.h>
#include <ata.h>
enum    IO_CONTROL_CODE {
    DFP_GET_VERSION        = 0x00074080,
    DFP_RECEIVE_DRIVE_DATA = 0x0007c088,
};

enum    ATAPI_COMMAND_CODE {
    ATAPI_IDENTIFY_DEVICE   = 0xA1,
    ATA_IDENTIFY_DEVICE = 0xEC,
};

typedef GETVERSIONINPARAMS GETVERSIONOUTPARAMS, *PGETVERSIONOUTPARAMS, *LPGETVERSIONOUTPARAMS;

#pragma pack(push,1)
typedef struct _IDENTIFY_DEVICE_OUTDATA {
    SENDCMDOUTPARAMS    sSendCmdOutParam;
    BYTE    pData[IDENTIFY_BUFFER_SIZE-1];
} IDENTIFY_DEVICE_OUTDATA, *PIDENTIFY_DEVICE_OUTDATA;
#pragma pack(pop)

String getDiskSerial(int drive, _TCHAR *device_id) {
    BOOL    status = FALSE;
    DWORD   ret_size = 0;
    String  serialNumber = String(_T("N/A"));
    /*
     *  取得したい物理デバイスをオープンする
     *  return  : I/Oハンドル
     */
    HANDLE  hIoCtrl = CreateFile(device_id,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
    if (hIoCtrl != INVALID_HANDLE_VALUE) {
        GETVERSIONOUTPARAMS     ver_param;
        memset((void*)&ver_param, 0, sizeof(ver_param));
        /*
         *  デバイスからDFPのバージョンを取得する
         *  return  : 処理ステータス
         *            0以外 = 成功()
         *            0 = 失敗
         *  参照:http://msdn.microsoft.com/en-us/library/aa363216.aspx
         */
        status = DeviceIoControl(hIoCtrl,
                                DFP_GET_VERSION,
                                NULL,
                                0,
                                &ver_param,
                                sizeof(ver_param),
                                &ret_size,
                                NULL);
        if (status != 0) {
            SENDCMDINPARAMS             send_cmd;
            IDENTIFY_DEVICE_OUTDATA     device_data;

            memset(&send_cmd, 0, sizeof(SENDCMDINPARAMS));
            send_cmd.cBufferSize = IDENTIFY_BUFFER_SIZE;
            send_cmd.irDriveRegs.bCommandReg = (ver_param.bIDEDeviceMap >> drive & 0x10) ? ATAPI_IDENTIFY_DEVICE : ATA_IDENTIFY_DEVICE;
            send_cmd.irDriveRegs.bDriveHeadReg = 0xA0 | ((drive & 1) << 4);
            send_cmd.bDriveNumber = drive;

            ret_size = 0;
            memset(&device_data, 0, sizeof(IDENTIFY_DEVICE_OUTDATA));
            /*
             *  デバイスからDFPのバージョンを取得する
             *  return  : 処理ステータス
             *            0以外 = 成功
             *            0 = 失敗
             */
            status = DeviceIoControl(hIoCtrl,
                                    DFP_RECEIVE_DRIVE_DATA,
                                    &send_cmd,
                                    sizeof(SENDCMDINPARAMS),
                                    &device_data,
                                    sizeof(IDENTIFY_DEVICE_OUTDATA),
                                    &ret_size,
                                    NULL);
            if (status != 0) {
                PIDENTIFY_DEVICE_DATA   id_device;
                id_device = (PIDENTIFY_DEVICE_DATA)device_data.sSendCmdOutParam.bBuffer;
                size_t len = sizeof(id_device->SerialNumber);
                unsigned char *serial = (unsigned char *)calloc(len+1, sizeof(char));
                for(size_t i = 0; i < len; i += 2) {
                    serial[i] = id_device->SerialNumber[i+1];
                    serial[i+1] = id_device->SerialNumber[i];
                }
                _bstr_t buff = _bstr_t((char *)serial);
                serialNumber = String((_TCHAR*)buff);
                free(serial);
            }
        }
        else {
            STORAGE_PROPERTY_QUERY      query;
            STORAGE_DESCRIPTOR_HEADER   header;

            memset(&query, 0, sizeof(STORAGE_PROPERTY_QUERY));
            query.PropertyId = StorageDeviceProperty;
            query.QueryType = PropertyStandardQuery;

            /*
             *  デバイスのプロパティを取得する(但しここではプロパティを格納するエリアのサイズだけ取得する)
             *  return  : 処理ステータス
             *            0以外 = 成功
             *            0 = 失敗
             *  参照:http://msdn.microsoft.com/en-us/library/ms803642.aspx
             */
            status = DeviceIoControl(hIoCtrl,
                                    IOCTL_STORAGE_QUERY_PROPERTY,
                                    &query,
                                    sizeof(STORAGE_PROPERTY_QUERY),
                                    &header,
                                    sizeof(STORAGE_DESCRIPTOR_HEADER),
                                    &ret_size,
                                    NULL);
            if (status != 0) {
                PSTORAGE_DEVICE_DESCRIPTOR  device_desc;
                device_desc = (PSTORAGE_DEVICE_DESCRIPTOR)calloc(header.Size+1, sizeof(char));
                ret_size = 0;
                /*
                 *  デバイスのプロパティを取得する
                 *  return  : 処理ステータス
                 *            0以外 = 成功
                 *            0 = 失敗
                 *  参照:http://msdn.microsoft.com/en-us/library/ms803642.aspx
                 */
                status = DeviceIoControl(hIoCtrl,
                                        IOCTL_STORAGE_QUERY_PROPERTY,
                                        &query,
                                        sizeof(STORAGE_PROPERTY_QUERY),
                                        device_desc,
                                        header.Size,
                                        &ret_size,
                                        NULL);
                if (status != 0) {
                    if ((int)device_desc->SerialNumberOffset > 0) {
                        char *serial = (char *)device_desc;
                        serial[header.Size] = 0;
                        _bstr_t buff = _bstr_t((char *)&serial[device_desc->SerialNumberOffset]);
                        serialNumber = String((_TCHAR*)buff);
                    }
                }
                free(device_desc);
            }
        }
        CloseHandle(hIoCtrl);
    }
    return serialNumber;
}

DDKを使っているので、これは
http://www.microsoft.com/japan/whdc/DevTools/WDK/WDKpkg.mspx
からリンクを辿ってダウンロードしてきました。
それで、呼び出しプログラム部分は、

    WmiResult disk_drive = wmi.access(_T("SELECT * FROM Win32_DiskDrive"));
    if (disk_drive.items() == 0) {
        String msg = wmi.getErrorMessage();
        return;
    }
    COUT << disk_drive.print();

    for (size_t i = 0; i < disk_drive.size(); i++) {
        String serial = getDiskSerial(disk_drive.getLong(_T("Index"), i), (_TCHAR *)disk_drive.getString(_T("DeviceID"), i).c_str());
        COUT << _T("SerialNumber = ") << serial << endl;
    }

に変更して完了です。

SerialNumberの取得(2)

Vistaレジストリ情報とXPでは違っていたみたいで、このロジックでは取得できなくなってしまった。
WmiToolsを眺めていると、
Win32_PhysicalMediaに"SerialNumber"情報が入っていて、欲しい情報が取得できるみたいです。

    WmiResult disk_drive = wmi.access(_T("SELECT * FROM Win32_DiskDrive"));
    if (disk_drive.items() == 0) {
        String msg = wmi.getErrorMessage();
        return;
    }
    COUT << disk_drive.print();

    for (size_t i = 0; i < disk_drive.size(); i++) {
        String drive = WmiCall::escape(disk_drive.getString(String(_T("DeviceID")), i));
        String sub_select = WmiCall::createSelect(_T("ASSOCIATORS OF {Win32_DiskDrive.DeviceID=\"%s\"} WHERE AssocClass = Win32_DiskDrivePhysicalMedia ResultClass = Win32_PhysicalMedia"), drive.c_str(), NULL);
        WmiResult media = wmi.access(sub_select);
        if (media.items() > 0) {
            COUT << _T("SerialNumber = ") << media.getString(_T("SerialNumber")) << endl;
        }
    }

こんな感じでVistaでも取れるようになりました。
ついでにWimCallクラスの中に、escapeと言う名前のメソッドを作成しました。

/*
 *  文字列中の'\'をエスケープするために'\\'になるように変換した文字列を返す
 *  param   : 処理の対象となる文字列(String)
 *
 *  return  : String
 */
String WmiCall::escape(String original) {
    size_t spos = 0, epos = String::npos;
    String token = String(_T(""));
    do {
        epos = original.find_first_of(_T("\\"), spos);
        if (epos != String::npos) {
            size_t count = epos - spos + 1;
            token.append(original.substr(spos, count));
            token.append(_T("\\"));
        }
        else {
            token.append(original.substr(spos));
        }
        spos = epos + 1;
    } while (epos != String::npos);
    return token;
}

これは、disk_drive.getString(String(_T("DeviceID")), i)の処理の結果として

\\.\PHYSICALDRIVE1

と言う値が帰ってくるので、これを次のWQL文に変換する時には、

\\\\.\\PHYSICALDRIVE1

のように、"\"を"\\"にエスケープするために作成しました。

HDDのSerianNumberを取得する方法

レジストリキーの

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi

以下のTreeに

\Scsi Port w\Scsi Bus x\Target Id y\Logical Unit Id z\SerialNumber

という値が存在するので、これを利用することにしました。
Wmi32_DiskDriveを使って、次のようなロジックで処理しました。

    WmiResult disk_drive = wmi.access(_T("SELECT * FROM Win32_DiskDrive"));
    if (disk_drive.items() == 0) {
        String msg = wmi.getErrorMessage();
        return;
    }
    COUT << disk_drive.print();

    for (size_t i = 0; i < disk_drive.size(); i++) {
        String reg_key = WmiCall::createSelect(_T("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d"),
                                                disk_drive.getLong(_T("SCSIPort"), i),
                                                disk_drive.getLong(_T("SCSIBus"), i),
                                                disk_drive.getLong(_T("SCSITargetId"), i),
                                                disk_drive.getLong(_T("SCSILogicalUnit"), i),
                                                NULL);
        HKEY hkey = NULL;
        int rc = ERROR_SUCCESS;
        if ((rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_key.c_str(), 0, KEY_READ, &hkey)) == ERROR_SUCCESS) {
            DWORD buffSize = 0;
            DWORD valueType = 0;
            rc = RegQueryValueEx(hkey, _T("SerialNumber"), 0, &valueType, NULL, &buffSize);
            String result;
            if (rc == ERROR_SUCCESS) {
                buffSize++;
                char *buff = (char *)calloc(buffSize, sizeof(char));
                if (buff != NULL) {
                    if (RegQueryValueEx(hkey, _T("SerialNumber"), 0, &valueType, (unsigned char *)buff, &buffSize) == ERROR_SUCCESS) {
                        _bstr_t value = _bstr_t(buff);
                        result = String((_TCHAR *)buff);    
                    }
                    free(buff);
                }
            }
            RegCloseKey(hkey);
            if (!result.empty()) {
                COUT << _T("SerialNumber = ") << result << endl;
            }
        }
    }