お仕事で、文字コードに苦しんだのでちょっと調べてみた。
ファイル出力されたUNICODE(UTF-8)、Windowsアプリ内でBSTRやら_bstr_tやらの扱いが混乱してしまった。あと、WideChar、マルチバイト文字、2バイト文字とか。
文字コードで一般的に頭に思い浮かべるのはASCII、SHIFT-JIS、UTF-8。UNICODEも。UNICODEとUTF-8、UTF-16とかの違いは?EUC-JPってのもある。
つらつらと書きだしてみる。。
- UNICODEとUTF-8について。
UNICODEは、符号化文字集合や文字符号化方式を定めたもの。
- 符号化文字集合は、文字に対する数値が割り振られたもの(論理コードとでもいうべきか)。文字コード、コードセット、キャラセット、CES。UNICODEは、U+0000~U+10ffffとか表記される。”犬”は、UNICODEでU+72ac
- 文字符号化方式は、符号化文字集合をバイト列に扱う仕様、方法を記したもの(物理コードという感じ?)。エンコーディング、CEF。UTF-8は、UNICODEの符号化文字集合の一つで1~4バイトで表現される。”犬”をUTF-8でバイト列にすると0xe78aac。UTF-16だと0x72ac。SHIFT-JISだと0x8ca2。
ASCII文字類はUTF-8・UTF-16だと0x00を加えて2バイトにする。
- BSTRは、内部でWideCharで扱われる。正確に言うと、OLECHAR *であり、OLECHARはWIN32であるとき+OLE2ANSIではないときにWCHARとなる。それ以外の時はchar型となる。
- _bstr_tは、BSTRのスマートポインタ、ヘルパークラス。デバッグで見るとメンバ変数を見ると、m_wstrメンバ変数(BSTR)とm_strメンバ変数(mutable char*)、m_RefCount(unsigned long:スマートポインタのカウンタ)がある。
_bstr_tの変数を作ると、m_wstrメンバ変数にUTF-16(WideCharな 2バイト文字)で格納される。(const char*)でキャストするとm_strメンバ変数に新たに領域を確保してからロケールに従った変換結果(日本ならSHIFT-JISのマルチバイト)を格納する。変数の参照が終わると領域を解放する。
ちなみに、DLL内の関数に_bstr_t変数を渡して(const char*)でキャストすると、_bstr_t変数のスコープ外にでたときに領域解放できない。EXEとDLLのメモリ空間が異なるため。BSTRで渡してDLL内部で変換するべき。
- UNICODE(WideChar)をUTF-8やSHIFT-JIS(マルチバイト)に変換するには、WindowsAPIではWideCharToMultiByte()を使う。その逆はMultiByteToWideChar()を使う。UTF-8とSHIFT-JISの変換は、いったんUNICODE(WideChar)に変換してから、変換したい文字コード(マルチバイト)にする。
- BSTRからロケールに合わせた文字コードにするにはOLE2T、W2Aとか使えるかも。MSDN「ATLとMFCの文字列変換マクロ」を参照のこと。
テキストエディタで保存した、SHIFT-JIS、UTF-8、UTF-16、UTF-16(BigEndian)をVisualStudioでバイナリとして開いてみると以下のようになる。UTF-8,16,16(BE)の先頭3バイトはBOMが付いている。
試してみたプログラム
// TestBSTR.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"comsuppw.lib") // WideChar (link comsuppw.lib)
//#pragma comment(lib,"comsupp.lib") // Not WideChar (link comsupp.lib)
#include "stdafx.h"
#include <windows.h>
#include <comutil.h>
#include <tchar.h>
#include <mbstring.h>
void dumpBin(LPCSTR type, LPCSTR data, size_t len) {
printf("%s (address=0x%08x): \n", type, (unsigned int)data);
for (size_t idx = 0; len > idx; idx++) {
printf("\t");
for (size_t cnt = 0; (len > idx) && (16 > cnt); cnt++, idx++) {
printf("%02x ", (unsigned char)data[idx] );
}
printf("\n");
}
}
// WideCharからMultiByteに変換
//関数内で領域を確保するので、使用後は開放すること
int convertWideToMulti(const wchar_t *pwsData, UINT encoding, LPSTR *pstrRslt)
{
size_t len = wcslen(pwsData);
int iW2MLen1 = WideCharToMultiByte(encoding, 0, pwsData, len, NULL, 0, NULL, NULL);
if (iW2MLen1 == 0) {
// 失敗
return -1;
}
*pstrRslt = (LPSTR)malloc(iW2MLen1 + 1);
if (*pstrRslt == NULL) {
// 失敗
return -1;
}
ZeroMemory(*pstrRslt, iW2MLen1 + 1);
int iW2MLen2 = WideCharToMultiByte(encoding, 0, pwsData, len, (LPSTR)*pstrRslt, iW2MLen1, NULL, NULL);
if (iW2MLen2 == 0) {
// 失敗
free(*pstrRslt);
return -1;
}
return iW2MLen2;
}
// MultiByteからWideCharに変換
//関数内で領域を確保するので、使用後は開放すること
int convertMultiToWide(LPCSTR pcstrData, UINT encoding, LPWSTR *ppwsRslt )
{
size_t len = strlen(pcstrData);
int iW2MLen1 = MultiByteToWideChar(encoding, 0, pcstrData, len, NULL, 0);
if (iW2MLen1 == 0) {
// 失敗
return -1;
}
*ppwsRslt = (LPWSTR)malloc((iW2MLen1 + 1)*2);
if (*ppwsRslt == NULL) {
// 失敗
return -1;
}
ZeroMemory(*ppwsRslt, (iW2MLen1 + 1) * 2);
int iW2MLen2 = MultiByteToWideChar(encoding, 0, pcstrData, len, (LPWSTR)*ppwsRslt, iW2MLen1);
if (iW2MLen2 == 0) {
// 失敗
free(*ppwsRslt);
return -1;
}
return iW2MLen2 * 2;
}
int main()
{
const wchar_t *pwsData =
L"kotatuinu!\n"
L"炬燵犬!\n"
L"コタツイヌ\n";
printf("データ:%ls\n", pwsData);
// wchar_t
size_t len = wcslen(pwsData);
dumpBin("wchar_t", (LPCSTR)pwsData, len*2);
// _bstr_t -> BSTR
_bstr_t bstrData1(pwsData);
BSTR bsData1 = bstrData1;
len = SysStringByteLen(bsData1);
dumpBin("_bstr_t -> BSTR", (LPCSTR)bsData1, len);
// _bstr_t.copy(false) -> BSTR
// _bstr_t.copy(false)は_bstr_tのm_wstrメンバの領域を渡す→解放しちゃダメ
BSTR bsData3 = bstrData1.copy(false);
len = SysStringByteLen(bsData3);
dumpBin("_bstr_t.copy(false) -> BSTR", (LPCSTR)bsData3, len);
// _bstr_t.copy(true) -> BSTR
// _bstr_t.copy(true)は新たに領域を取得する→SysFreeString()で解放が必要
BSTR bsData2 = bstrData1.copy(true);
len = SysStringByteLen(bsData2);
dumpBin("_bstr_t.copy(true) -> BSTR", (LPCSTR)bsData2, len);
::SysFreeString(bsData2);
// _bstr_t -> const char *
const char *szData1 = (const char*)bstrData1;
len = strlen(szData1);
dumpBin("_bstr_t -> const char *", (LPCSTR)szData1, len);
// wchar->multibyte(encode UTF8)
LPSTR pszData4;
int iRtn = convertWideToMulti(pwsData, CP_UTF8, &pszData4);
len = strlen(pszData4);
len = _mbstrlen(pszData4);
dumpBin("wchar->multibyte(encode UTF8)", (LPCSTR)pszData4, len);
//free(pszData4);
// wchar->multibyte(encode SHIFT-JIS)
LPSTR pszData5;
iRtn = convertWideToMulti(pwsData, CP_ACP, &pszData5);
len = strlen(pszData5);
len = _mbstrlen(pszData5);
dumpBin("wchar->multibyte(encode SHIFT-JIS)", (LPCSTR)pszData5, len);
free(pszData5);
// multibyte(UTF-8)->wchar(UNICODE)->multibyte(SHIFT-JIS)
LPWSTR pwsData1;
LPSTR pszData6;
iRtn = convertMultiToWide((LPCSTR)pszData4, CP_UTF8, &pwsData1);
iRtn = convertWideToMulti(pwsData1, CP_ACP, &pszData6);
dumpBin("multibyte(UTF-8)->wchar(UNICODE)->multibyte(SHIFT-JIS)", (LPCSTR)pszData6, iRtn);
free(pszData4);
free(pszData6);
free(pwsData1);
return 0;
}
出力結果
データ:kotatuinu!
wchar_t (address=0x00f5cc5c):
6b 00 6f 00 74 00 61 00 74 00 75 00 69 00 6e 00
00 21 00 0a 00 ac 70 f5 71 ac 72 01 ff 0a 00 7a
80 ff 82 ff 72 ff 87 ff 0a 00
_bstr_t -> BSTR (address=0x00d96da4):
6b 00 6f 00 74 00 61 00 74 00 75 00 69 00 6e 00
00 21 00 0a 00 ac 70 f5 71 ac 72 01 ff 0a 00 7a
80 ff 82 ff 72 ff 87 ff 0a 00
_bstr_t.copy(false) -> BSTR (address=0x00d96da4):
6b 00 6f 00 74 00 61 00 74 00 75 00 69 00 6e 00
00 21 00 0a 00 ac 70 f5 71 ac 72 01 ff 0a 00 7a
80 ff 82 ff 72 ff 87 ff 0a 00
_bstr_t.copy(true) -> BSTR (address=0x00d96a44):
6b 00 6f 00 74 00 61 00 74 00 75 00 69 00 6e 00
00 21 00 0a 00 ac 70 f5 71 ac 72 01 ff 0a 00 7a
80 ff 82 ff 72 ff 87 ff 0a 00
_bstr_t -> const char * (address=0x00d96ee0):
6b 6f 74 61 74 75 69 6e 75 21 0a e0 78 e0 9d 8c
81 49 0a ba c0 c2 b2 c7 0a
wchar->multibyte(encode UTF8) (address=0x00d8b6c0):
6b 6f 74 61 74 75 69 6e 75 21 0a e7 82 ac e7 87
e7 8a ac ef bc 81 0a ef bd ba ef be 80 ef be 82
bd b2 ef be 87 0a
wchar->multibyte(encode SHIFT-JIS) (address=0x00d96d30):
6b 6f 74 61 74 75 69 6e 75 21 0a e0 78 e0 9d 8c
81 49 0a ba c0 c2 b2 c7 0a
multibyte(UTF-8)->wchar(UNICODE)->multibyte(SHIFT-JIS) (address=0x00d96fb8):
6b 6f 74 61 74 75 69 6e 75 21 0a e0 78 e0 9d 8c
81 49 0a ba c0 c2 b2 c7 0a
最近のコメント