文字列操作関数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;
}
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バイト(以上)でひとつの文字として埋める、といったような動作はしてくれません。
そのためその領域をワイド文字として読みだすと期待した文字を読みだすことはできません。