文字列操作関数2

String○○系関数

Windows APIには「String」から始まる文字列操作関数があります。
これらはバッファオーバーランの危険を無くしたより安全な文字列操作関数です。
使用するにはstrsafe.hのインクルードが必要です。

戻り値について

これらの関数は戻り値にSTRSAFEAPI型を返します。
これはHRESULT型の別名で、関数実行の成否情報が格納されています。
ただしBOOL型とは異なり、関数実行の成否を判断するにはSUCCEEDEDマクロまたはFAILEDマクロを使用します。


WCHAR buf[32];
size_t length;
HRESULT result;

result = StringCchLength(buf, 32, &length);
if (SUCCEEDED(result)) {
	//成功
}
else {
	//失敗
}

FAILEDマクロはSUCCEEDEDマクロとは逆の結果を返します。
(失敗した時に真となります)

HRESULT型を返す関数はSUCCEEDEDマクロまたはFAILEDマクロで関数の成否を判定しますが、関数が「成功」したときに、それにより得られた値が必ずしも有効であるとは限りません。
(戻り値はHRESULT型なので、引数に変数のポインタを渡して値を得ます)

HRESULT型を返す関数には「S_FALSE」という値(定数)を返すものがあります。
これは名前だけ見ればFALSE(失敗)ですがSUCCEEDEDマクロで真となり、FAILEDマクロで偽となります。
つまり関数は成功しているのですが、何らかの「失敗」を含む結果ということで、得られる値は期待通りではないかもしれません。
例えば関数の仕様としてNULLが渡される場合があり、NULLチェックをせずにその変数を使用するとエラーになる可能性があります。
S_FALSEを返す場合がある関数は仕様に注意する必要があります。

StringCch系とStringCb系

String○○系関数は大別してStringCch系StringCb系が存在します。
これらは文字列サイズの扱い方が異なります。

「StringCch」から始まる関数は文字列サイズを文字数で扱います。
「StringCb」から始まる関数は文字列サイズをバイト数で扱います。
それ以外の機能は同じです。

StringCb系関数で文字列サイズをバイト数で指定するには以下のようにします。


//WCHARの場合

int length = 10; //10文字
size_t size = length * sizeof(WCHAR); //10文字分のバイト数

一覧

以下にString○○系関数の一覧を示します。

関数名 対応するC標準関数
StringCchLength
StringCbLength
strnlen
(ただし使い方が異なる)
StringCchCopy
StringCbCopy
strncpy
StringCchCopyN
StringCbCopyN
strncpy_s
StringCchCat
StringCbCat
strncat
StringCchCatN
StringCbCatN
strncat_s
StringCchGets
StringCbGets
gets
StringCchPrintf
StringCbPrintf
snprintf
StringCchVPrintf
StringCbVPrintf
vsnprintf
StringCchPrintf_l
StringCbPrintf_l
_snprintf_l
StringCchVPrintf_l
StringCbVPrintf_l
_vsnprintf_l

また、「○○Length」以外の関数には末尾に「Ex」が付いたEx関数が存在します。
例えば「StringCchCopy」関数ならば「StringCchCopyEx」関数です。
これについては後述します。

strnlenとStringCch(Cb)Lengthの違い

C言語のstrnlen関数は文字列のサイズ(バイト数)を返しますが、StringCch(Cb)Length関数は第三引数が追加されていて、ここにsize_t型変数のポインタを渡してサイズを取得します。
StringCchLength関数は「文字数」を取得し、StringCbLength関数は「バイト数」を取得します。

また、strnlen関数の第二引数は取得するサイズの最大値の指定で、この値よりも文字列のサイズが大きい場合は第二引数の値をそのまま返します。
StringCch(Cb)Length関数は正確には「文字列サイズが第二引数の値を超えるか否か」を判定する関数です。
文字列サイズが第二引数の値を超える場合は関数は失敗し(SUCCEEDEDマクロが偽を返す)、取得される値(第三引数)に格納される値は不定です。

通常の範疇であればSTRSAFE_MAX_CCHという定数を第二引数を指定することでサイズを正常に取得できます。
STRSAFE_MAX_CCHはstrsafe.hで定義される文字列操作関数が扱える最大の文字数を表します。
(サイズをバイト数で扱うStringCbLength関数の場合はSTRSAFE_MAX_CCH * sizeof(WCHAR)を指定する)


char text1[] = "あいうえお"; 
size_t size1 = strnlen(text1, 256);
//"size1" は 10 (ただし環境による)

WCHAR text2[] = L"あいうえお";
size_t size2;
StringCchLength(text2, STRSAFE_MAX_CCH, &size2);
//"size2" は 5

StringCch(Cb)Getsとgetsの違い

StringCchGets関数、StringCbGets関数は標準入力(stdin)から改行文字(\n)までの一行を読み取る関数です。
C言語のgets関数と同じですが、バッファサイズを指定する引数が一つ増えています。
fgets関数から第三引数のストリームの指定を無くした版とも言えます。
ただし、改行文字を読み取った場合に改行文字をNULL文字に置き換える点が異なります。
(fgetsは改行文字をそのまま読み取り、その後ろにNULL文字を付加する)

サンプルコード


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

//ウィンドウの生成等は省略

#define BUFFERSIZE 128

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	static WCHAR output[BUFFERSIZE];
	WCHAR buf[BUFFERSIZE];

	static const WCHAR* format =
		L"文字列: %s\n"
		L"文字数: %d\n"
		L"バイト数: %d\n";
	size_t length1, length2;

	switch (message)
	{
	case WM_CREATE:
		//文字列のコピー
		StringCchCopy(buf, BUFFERSIZE, L"コピー");

		//文字列の結合
		StringCchCat(buf, BUFFERSIZE, L"そして結合");

		//文字列の文字数を取得
		//STRSAFE_MAX_CCHはSTRSAFE系関数で処理可能な最大の文字数を示す定数
		StringCchLength(buf, STRSAFE_MAX_CCH, &length1);

		//文字列のバイト数を取得
		StringCbLength(buf, STRSAFE_MAX_CCH * sizeof(WCHAR), &length2);

		//書式指定コピー
		StringCchPrintf(output, BUFFERSIZE, format,
			buf,
			length1,
			length2);
		break;

	case WM_PAINT: //ウィンドウの描画発生
		GetClientRect(hWnd, &rect);
		hdc = BeginPaint(hWnd, &ps);
		DrawText(hdc, output, -1, &rect, DT_WORDBREAK | DT_NOPREFIX);
		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

String系関数のサンプル

Ex関数

上に書いた通り、○○Length以外の関数には末尾に「Ex」が付いた関数があります。
Ex関数は通常版よりも引数が三つ増えています。
ここではStringCchCopyEx関数を例に説明します。

STRSAFEAPI StringCchCopyExW(
 STRSAFE_LPWSTR pszDest,
 size_t cchDest,
 STRSAFE_LPCWSTR pszSrc,
 STRSAFE_LPWSTR *ppszDestEnd,
 size_t *pcchRemaining,
 DWORD dwFlags
);
サイズがcchDestの文字列バッファpszDestに文字列pszSrcをコピーする。
文字型ポインタppszDestEndにはコピーした文字列の終端のNULL文字の位置を格納する。
サイズ変数pcchRemainingには残りの空き領域のサイズを格納する。
フラグdwFlagsで詳細な動作を決定する。

第四引数ppszDestEndはWCHAR型のポインタ変数のアドレスを指定します。
ここにはバッファに文字列をコピーした後のNULL文字の位置が格納されます。
必要がなければNULLを指定できます。

第五引数pcchRemainingはsize_t型変数のアドレスを指定します。
ここにはバッファに文字列をコピーした後の残りの領域サイズが格納されます。
(NULL文字は含まない)
必要がなければNULLを指定できます。

第六引数dwFlagsは関数の動作を変更するフラグです。
以下の定数の組み合わせを指定できます。

定数 説明
STRSAFE_IGNORE_NULLS 文字列を指定する引数にNULLを指定したとき、空文字("")が渡されたものとして扱う。
STRSAFE_FILL_BEHIND_NULL 関数が成功した場合、バッファの終端のNULL文字以降の領域をdwFlagsの下位バイトの値で埋める。
STRSAFE_FILL_ON_FAILURE 関数が失敗した場合、バッファの全ての領域をdwFlagsの下位バイトの値で埋め、終端をNULL文字にする。
STRSAFE_NULL_ON_FAILURE 関数が失敗した場合、バッファを空文字("")にする。
STRSAFE_NO_TRUNCATION 関数が失敗した場合、バッファを空文字("")にする。
StringCch(Cb)CatEx関数StringCch(Cb)CatNEx関数の場合、バッファに文字を追加しない。
(元の文字列はそのまま残る)

STRSAFE_FILL_BEHIND_NULLフラグおよびSTRSAFE_FILL_ON_FAILUREフラグでは、dwFlagsの下位バイト(下位ワードではない)に、領域を埋めるために使用する値を指定できます。
例えば以下のように使用します。


#define BUFFERSIZE 10

WCHAR buf[BUFFERSIZE];
WCHAR* ptr;
size_t sizeRemain;

StringCchCopyEx(
	buf, BUFFERSIZE, L"abc",
	&ptr, &sizeRemain, STRSAFE_FILL_BEHIND_NULL | L'*');

上記の例では、バッファに文字列をコピーした後に残りの空き領域を「*」で埋めます。
ただしワイド文字の場合は1文字を2バイト以上で表すため、この機能を使用して特定の「文字」で埋めることは実質的に不可能です。
上記コードも「*」で埋めることはできません。
NULL文字(\0、または数値の0)はワイド文字でも全てのバイトが0なので、NULL文字で埋めることは可能です。
(何も指定しない場合は下位バイトが0になるので、0で埋められます)

dwFlagsフラグの定数の実際の値は、最下位バイトが全て0になるように定義されています。
なので、ビットOR演算子(|記号)でここに1バイトの情報(半角英数字)を格納できるわけです。

これはワイド文字版(末尾にWが付く関数)でも同じですが、指定の領域は1バイトずつのデータで埋められます。
ワイド文字を考慮して2バイト(以上)でひとつの文字として埋める、といったような動作はしてくれません。
そのためその領域をワイド文字として読みだすと期待した文字を読みだすことはできません。