テクニカル ノート 59: MFC の MBCS/Unicode 変換マクロの使用
更新 : 2007 年 11 月
メモ : |
---|
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。 |
ここでは、AFXPRIV.H 中で定義されている MBCS/Unicode 変換用マクロの使用方法を説明します。これらのマクロは、OLE API を直接扱うようなアプリケーションや、なんらかの理由で Unicode と MBCS 間で変換を行う必要がある場合に便利です。
概要
MFC 3.x では、OLE インターフェイスが呼び出された場合に、特別な DLL (MFCANS32.DLL) を使用して、Unicode と MBCS 間の変換を自動的に行っていました。この DLL は透過的な層であり、この DLL を使用すると、Unicode が使用されていても OLE API とインターフェイスは MBCS であるかのように、OLE アプリケーションを作成できました。この変換層を使用すると、Win16 から Win32 アプリケーションにすばやく移植できるようになりましたが (MFC、Microsoft Word、Microsoft Excel、および VBA はこの技術を使用する Microsoft アプリケーションの一例)、パフォーマンスの低下が生じる場合もありました。この理由により、MFC 4.x ではこの DLL を使用せずに、Unicode OLE インターフェイスを直接呼び出しています。このため、MFC では、OLE インターフェイスを呼び出すときに MBCS から Unicode への変換が必要となり、OLE インターフェイスを実装するときには、Unicode から MBCS への変換が必要となります。この変換を効率的で容易に行うために、多数のマクロが作成されました。
このようなマクロの作成において最も困難な問題は、メモリの割り当てです。文字列はその場で変換できないため、変換結果を保持するためのメモリを新しく割り当てる必要があります。これは、次のようなコードで実現できます。
// 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 のビルド用に適切な #ifdefs を追加する必要もあります。
これらの問題に対する解決策としては、1) さまざまなプラットフォーム間の違いを隠ぺいし、2) 効率的なメモリ割り当て方式を使用し、3) 既存のソース コードに簡単に挿入できるマクロを作成します。マクロ定義の一例を次に示します。
#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) を複数回呼び出さずに済みます。これは、メモリを必要な量よりも少し多めに割り当てることで実現できます。1 文字のマルチバイト文字は WCHAR 1 文字に変換され、WCHAR は最大で 2 文字のマルチバイト文字に変換されます。必要な量よりも少し多く、ただし変換処理には十分なメモリを割り当てることで、変換関数を 2 回呼び出す必要がなくなります。ヘルパ関数 AfxA2Whelper を呼び出すことで、変換を実行するために必要な引数のプッシュ回数が減少し、その結果、生成されるコードは MultiByteToWideChar を直接呼び出す場合よりも小さくなります。
マクロで一時的な長さを格納するスペースを確保するために、変換マクロを使用する関数の中で _convert というローカル変数を宣言する必要があります。このように宣言するには、上の例と同じように USES_CONVERSION マクロを呼び出します。
汎用変換マクロと OLE 固有のマクロがあります。ここでは、それぞれについて説明します。これらのマクロはすべて AFXPRIV.H で定義されています。
汎用変換マクロ
汎用変換マクロは基礎となる機構を形成します。上のマクロの例で示した A2W は、この種の "汎用" マクロの一例です。これは OLE とは特に関係はありません。汎用マクロの一覧を次に示します。
A2CW (LPCSTR) -> (LPCWSTR)
A2W (LPCSTR) -> (LPWSTR)
W2CA (LPCWSTR) -> (LPCSTR)
W2A (LPCWSTR) -> (LPSTR)
テキストの変換以外に、TEXTMETRIC、DEVMODE、BSTR、および OLE が割り当てた文字列を変換するためのマクロやヘルパ関数もあります。これらのマクロの詳細については、AFXPRIV.H を参照してください。
OLE 変換マクロ
OLE 変換マクロは OLESTR 文字を扱うことを目的とした関数の処理用にデザインされています。OLE のヘッダーをチェックすると、LPCOLESTR と OLECHAR への参照が数多くあるのがわかります。これらの型は、OLE インターフェイスで使用する文字の型をプラットフォームに依存しない方法で参照するために使用されます。OLECHAR は Win16 のプラットフォームでは 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 の内容によっては何 MB ものメモリがスタック上に割り当てられる可能性があります。また、ループの繰り返しのたびに文字列を変換するため時間がかかります。このような定数の変換は、ループの外で行います。
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
}
これらのマクロは使いやすく、簡単にコードに追加できますが、使用する場合は、上で説明した注意事項を考慮する必要があります。