CEditView 雑記

Last Updated: Nov.24,2006
使用しているコンパイラは、Microsoft Visual C++ 6.0 です。これ以外のバージョンでは記述と食い違うことがあるかも知れません。

8. Unicode アプリケーションにおける Shift-JIS ファイルの読み書き

CEditView クラスは、シンボル _UNICODE を定義してビルドすると Shift-JIS ファイルの読み書きが出来なくなります。これが不都合な場合には自分で実装する必要があります。

実装例1

まず、CEditView の派生クラスで次の 3 つのメンバ関数を宣言します。

public:
#ifdef _UNICODE
    void ReadFromArchiveA(CArchive& ar);
    void WriteToArchiveA(CArchive& ar);
    void SerializeA(CArchive& ar);
#endif

いろいろな実装法があると思いますが、以下の例では Win32 API の MultiByteToWideChar, WideCharToMultiByte 関数を使ってみました。この他、ATL マクロの A2W, W2A を使う方法もあります。

#ifdef _UNICODE
void CMyView::ReadFromArchiveA(CArchive& ar)
{
    ASSERT_VALID(this);
    CFile* pFile = ar.GetFile();
    ASSERT(pFile->GetPosition() == 0);
    DWORD nFileSize = pFile->GetLength();
    char *lpszA = new char[nFileSize+1];
    if (lpszA == NULL)
        AfxThrowMemoryException();
    if (ar.Read(lpszA, nFileSize) != nFileSize)
    {
        delete [] lpszA;
        AfxThrowArchiveException(CArchiveException::endOfFile);
    }
    lpszA[nFileSize] = '\0';
    int nLen = ::MultiByteToWideChar(CP_ACP, 0, lpszA, -1, NULL, 0);
    if ((UINT)nLen > nMaxSize)
    {
        AfxMessageBox(AFX_IDP_FILE_TOO_LARGE);
        delete [] lpszA;
        AfxThrowUserException();
    }
    LPWSTR lpszW = new WCHAR[nLen+1];
    ::MultiByteToWideChar(CP_ACP, 0, lpszA, -1, lpszW, nLen);
    SetWindowText(lpszW);
    delete [] lpszW;
    delete [] lpszA;
    ASSERT_VALID(this);
}

void CMyView::WriteToArchiveA(CArchive& ar)
{
    ASSERT_VALID(this);
    LPCWSTR lpszW = LockBuffer();
    ASSERT(lpszW != NULL);
    int nLen = ::WideCharToMultiByte(CP_ACP, 0, lpszW, -1, NULL, 0, NULL, NULL);
    char *lpszA = new char[nLen+1];
    ::WideCharToMultiByte(CP_ACP, 0, lpszW, -1, lpszA, nLen, NULL, NULL);
    TRY
    {
        ar.Write(lpszA, strlen(lpszA));
    }
    CATCH_ALL(e)
    {
        UnlockBuffer();
        delete [] lpszA;
        THROW_LAST();
    }
    END_CATCH_ALL
    UnlockBuffer();
    delete [] lpszA;
    ASSERT_VALID(this);
}

void CMyView::SerializeA(CArchive& ar)
{
    ASSERT_VALID(this);
    if (ar.IsStoring())
    {
        WriteToArchiveA(ar);
    }
    else
    {
        ReadFromArchiveA(ar);
    }
    ASSERT_VALID(this);
}
#endif

呼び出しは、CDocument の派生クラスの Serialize 関数を以下のように書き直します。

void CMyDoc::Serialize(CArchive& ar)
{
#ifdef _UNICODE
    ((CMyView*)m_viewList.GetHead())->SerializeA(ar);
#else
    ((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
#endif
}

実装例2

正攻法ではないのですが、CStdioFile クラスをロケールを切り替えて使うというちょっと面白い方法があります。CStdioFile クラスのソースをみると、ReadString, WriteString 関数はそれぞれ C ランタイムの fgetws, fputws 関数を使って実装されており、現在のロケールの LC_CTYPE カテゴリに依存して動作することが分かります。

BOOL CMyDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
    CStdioFile f;
    CFileException fe;
    CString sLine, sText;
    if (!f.Open(lpszPathName,
                CFile::modeRead | CFile::shareDenyWrite,
                &fe))
    {
        ReportSaveLoadException(lpszPathName, &fe,
            FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
        return FALSE;
    }
    DeleteContents();
    SetModifiedFlag();
    CWaitCursor wait;
    if (f.GetLength() != 0)
    {
        /* m_sLocale = _T(".932"); */
        _tsetlocale(LC_CTYPE, m_sLocale);
        while (f.ReadString(sLine))
            sText += sLine + _T("\r\n");
        f.Close();
        _tsetlocale(LC_CTYPE, _T("C"));
    }
    SetModifiedFlag(FALSE);
    ((CEditView*)m_viewList.GetHead())->SetWindowText(sText);
    return TRUE;
}

BOOL CMyDoc::OnSaveDocument(LPCTSTR lpszPathName) 
{
    CStdioFile f;
    CFileException fe;
    CString sText;
    if (!f.Open(lpszPathName, 
                CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive,
                &fe))
    {
        ReportSaveLoadException(lpszPathName, &fe,
            TRUE, AFX_IDP_INVALID_FILENAME);
        return FALSE;
    }
    CWaitCursor wait;
    /* m_sLocale = _T(".932"); */
    _tsetlocale(LC_CTYPE, m_sLocale);
    ((CEditView*)m_viewList.GetHead())->GetWindowText(sText);
    sText.Replace(_T("\r\n"), _T("\n"));
    f.WriteString(sText);
    f.Close();
    _tsetlocale(LC_CTYPE, _T("C"));
    SetModifiedFlag(FALSE);
    return TRUE;
}

こちらは、CDocument の派生クラス CMyDoc に実装してみました。ファイルの末尾に勝手に改行を挿入してしまうなど、あまり厳密ではありません。なお、使用する場合には、<locale.h> をインクルードする必要があります。

【参考】
Microsoft Visual C++ 6.0 で Unicode アプリケーションを作成する場合の手順
  1. 「C/C++」 → 「一般」 → 「プリプロセッサの定義」の _MBCS を _UNICODE に変更。

  2. stdafx.h に以下のコードを追加。
    #ifdef _UNICODE
        #pragma comment(linker, "/ENTRY:wWinMainCRTStartup")
    #endif
 

Back Copyright (c) 2006 AOK. All Rights Reserved.