TN059: MFC の MBCS/Unicode 変換マクロの使用

Note

次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。

このノートでは、AFXPRIV.H で定義されている MBCS と Unicode の変換にマクロを使用する方法について説明します。 これらのマクロは、アプリケーションで OLE API を直接処理する場合、または何かの理由で Unicode と MBCS の間での変換が必要な場合に最も便利です。

概要

MFC 3.x では、OLE インターフェイスを呼び出す際に、特殊な DLL (MFCANS32.DLL) を使用して、Unicode と MBCS の間で自動的に変換を行っていました。 この DLL はほぼ透過的なレイヤーであったため、OLE API とインターフェイスが常に Unicode (Macintosh を除く) である場合でも、まるで MBCS であるかのように OLE アプリケーションを記述することができました。 このレイヤーは使い勝手がよく、アプリケーションを Win16 から Win32 (MFC、Microsoft Word、Microsoft Excel、VBA は、このテクノロジを使用する Microsoft アプリケーションのごく一部にすぎません) に迅速に移植できる一方で、パフォーマンスには大きな影響を与える場合がありました。 このため、MFC 4.x ではこの DLL は使用せず、代わりに Unicode OLE インターフェイスと直接やり取りします。 MFC では、これを行うために、OLE インターフェイスを呼び出す際に Unicode から MBCS への変換を行う必要があります。また、OLE インターフェイスを実装する際も多くの場合、Unicode から MBCS への変換が必要になります。 この変換を効率的かつ容易に処理するために、多数のマクロが作成されました。

このような一連のマクロを作成する際の最大の障害の 1 つが、メモリ割り当てです。 文字列を適切な位置に変換できないため、変換された結果を保持する新しいメモリを割り当てる必要があります。 これを行おうとした場合、次のようなコードが考えられます。

// we want to convert an MBCS string in lpszA
int nLen = MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    NULL,
    NULL);

LPWSTR lpszW = new WCHAR[nLen];
MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    lpszW,
    nLen);

// use it to call OLE here
pI->SomeFunctionThatNeedsUnicode(lpszW);

// free the string
delete[] lpszW;

このアプローチには、多くの問題があります。 主な問題は、記述、テスト、デバッグを行わなければならないコードが多いという点です。 単純な関数呼び出しが、はるかに複雑なコードになっています。 さらに、これを行う際に、ランタイムのオーバーヘッドが大幅に増えます。 メモリをヒープに割り当て、変換を行うたび解放する必要があります。 最後に、(この変換が不要な) Unicode ビルドと Macintosh ビルドの場合、上記のコードに適切な #ifdefs を追加する必要があります。

これで考え出された解決策は、1) さまざまなプラットフォーム間の違いをマスクし、2) 効率的なメモリ割り当てスキームを使用し、3) 既存のソース コードに簡単に挿入できる、いくつかのマクロを作成することでした。 次の例に、定義の 1 つを示します。

#define A2W(lpa) (\
((LPCSTR)lpa == NULL) NULL : (\
    _convert = (strnlen(lpa)+1),\
    AfxA2WHelper((LPWSTR) alloca(_convert*2),
    lpa,
    _convert)\)\)

上記のコードの代わりにこのマクロを使用すると、はるかにシンプルになります。

// use it to call OLE here
USES_CONVERSION;
pI->SomeFunctionThatNeedsUnicode(T2OLE(lpszA));

変換が必要な場合に追加の呼び出しがあるものの、マクロの使用はシンプルで効果的です。

各マクロの実装では、_alloca() 関数を使用して、ヒープではなくスタックからメモリを割り当てます。 スタックからメモリを割り当てると、メモリをヒープに割り当てるよりもはるかに高速であり、関数が終了したときにメモリが自動的に解放されます。 さらに、マクロでは、MultiByteToWideChar (または WideCharToMultiByte) が複数回呼び出されるのを避けることができます。 これを行うには、必要な量よりも少しだけ多くのメモリを割り当てます。 MBC は最大で 1 つの WCHAR に変換され、WCHAR ごとに MBC のバイト数の最大 2 倍になることがわかっています。 必要な量より少し多いが、変換を処理するのに十分な量を常に割り当てることによって、変換関数への 2 回目の呼び出しを回避できます。 ヘルパー関数 AfxA2Whelper の呼び出しにより、変換を行うために実行する必要がある引数プッシュの数が減ります (これにより、MultiByteToWideChar を直接呼び出す場合よりもコードが短くなります)。

マクロで一時的な長さを格納する領域を確保するには、変換マクロを使用する各関数でこれを行う _convert というローカル変数を宣言する必要があります。 これは、上記の例で示した USES_CONVERSION マクロを呼び出して行います。

汎用変換マクロと OLE 固有のマクロの両方があります。 これら 2 つの異なるマクロ セットについて以下で説明します。 すべてのマクロは AFXPRIV.H に存在します。

汎用変換マクロ

汎用変換マクロは、基盤のメカニズムとなっています。 前のセクションで示した、マクロの例と実装である A2W は、このような "汎用" マクロの 1 つです。 これは厳密には OLE に関連付けられていません。 一連の汎用マクロを以下に示します。

A2CW      (LPCSTR) -> (LPCWSTR)
A2W      (LPCSTR) -> (LPWSTR)
W2CA      (LPCWSTR) -> (LPCSTR)
W2A      (LPCWSTR) -> (LPSTR)

テキスト変換の実行に加え、TEXTMETRICDEVMODEBSTR、および OLE で割り当てられた文字列を変換するマクロとヘルパー関数も用意されています。 これらのマクロは、ここでの説明の範囲を超えているため、詳細については、AFXPRIV.H を参照してください。

OLE 変換マクロ

OLE 変換マクロは、OLESTR 文字を受け取る関数を処理するために特別に設計されています。 OLE ヘッダーを調べると、LPCOLESTROLECHAR への参照が多数見つかります。 これらの型は、プラットフォーム固有ではない方法で、OLE インターフェイスで使用される文字の種類を参照するために使用されます。 OLECHAR は Win16 および Macintosh プラットフォームでは char にマップされ、Win32 では WCHAR にマップされます。

MFC コード内の #ifdef ディレクティブの数を最小限に抑えるために、OLE 文字列が関与する変換ごとに同様のマクロが用意されています。 最もよく使用されるマクロは次のとおりです。

T2COLE   (LPCTSTR) -> (LPCOLESTR)
T2OLE   (LPCTSTR) -> (LPOLESTR)
OLE2CT   (LPCOLESTR) -> (LPCTSTR)
OLE2T   (LPCOLESTR) -> (LPCSTR)

ここでも、TEXTMETRIC、DEVMODE、BSTR、および OLE で割り当てられた文字列を変換する同様のマクロがあります。 詳細については、AFXPRIV.H を参照してください。

その他の注意事項

マクロは、短いループでは使用しないでください。 たとえば、次のようなコードを記述してはいけません。

void BadIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, T2COLE(lpsz));

}

上記のコードでは、文字列 lpsz の内容しだいでは、スタックにメガバイトのメモリが割り当てられる可能性があります。 また、ループのイテレーションごとに文字列を変換するのにも時間がかかります。 代わりに、このような定数変換をループから移動します。

void MuchBetterIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    LPCOLESTR lpszT = T2COLE(lpsz);

    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, lpszT);

}

文字列が定数ではない場合は、メソッド呼び出しを関数内にカプセル化します。 これにより、変換バッファーを毎回解放できます。 次に例を示します。

void CallSomeMethod(int ii, LPCTSTR lpsz)
{
    USES_CONVERSION;
    pI->SomeMethod(ii, T2COLE(lpsz));

}

void MuchBetterIterateCode2(LPCTSTR* lpszArray)
{
    for (int ii = 0; ii <10000; ii++)
    CallSomeMethod(ii, lpszArray[ii]);

}

戻り値が、返される前のデータをコピーすることを意味するのではない場合は、マクロのいずれかの結果を返さないでください。 たとえば、次のコードは不適切です。

LPTSTR BadConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

上記のコードは、戻り値を、値をコピーするものに変更することで修正できます。

CString BetterConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

マクロは使いやすく、コードに簡単に挿入できますが、上記の注意点からもわかるように、慎重に使用する必要があります。

関連項目

番号順テクニカル ノート
カテゴリ別テクニカル ノート