UTF-8エンコードについて

※開発環境は、Windows2000sp4/IE6sp1/VC++6.0sp6/ATL3.0/WTL7.0

ちょっとShiftJISコードの文字列をUTF-8コードに変換しようと思って調べたのですが、MSDNを見てもよく分からないし、 Webで検索してみても、Unicode への変換のサンプルコードはあるものの、ShiftJISからUTF-8への変換方法について詳しいものが見つかりませんでした。 独自のコードやANSI固定環境用のコードはありましたが、汎用性のあるサンプルコードを見つけることができませんでした。

そこで、覚書です。LPCTSTR のShiftJIS文字列を LPTSTR のUTF-8にエンコードするコードです。

一般的な教科書的コードと、MSDNとWTL7.0のソースを参考にして作った高能率のコードを記載しておきます。

ちなみにDonutでは後者のコードを使用しています。

コード1:率直なコード

    // ShiftJISコード文字列を 一度 Unicode に変換してから UFT-8 へと変換し、そのデータを返す
    // 内部でメモリ確保しているため、使用後は外部でメモリを解放する必要がある
    LPSTR SJIStoUTF8(LPCSTR lpText)
    {
        if(lpText == NULL || *lpText == '\0'){
            return NULL;
        }

        // (1) ShiftJIS 文字列を Unicode に変換
        //     ワイド文字列変換に必要な文字数分のバッファを確保
        const int cchWideChar = ::MultiByteToWideChar(CP_ACP, 0, lpText, -1, NULL, 0);
        LPWSTR lpw = new WCHAR [cchWideChar];
        if(lpw == NULL){
            return NULL;
        }
        *lpw = L'\0';

        //     上記で求めたワイド文字列バッファを用いて Unicode に変換
        const int nUnicodeCount = ::MultiByteToWideChar(CP_ACP, 0, lpText, -1, lpw, cchWideChar);
        if( nUnicodeCount <= 0 ){
            delete[] lpw;
            return NULL;
        }

        // (2) Unicode 文字列を UTF-8 に変換
        //     マルチバイト文字列変換に必要な文字数分のバッファを確保
        const int cchMultiByte = ::WideCharToMultiByte(CP_UTF8, 0, lpw, -1, NULL, 0, NULL, NULL);
        LPSTR lpa = new CHAR [cchMultiByte];
        if(lpa == NULL){
            delete[] lpw;
            return NULL;
        }
        *lpa = '\0';

        //     上記で求めたマルチバイト文字列バッファを用いて UTF-8 に変換
        const int nMultiCount = ::WideCharToMultiByte(CP_UTF8, 0, lpw, -1, lpa, cchMultiByte, NULL, NULL);
        if( nMultiCount <= 0 ){
            delete[] lpw;
            delete[] lpa;
            return NULL;
        }

        // (3) 変換成功。変換に使った一時バッファを解放
        delete[] lpw;

        return lpa;
    }

サンプル1:率直なコードの使用例

    int main()
    {
        LPSTR lpa = SJIStoUTF8(_T("明日は晴れるかな?"));
        if(lpa)
        {
            std::cout << lpa << std::endl;
            delete[] lpa;
        }
        return 0;
    }

上記の方法では、下記の理由により、速度が遅くなったり煩雑になります。

  1. 変換後の文字数を計測後、再度変換を行なっていること
  2. newでメモリをヒープに確保している(* 実装依存ですが、筆者の環境では、malloc()でメモリ管理されていました。)

そこで、下記の方法によって高速化します。また、USES_CONVERSION を使うことによって さらに楽ができます。

  1. 文字数は必要充分サイズを自前で計算して確保 → API 呼び出し回数の半減
  2. _alloca()でメモリをスタックに確保 → 高速かつ確保したメモリはブロックを抜けると自動的に解放

コード2:高速なコード

// ATL3.0 [AtlConv.h] USES_CONVERSION, T2W
// WTL7.0 [AtlMisc.h] WTL::CString

    BOOL SJIStoUTF8(CString& r_strText)
    {
        if(r_strText.IsEmpty()){
            return FALSE; // no data
        }

        USES_CONVERSION;

        // (1) ShiftJIS 文字列を Unicode に変換
        LPWSTR lpw = T2W(static_cast<LPCTSTR>(r_strText));

        // (2) Unicode 文字列を UTF-8 に変換
        //     UTF-8 コードは 1〜3 バイトで表現されることから、
        //     変換元文字数(終端文字含)の3倍のバッファを用意すれば充分
        const int cchMultiByte = (lstrlenW(lpw) + 1) * 3;
        LPSTR lpa = reinterpret_cast<LPSTR>(_alloca(cchMultiByte));
        *lpa = '\0';
        const int nBytes = ::WideCharToMultiByte(CP_UTF8, 0, lpw, -1, lpa, cchMultiByte, NULL, NULL);
        ATLASSERT( nBytes > 0 );

        // (3) 変換成功
        r_strText = lpa;

        return TRUE;
    }

サンプル2:高速なコードの使用例

    int main()
    {
        CString strText(_T("明日は晴れるかな?"));
        if(SJIStoUTF8(strText))
        {
            std::cout << strText << std::endl;
        }
        return 0;
    }

更新履歴

2005/02/18
コード1:率直なコードで、malloc(), free()と書いていたものをC++らしく、new, delete に書き換えた。
使用例としてサンプルコードを追加。← 標準出力に出力しているけど、文字化けして読めないでしょう。ま、サンプルっつーことで。


[HOME]-[RTL]
Copyright© 2004-2006 RAPT.