Unicodeを学習しながら書いているので、なかなか難しいものがありますが、現時点でのCSTRクラスのワイド文字版(Windowsは2バイト文字WCHARを使用するのでUTF-16)を紹介します。

 

前回も書きましたが、最も基礎的な変更点は以下の通り。

(1)文字列は8バイトのchar型から16バイトWCHAR(wchar_t)型に替える。その為にwchar.hをインクルードする。
(2)今まで使っていた8バイト用のWin32API関数や構造体は(ワイド文字用のものが必ずあるので)ワイド文字用のものを使う。探す際のヒントは、以下の通りです。
 ①(関数名)→(関数名)W や"str"等の文字列を意味する文字が入っている関数名であれば"strW"に置き換えて検索する。
 ②(構造体名)→(構造体名)Wや①と同様に文字列を彷彿させる名称にWを付加したものを検索する。
(3)文字列定数は"なんちゃら"→L"なんちゃら"とする。単一文字の場合も'〇'→L'〇'とする。
(4)文字列のヌル終端はNULL(8バイト)でなく、明示的に16バイトのL'\0'とする。

しかし、CSTRはSJISの文字列を扱い、ファイル読書きもするので更に次の点が要点になります。

(5)SJISでは全角文字が2バイトでShift-JISの第1バイト(0x81-0x9Fまたは0xE0-0xEF)があれば例外処理をしていましたが、それがほぼ(注1)不要となります。

(6)ファイルはWCHAR(UTF-16)ベースでの読書きとなります。(注2)ファイルを書く際には必ずBOMが必要となります。また読む際にはBOMを確認して、(CSTRが文字列テキストであろうが何だろうが一応読み込むことから)UTF-16テキストであればBOM以降を、それ以外であれば全てを読むようにしました。(従ってUTF-32のテキストではBOMの0xFF、0xFEの次にいきなりL'\0'(2バイトNULL)が来ることになります。)

注1:サロゲートペア文字はプログラミング対象テキストではめったに出てこないので...

注2:少なくともUTF-8の読み書きもしたいな、とも考えましたが、テキストコンバージョンはCSTRの仕様外ですし、結構めんどいので今回はストレートにUTF-16だけにしています。しかし、コーディング上はUTF-32、UTF-8の読み込みの可能性を意識していますので参考にしてください。また、現実のUTF-8、UTF-16の変換を実装される場合はこの記事が大変参考になります。(経緯や評価で、前回の【無駄話】が間違っていなかったことを示していますね。)

 

以下は上記(1)~(4)は解説を省略し、(5)、(6)のみ触れます。

 

【CSTRW.h】

//-------------------------
//         CSTRW.h
//   Copyright April 2022
//     By K. Yoshida
//      Version 1.0A
//注:使用にはwchar.hが必要
//-------------------------
#ifndef    _CSTRW_
#define _CSTRW_

class CSTRW {

    WCHAR*    m_str;

public:
    CSTRW();                                    //コンストラクター(無)
    CSTRW(const WCHAR*);                        //コンストラクター(文字列)
    CSTRW(const CSTRW&);                        //コンストラクター(CSTRW)
    CSTRW(const long);                            //コンストラクター(整数)
    ~CSTRW();                                    //デストラクター
    CSTRW& operator = (const CSTRW&);            //operator = (B) ---> = B;
    CSTRW& operator = (const long);                //operator = (B) ---> = B;
    WCHAR* ToChar() {return m_str;}                //文字列ポインターとして返す
    void Print();                                //m_strを印字する
    friend CSTRW operator + (const CSTRW&, const CSTRW&);    //A operator + (B)  --->= A + B;
    friend bool operator == (const CSTRW&, const CSTRW&);    //A operator == (B) --->= A == B;
    friend bool operator == (const CSTRW&, const WCHAR*);    //A operator == (B) --->= A == B;
    friend bool operator != (const CSTRW&, const CSTRW&);    //A operator == (B) --->= A != B;
    friend bool operator != (const CSTRW&, const WCHAR*);    //A operator == (B) --->= A != B;
    bool FromFile(WCHAR*, WCHAR*, HWND);        //ファイルからの読込
    bool FromFile(WCHAR*);                        //ファイルからの読込
    bool ToFile(WCHAR* , WCHAR*, HWND);            //ファイルへの書き込み
    bool ToFile(WCHAR*);                        //ファイルへの書き込み
    bool Next(CSTRW&);                            //m_strの単語を切り取り出す
    bool GetLine(CSTRW&);                        //この行末まで進む
    bool NextLine();                            //次の行頭まで進む
    WCHAR* Find(WCHAR*, WCHAR*);                //文字列の検索
};

//コンストラクター(無)
CSTRW::CSTRW() {

    m_str = new WCHAR[1];
    *m_str = L'\0';
}

//コンストラクター(文字列)
CSTRW::CSTRW(const WCHAR* string) {

    m_str = new WCHAR[lstrlenW(string) + 1];
    lstrcpyW(m_str, string);
}

//コンストラクター(CSTRW)
CSTRW::CSTRW(const CSTRW& cst) {

    m_str = new WCHAR[lstrlenW(cst.m_str) + 1];
    lstrcpyW(m_str, cst.m_str);
}

//コンストラクター(整数)
CSTRW::CSTRW(const long i) {

    WCHAR sbuff[MAX_PATH / 8];
    wsprintfW(sbuff, L"%d", i);
    m_str = new WCHAR[lstrlenW(sbuff) + 1];
    lstrcpyW(m_str, sbuff);
}

//デストラクター
CSTRW::~CSTRW() {

    delete [] m_str;
}

//CSTRWの代入
CSTRW& CSTRW::operator = (const CSTRW& cst) {

    if(&cst != this) {    //You can't copy yourself
        delete [] m_str;
        m_str = new WCHAR[lstrlenW(cst.m_str) + 1];
        lstrcpyW(m_str, cst.m_str);
    }
    return *this;
}

//整数の代入
CSTRW& CSTRW::operator = (const long i) {

    WCHAR sbuff[MAX_PATH / 8];
    wsprintfW(sbuff, L"%d", i);
    m_str = new WCHAR[lstrlenW(sbuff) + 1];
    lstrcpyW(m_str, sbuff);
    return *this;
}

//m_strを印字する
void CSTRW::Print() {

    MessageBoxW(NULL, m_str, L"CSTRW Message", MB_OK);
}

//追加演算子の定義
CSTRW operator + (const CSTRW& cst1, const CSTRW& cst2) {

    CSTRW strw;
    delete [] strw.m_str;
    strw.m_str = new WCHAR[lstrlenW(cst1.m_str) + lstrlenW(cst2.m_str) + 1];
    lstrcpyW(strw.m_str, cst1.m_str);
    lstrcatW(strw.m_str, cst2.m_str);
    return strw;
}

//比較演算子の定義(1)
bool operator == (const CSTRW& cst1, const CSTRW& cst2) {

    return !lstrcmpW(cst1.m_str, cst2.m_str);
}

//比較演算子の定義(2)
bool operator == (const CSTRW& cst, const WCHAR* str) {

    return !lstrcmpW(cst.m_str, str);
}

//比較演算子の定義(3)
bool operator != (const CSTRW& cst1, const CSTRW& cst2) {

    return lstrcmpW(cst1.m_str, cst2.m_str);
}

//比較演算子の定義(4)
bool operator != (const CSTRW& cst, const WCHAR* str) {

    return lstrcmpW(cst.m_str, str);
}

//ファイルからの読込
bool CSTRW::FromFile(WCHAR* Filter, WCHAR* FilePath, HWND hWnd = NULL) {

    WCHAR FileName[MAX_PATH];
    WCHAR file_path[MAX_PATH];
    lstrcpyW(file_path, FilePath);    //Default Path
    OPENFILENAMEW ofn;                //オープンファイルダイアログ構造体
    //オープンファイルダイアログでファイル名を取得する
    FileName[0] = NULL;
    ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
    ofn.lStructSize        = sizeof(ofn);
    ofn.hwndOwner        = hWnd;
    ofn.lpstrFilter        = Filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile        = FileName;
    ofn.nMaxFile        = MAX_PATH;
    if(!*FilePath)                    //規定値のNULLであれば現在のディレクトリー
        GetCurrentDirectoryW(MAX_PATH, file_path);
    else
        lstrcpyW(file_path, FilePath);
    ofn.lpstrInitialDir = file_path;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    if(!GetOpenFileNameW(&ofn))    return FALSE;        //読み込み結果
    //ファイルを読み込む
    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
    if(hFile != INVALID_HANDLE_VALUE) {                //オープンできたか
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF) {                //-1は(0xFFFFFFFF)エラー
            LPBYTE Buff;                            //読み込みバッファ用ポインター
            Buff = new BYTE[dwFileSize];
//(解説:直接WCHARのm_strに読み込ませず、一旦バイト単位のバッファ―に読み込ませます。)

            DWORD dwRead;
            if(ReadFile(hFile, Buff, dwFileSize, &dwRead, NULL)) {
                bSuccess = TRUE; //読み込み成功
            }
            if(Buff[0] == 0xFF && Buff[1] == 0xFE) {//UTF-16、UTF-32のBOM

//(解説:ここでまず頭の2バイトをチェックし、UTF-16とUTF-32が使う'FF FE'があるか否か、確認します。)

               if(Buff[2] & Buff[3]) {                //いずれもNULLではない

//(解説:更にリトルエンディアンの2バイトの'0 0'がなく、UTF-32でないことを確認します。)

                    delete [] m_str;                //元の文字列を廃棄して
                    //BOM以降の読み込んだデータを入れられるようにする
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR)];

//(解説:CSTR版では終端用の+1がありますが、BOMを差し引くとこうなります。)

                    CopyMemory(m_str, Buff + 2, dwFileSize - 2);

//(解説:BYTE配列をWCHAR配列へ(両方がオーバーラップしていないので)コピーします。)

                    //念のため、バッファの終端にNULLを置く
                    m_str[(dwFileSize - 2) / sizeof(WCHAR) - 1] = L'\0';
                }
                //else {    //UTF-32の処理

//(解説:UTF-32の場合にはここに変換処理を書きます。)

                //}            }
            //else if(Buff[0] == 0xEF && Buff[1] == 0x0xBB && Buff[1] == 0xBF) {    //UTF-8の処理

//(解説:UTF-8の場合にはここに変換処理を書きます。)

            //}
            else {                                    //UTF-16(32、8)ではない場合

//(解説:それ以外の場合は「丸のみ」します。)

                m_str = new WCHAR[dwFileSize / sizeof(WCHAR) + 1];
                CopyMemory(m_str, Buff, dwFileSize);
                //念のため、バッファの終端にNULLを置く
                m_str[dwFileSize / sizeof(WCHAR)] = L'\0';
            }
            delete [] Buff;                            //読み込みバッファ解放
        }
        CloseHandle(hFile);                            //ファイルのクローズ
    }
    return bSuccess;
}

//ファイルからの読込
bool CSTRW::FromFile(WCHAR* FileName) {

    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
    if(hFile != INVALID_HANDLE_VALUE) {                //オープンできたか
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF) {                //-1は(0xFFFFFFFF)エラー
            LPBYTE Buff;                            //読み込みバッファ用ポインター
            Buff = new BYTE[dwFileSize];
            DWORD dwRead;
            if(ReadFile(hFile, Buff, dwFileSize, &dwRead, NULL)) {
                bSuccess = TRUE; //読み込み成功
            }
            if(Buff[0] == 0xFF && Buff[1] == 0xFE) {//UTF-16、UTF-32のBOM
                if(Buff[2] & Buff[3]) {                //いずれもNULLではない
                    delete [] m_str;                //元の文字列を廃棄して
                    //BOM以降の読み込んだデータを入れられるようにする
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR)];
                    CopyMemory(m_str, Buff + 2, dwFileSize - 2);
                    //念のため、バッファの終端にNULLを置く
                    m_str[(dwFileSize - 2) / sizeof(WCHAR) - 1] = L'\0';
                }
                //else {    //UTF-32の処理
                //}
            }
            //else if(Buff[0] == 0xEF && Buff[1] == 0x0xBB && Buff[1] == 0xBF) {    //UTF-8の処理
            //}
            else {                                    //UTF-16(32、8)ではない場合
                m_str = new WCHAR[dwFileSize / sizeof(WCHAR) + 1];
                CopyMemory(m_str, Buff, dwFileSize);
                //念のため、バッファの終端にNULLを置く
                m_str[dwFileSize / sizeof(WCHAR)] = L'\0';
            }
            delete [] Buff;                            //読み込みバッファ解放
        }
        CloseHandle(hFile);                            //ファイルのクローズ
    }
    return bSuccess;
}

//(解説:同じ処理です。)


//ファイルへの書き込み(規定値UTF-16 リトルエンディアン)
bool CSTRW::ToFile(WCHAR* Filter, WCHAR* DefExt, HWND hWnd = NULL) {

    WCHAR FileName[MAX_PATH];
    WCHAR file_path[MAX_PATH];
    OPENFILENAMEW ofn;                //オープンファイルダイアログ構造体
    //オープンファイルダイアログでファイル名を取得する
    ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
    FileName[0] = NULL;
    ofn.lStructSize        = sizeof(ofn);
    ofn.hwndOwner        = hWnd;
    ofn.lpstrFilter        = Filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile        = FileName;
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrInitialDir = L".";
    ofn.lpstrDefExt        = DefExt;    //デフォルトの拡張子
    ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
                OFN_OVERWRITEPROMPT;
    if(!GetSaveFileNameW(&ofn))    return FALSE;;
    //ファイルを書き込む
    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_WRITE, NULL, NULL,    //ファイルを書込みでオープン
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        //バッファから文字列長取得し、NULL終端も加算
        DWORD dwTextLength = (lstrlenW(m_str) + 1) * sizeof(WCHAR);
        DWORD dwWritten;
        //BOMの書き込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
        BYTE BOM[2] = {0xFF, 0xFE};
        if(WriteFile(hFile, BOM, 2, &dwWritten, NULL)) {

//(解説:まずUTF-16のBOMを書き込みます。)

            WriteFile(hFile, m_str, dwTextLength, &dwWritten, NULL);
            bSuccess = TRUE;    //書込み成功
        }
        CloseHandle(hFile);            //ファイルのクローズ
    }
    return bSuccess;
}

//ファイルへの書き込み(規定値UTF-16 リトルエンディアン)
bool CSTRW::ToFile(WCHAR* FileName) {

    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_WRITE, NULL, NULL,    //ファイルを書込みでオープン
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        //バッファから文字列長取得し、NULL終端も加算
        DWORD dwTextLength = (lstrlenW(m_str) + 1) * sizeof(WCHAR);
        DWORD dwWritten;
        //BOMの書き込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
        BYTE BOM[2] = {0xFF, 0xFE};
        if(WriteFile(hFile, BOM, 2, &dwWritten, NULL)) {
            WriteFile(hFile, m_str, dwTextLength, &dwWritten, NULL);
            bSuccess = TRUE;    //書込み成功
        }
        CloseHandle(hFile);            //ファイルのクローズ
    }
    return bSuccess;
}

//(解説:同じ処理です。)


//m_strの単語を切り取り出す
bool CSTRW::Next(CSTRW& destination) {

    CSTRW str;
    delete [] str.m_str;
    str.m_str = new WCHAR[lstrlenW(m_str) + 1];
    WCHAR* ptr = m_str;
    WCHAR* des = str.m_str;
    bool Terminate = TRUE;
    //スペース文字はスキップする
    while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {

//(解説:L' 'というのは全角スペースです。CSTRの時はここの処理が2バイトチェックでした。)

            ptr++;                        //スキップスペース文字
    }
    //非スペース文字を調べる
    while(!iswspace(*ptr)) {            //スペース文字でない
        if(!*ptr) {                        //NULL終端ならFALSEを返す
            Terminate = FALSE;
            break;
        }
        else if(*ptr == L' ') {
            break;                        //全角" "なら終了
        }
        else if(*ptr == L',') {
            ptr++;                        //','をスキップして次の文字まで進む
            break;                        //空白文字扱い
        }
        else if(*ptr == L'"') {
            ptr++;                        //'"'をスキップして中身だけ取り出す
            while(*ptr != L'"') {        //'"'なら'"'まで進む
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                *des = *ptr;
                ptr++;    des++;
            }
            ptr++;                        //ソースポインターは次の文字へ進める
            break;                        //ループを抜けてデスティネーションはNULLが入る
        }
        else if(*ptr == L'/' && *(ptr + 1) == L'*') {
            ptr++;    ptr++;                //"/*~*/"コメント
            while(!(*ptr == L'*' && *(ptr + 1) == L'/')) {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;
            }
            ptr++;    ptr++;
            //再度スペース文字をスキップする
            while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;                //スキップスペース文字
            }
            continue;
        }
        else if(*ptr == L'/' && *(ptr + 1) == L'/') {
            while(*ptr != L'\n') {        //"//"コメント
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;
            }
            while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;                //スキップスペース文字
            }
            continue;
        }
        else {
            *des = *ptr;
        }
        ptr++;    des++;
    }
    *des = L'\0';

    destination = str;            //destinationに切り取った文字列を代入
    str = ptr;                    //strにm_strより進んだptr以降の文字列を代入
    delete [] m_str;
    m_str = new WCHAR[lstrlenW(str.m_str) + 1];
    lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    if(*str.m_str || *destination.m_str)
        Terminate = TRUE;        //残がNULLだけでも切り取った文字列があれば続ける
    else
        Terminate = FALSE;        //NULLだけなら終わり
    return Terminate;            //成功・失敗フラグを返す
}

//この行末まで進む
bool CSTRW::GetLine(CSTRW& destination) {

    bool Terminate = TRUE;
    WCHAR* ptr = m_str;
    WCHAR* des = ptr;
    while(*des && *des != L'\n')
        des++;
    if(!*des) {
        destination = L"";
        Terminate = FALSE;    //文字列の終了サイン
    }
    else {
        *des = L'\0'; des++;                //NULL終端させ、一つ進める
        delete [] destination.m_str;
        destination.m_str = new WCHAR[des - ptr + 1];
        lstrcpyW(destination.m_str, ptr);
        destination.m_str[des - ptr - 1] = L'\n';    //NULLにした'\n'を付加
        destination.m_str[des - ptr] = L'\0';        //NULL終端
    }
    if(Terminate) {                    //まだ文字列があれば残りを入れる
        CSTRW str = des;
        delete [] m_str;
        m_str = new WCHAR[lstrlenW(str.m_str) + 1];
        lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    }
    if(*destination.m_str)            //NULLに出会ったが、
        Terminate = TRUE;            //切り取った文字列があれば続ける
    else
        Terminate = FALSE;            //NULLだけなら終わり
    return Terminate;                //成功・失敗フラグを返す
}

//次の行頭まで進む
bool CSTRW::NextLine() {

    WCHAR* ptr = m_str;
    while(*ptr != L'\n') {        //改行コードまで進む
        if(!*ptr) {                //途中でNULL終端したならFALSEを返す
            return FALSE;
        }
        ptr++;
    }
    ptr++;
    CSTRW str = ptr;
    delete [] m_str;
    m_str = new WCHAR[lstrlenW(str.m_str) + 1];
    lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    return TRUE;                //成功・失敗フラグを返す
}

//文字列の検索
WCHAR* CSTRW::Find(WCHAR* ToFind, WCHAR* sp = 0) {

    if(!sp)
        sp = m_str;
    return StrStrW(sp, ToFind);
}

//(解説:実はここのコードをCSTRも変更しました。一回目の検索の際の戻り値+1を第二引数に入れて二回目以降の検索を行います。)

#endif

 

まぁ、実際には(無知の為に)2度ほどファイル読書きの部分を書き直しましたし、一旦UTF-8ファイルの読み込みのみをコーディングしましたが、矢張り「中途半端はやめよう」ということで、飽くまでも「WCHARを使ったCSTRのワイド文字版」に徹しています。

【UTF-16読込】

ToFileで書きだしたものを読み込んでいます。

【UTF-8読込】

当たり前ですが、「丸のみ」なので文字化けします。