桜、抹茶、白、日記

名古屋市在住のC++使いのcoderの日記だったもの。

続・またマルチバイト文字列 (1)

先日書いた内容(d:id:youandi:20081112#p1)は大嘘こいていましたorz
自分の確認の仕方が不味かったのと、自分の勘違いが招いていた模様。

API マルチバイト文字対応 Null文字終端処理
_tcsncpy O X
lstrcpyn X O
StrCpyN, StrNCpy X O
StringCbCopy, StringCchCopy X O
_sntprintf X X
wnsprintf *1 X O
StringCbPrintf, StringCchPrintf X O

とりあえず前回の場合には以下の事項の考慮が足りませんでした。

    1. Null文字終端
    2. 指定バッファサイズよりもコピー元文字列の方が文字数が多い場合の確認をしていなかった

1つ目の件は・・・、

const char szMBS[] = "123456789";

char szText1[ 5 ] = { 0 };
_tcsncpy( szText1, szMBS, 5 );               // szText1[ 4 ] == '5'

char szText2[ 5 ] = { 0 };
lstrcpyn( szText2, szMBS, 5 );               // szText1[ 4 ] == '\0'

char szText3[ 5 ] = { 0 };
_sntprintf( szText3, 5, "%s", szMBS );       // szText1[ 4 ] == '5'

char szText4[ 5 ] = { 0 };
StringCchPrintf( szText4, 5, "%s", szMBS );  // szText4[ 4 ] == '\0'

上記のように、_tcsncpy()/_snprintf()を利用した場合は引数に指定した文字数分文字が格納されるのに対し、lstrcpyn()/StringCchPrintf()を利用する場合は、指定した文字数目にはNull文字が格納されます。よって_tcsncpy()/_sntprintf()の場合は格納したい文字数からNull文字分をさっ引きます。

const char szMBS[] = "123456789";

char szText1[ 5 ] = { 0 };
_tcsncpy( szText1, szMBS, 5 - 1 );               // szText1[ 4 ] == '\0'

char szText3[ 5 ] = { 0 };
_sntprintf( szText3, 5 - 1, "%s", szMBS );       // szText1[ 4 ] == '\0'

自分がまず勘違いしていたのはlstrcpyn()の際にも文字数を -1 していたので本来格納出来る文字数よりも1文字少ない状態になっていました。
2つ目の件は、単純にこんなコードで確認していたのが原因。

const TCHAR	szMBS[] = _T("あいうえお");
TCHAR		szText[ 9 + 1 ];

"あいうえお"だけではちょっと足りなくて、もう1文字位はないとコピー元のデータ数としてまずかった。

// 当然ながらプロジェクトの文字セットの設定は「マルチバイト文字を使用する」で。
#include <stdio.h>
#include <tchar.h>

#include <windows.h>
#include <shlwapi.h>
#include <strsafe.h>

#pragma comment( lib, "shlwapi.lib" )

#define	CHAR_SIZE	(8)

void ToHex( TCHAR, TCHAR[] );
void Dump( const TCHAR*, TCHAR[] );

int _tmain(int argc, _TCHAR* argv[])
{
    const TCHAR	szMBS[]				= _T("あいうえおかきくけこ");
    TCHAR		szText[ CHAR_SIZE ]	= { 0 };

    ::_tcsncpy( szText, szMBS, CHAR_SIZE - 1 );
    Dump( _T("_tcsncpy"), szText );
    ::lstrcpyn( szText, szMBS, CHAR_SIZE );
    Dump( _T("lstrcpyn"), szText );
    ::StrCpyN( szText, szMBS, CHAR_SIZE );
    Dump( _T("StrCpyN\t"), szText );
    ::StringCbCopy( szText, sizeof( szText ), szMBS );
    Dump( _T("StringCbCopy"), szText );

    ::_sntprintf( szText, CHAR_SIZE - 1, _T("%s"), szMBS );
    Dump( _T("_sntprintf"), szText );
    ::wnsprintf( szText, CHAR_SIZE, _T("%s"), szMBS );
    Dump( _T("wnsprintf"), szText );
    ::StringCbPrintf( szText, sizeof( szText ), _T("%s"), szMBS );
    Dump( _T("StringCbPrintf"), szText );

    ::fgetc( stdin );
    return 0;
}

void ToHex( TCHAR cValue, TCHAR szBuff[] )
{
#ifdef _UNICODE
	::wnsprintf( szBuff, 7, _T("%x"), cValue );
#else
	int		nValue	= ( cValue < 0 ) ? cValue & 0xFF : cValue;
	div_t	stValue	= ::div( nValue, 16 );
	szBuff[ 0 ] = ( stValue.quot < 10 )
                    ? _T('0') + stValue.quot
                    : _T('A') + ( stValue.quot - 10 );
	szBuff[ 1 ] = ( stValue.rem  < 10 )
                    ? _T('0') + stValue.rem
                    : _T('A') + ( stValue.rem  - 10 );
	szBuff[ 2 ] = 0;
#endif
}

void Dump( const TCHAR* pszApi, TCHAR szText[] )
{
    TCHAR szHex[ 7 + 1 ];
    ::_ftprintf( stdout, _T("%s\t:|"), pszApi );
    for ( int i = 0; i < CHAR_SIZE; ++i )
    {
        ToHex( szText[ i ], szHex );
        ::_ftprintf( stdout, _T("%s|"), szHex );
    }
    ::_ftprintf( stdout, _T(":\"%s\" \n"), szText );
    ::StringCchCopy( szText, CHAR_SIZE, _T("") ); // 次回用にクリア
}

実行結果

_tcsncpy        :|82|A0|82|A2|82|A4|00|00|:"あいう"
lstrcpyn        :|82|A0|82|A2|82|A4|82|00|:"あいう・
StrCpyN         :|82|A0|82|A2|82|A4|82|00|:"あいう・
StringCbCopy    :|82|A0|82|A2|82|A4|82|00|:"あいう・
_sntprintf      :|82|A0|82|A2|82|A4|82|00|:"あいう・
wnsprintf       :|82|A0|82|A2|82|A4|82|00|:"あいう・
StringCbPrintf  :|82|A0|82|A2|82|A4|82|00|:"あいう・

という事で、マルチバイト文字列処理に対応しているのは_tcsncpy()のみとなりました。
結局の所自分の場合は、_mblen()を使った自作のstrncpy()(d:id:youandi:20070120#p1)/snprintf(大きめの文字バッファ数でsnprintf()してから自作strncpy()で文字数制限)でマルチバイト文字列処理に対応しました。

*1:浮動小数点形式「%f」には未対応なので注意