MicroSoftの_bstr_tクラスをシミュレート

最後に日記を書いたあとの作業が、社外秘の情報が多くて日記に書けない内容だったので、ずーっと空いてしまいました。
今回は文字コード変換のロジックをまかなうクラスを作ってみました。
以前作ったプログラムで、UNICODE文字セットでも、MBCS文字セットでも動作するプログラミングをするために、文字セットに依存しそうなロジック中では、"_bstr_t"というMSのクラスを利用していました。
例えば、

/*
 *  指定したインターフェースに設定されたアドレスを取得する
 *  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;
}

ただ"_bstr_t"クラスはMSのクラスで、Linuxへはこのまま移行できないので、この"_bstr_t"クラスをシミュレートする感じのクラスを作って見ました。
100%シミュレートするクラスを作るのは面倒だったので、自分の使ってるロジック中で利用される機能を中心に構築する事にしました。
クラスの名前は"FString"(ちょっとべた過ぎるかも・・・)にしています。


ソースプログラムは、
FString.h

#include <locale.h>
#include <tchar.h>

#include <string>
using std::string;
using std::wstring;

#ifdef  _UNICODE
    typedef std::wstring                String;
#else
    typedef std::string                 String;
#endif  //  _UNICODE

#define _LOCALE_JAPANESE    _T("Japanese_Japan.932")

class FString {
public:
    FString();
    FString(string str);
    FString(wstring wstr);
    FString(const char *str);
    FString(const wchar_t *wstr);
    ~FString();

    FString& operator=(const char *from);
    FString& operator=(const wchar_t *from);
    FString& operator+=(FString& from);
    FString& operator+=(const char *from);
    FString& operator+=(const wchar_t *from);

    operator const char*();
    operator char*();
    operator string();
    operator const wchar_t*();
    operator wchar_t*();
    operator wstring();

    string          getString();
    wstring         getWString();
    const char      *getCharBuff();
    const wchar_t   *getWCharBuff();

    static      void        setLocale(String loc);
    static      wstring     strToWstr(string str);
    static      string      wstrToStr(wstring wstr);

private:
    wstring     wchar_string;
    string      char_string;
};  //  class FString

それと、FStringのクラスボディは、
FString.cpp

#include "FString.h"

static String       own_loc = _LOCALE_JAPANESE;

//  Constructor
FString::FString() {
}

//  Constructor
//  param   : 初期化文字列(string)
FString::FString(string str) {
    wchar_string = strToWstr(str);
    char_string.clear();
}

//  Constructor
//  param   : 初期化文字列(wstring)
FString::FString(wstring wstr) {
    wchar_string = wstr;
    char_string.clear();
}

//  Constructor
//  param   : 初期化文字列(char*)
FString::FString(const char *str) {
    wchar_string = strToWstr(string(str));
    char_string.clear();
}

//  Constructor
//  param   : 初期化文字列(wchar_t*)
FString::FString(const wchar_t *wstr) {
    wchar_string = wstring(wstr);
    char_string.clear();
}

//  Destructor
FString::~FString() {
    wchar_string.clear();
    char_string.clear();
}

/*
 *  "=(代入)"オペレータの処理
 */
FString& FString::operator=(const char *from) {
    wchar_string = strToWstr(string(from));
    char_string.clear();
    return *this;
}

/*
 *  "=(代入)"オペレータの処理
 */
FString& FString::operator=(const wchar_t *from) {
    wchar_string = wstring(from);
    char_string.clear();
    return *this;
}

/*
 *  "+=(追加)"オペレータの処理
 */
FString& FString::operator+=(FString& from) {
    wchar_string.append(from.getWString());
    char_string.clear();
    return *this;
}

/*
 *  "+=(追加)"オペレータの処理
 */
FString& FString::operator+=(const char *from) {
    wstring ap = strToWstr(string(from));
    wchar_string.append(ap);
    char_string.clear();
    return *this;
}

/*
 *  "+=(追加)"オペレータの処理
 */
FString& FString::operator+=(const wchar_t *from) {
    wchar_string.append(from);
    char_string.clear();
    return *this;
}

/*
 *  "const char*(リダイレクト)"オペレータの処理
 */
FString::operator const char*() {
    return getCharBuff();
}

/*
 *  "char*(リダイレクト)"オペレータの処理
 */
FString::operator char*() {
    return (char *)getCharBuff();
}

/*
 *  "string(リダイレクト)"オペレータの処理
 */
FString::operator string() {
    getString();
    return char_string;
}

/*
 *  "const wchar_t*(リダイレクト)"オペレータの処理
 */
FString::operator const wchar_t*() {
    return getWCharBuff();
}

/*
 *  "wchar_t*(リダイレクト)"オペレータの処理
 */
FString::operator wchar_t*() {
    return (wchar_t *)getWCharBuff();
}

/*
 *  "wstring(リダイレクト)"オペレータの処理
 */
FString::operator wstring() {
    return wchar_string;
}

/*
 *  登録文字列を取得する
 *
 *  return  : string
 */
string FString::getString() {
    if (char_string.empty() && !wchar_string.empty()) {
        char_string = wstrToStr(wchar_string);
    }
    return char_string;
}

/*
 *  登録文字列を取得する
 *
 *  return  : char*
 */
const char *FString::getCharBuff() {
    getString();
    return char_string.c_str();
}

/*
 *  登録文字列を取得する
 *
 *  return  : wstring
 */
wstring FString::getWString() {
    return wchar_string;
}

/*
 *  登録文字列を取得する
 *
 *  return  : wchar_t*
 */
const wchar_t *FString::getWCharBuff() {
    return wchar_string.c_str();
}

/*
 *  Localeを設定する
 *
 *  param   : Locale(String)
 */
void FString::setLocale(String loc) {
    own_loc = loc;
}

/*
 *  stringをwstringに変換する
 *
 *  param   : オリジナル文字列(string)
 *
 *  return  : wstring
 */
wstring FString::strToWstr(string str) {
    wstring result;
    if (!str.empty()) {
        _TCHAR *org_loc = _tcsdup(_tsetlocale(LC_ALL, NULL));
        _tsetlocale(LC_ALL, own_loc.c_str());
        size_t from_len = str.length();
        char *from_value = (char *)calloc(from_len+1, sizeof(char));
        memcpy(from_value, str.c_str(), from_len);
        size_t to_len = mbstowcs(NULL, from_value, from_len);
        wchar_t *buffer = (wchar_t *)calloc(to_len+1, sizeof(wchar_t));
        mbstowcs(buffer, str.c_str(), from_len);
        free(from_value);
        result = wstring(buffer);
        free(buffer);
        _tsetlocale(LC_ALL, org_loc);
        free(org_loc);
    }
    return result;
}

/*
 *  wstringをstringに変換する
 *
 *  param   : オリジナル文字列(wstring)
 *
 *  return  : string
 */
string FString::wstrToStr(wstring wstr) {
    string result;
    if (!wstr.empty()) {
        _TCHAR *org_loc = _tcsdup(_tsetlocale(LC_ALL, NULL));
        _tsetlocale(LC_ALL, own_loc.c_str());
        size_t from_len = wstr.length();
        wchar_t *from_value = (wchar_t *)calloc(from_len+1, sizeof(wchar_t));
        memcpy(from_value, wstr.c_str(), from_len*sizeof(wchar_t));
        size_t to_len = wcstombs(NULL, from_value, from_len);
        char *buffer = (char *)calloc(to_len+1, sizeof(char));
        /*
         *  本当なら"wcstombs(buffer, from_value, from_len);"で事が足りるはずなんですが、
         *  なぜか、ロケールに"japanese"を指定して第一パラメータに値を指定すると途中まで
         *  しか変換されないケースがあるので、あえて一文字づつ変換させています
         */
        size_t pos = 0;
        for (size_t i = 0; i < from_len; i++) {
            int len = wctomb(&buffer[pos], from_value[i]);
            if (len < 0)
                break;
            pos += len;
        }
        free(from_value);
        result = string(buffer);
        free(buffer);
        _tsetlocale(LC_ALL, org_loc);
        free(org_loc);
    }
    return result;
}

テストで、

int _tmain(int argc, _TCHAR* argv[])
{
    wcout.imbue(locale("japanese"));
    BString::setLocale(_LOCALE_JAPANESE);
    BString mbs = BString("漢字のデータ");
    const wchar_t *mbs_buff = (const wchar_t*)mbs;
    wstring wcs_ref = mbs;
    mbs += L"−追加";
    wstring wcs_val = mbs.getWString();
    wcout << L"WString : " << wcs_val << endl;
    BString wcs = BString(wcs_val);
    string mbs_val = wcs.getString();
    cout << "String : " << mbs_val << endl;
    return 0;
}

のロジックが動作する事を確認しました。


最初に提示した以前のロジックとの変更点としては、

/*
 *  指定したインターフェースに設定されたアドレスを取得する
 *  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 *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr)));
ここ==>         netmask = String((_TCHAR *)FString(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;
}

になります。
テストしましたが、何とか動作しています。


余談で、FString.cppのロジック中にある、"string FString::wstrToStr(wstring wstr)"メソッドのロジック中にもコメントしたんですが、localeを"japanese"にすると、

    size_t to_len = wcstombs(NULL, from_value, from_len);

のロジックは問題なく動作して、from_valueが"漢字のデータ"を設定した時のto_lenの値は12になるのですが、

    char *buffer = (char *)calloc(to_len+1, sizeof(char));
    size_t to_len = wcstombs(buffer, from_value, from_len);

のロジックでは、from_valueが"漢字のデータ"を設定した時のto_lenの値が6になり、bufferには"漢字の"までしか変換されませんでした。
色々と試してみたのですが、原因が特定できなかったので、

    char *buffer = (char *)calloc(to_len+1, sizeof(char));
    size_t pos = 0;
    for (size_t i = 0; i < from_len; i++) {
        int len = wctomb(&buffer[pos], from_value[i]);
        if (len < 0)
            break;
        pos += len;
    }

として回避しています。