Notes C API探訪: RANGE(データ型)その6
・2021/7/21 ソースコードの見直しをしました。
LIST型には、保持しているエントリー数を超えて、エントリーデータを追加できるListAddEntry関数というのがありました。今回はそれのRANGE型版を作ってみます。
RangeGetSize
まず、メモリハンドルで保持しているサイズとは別に、RANGE型が持っているはずのメモリサイズを求めるテンプレート関数、RangeGetSizeを定義しておきます。
template <typename Traits>
WORD RangeGetSize(
void *pRange,
BOOL fPrefixDataType
) {
size_t prefixSize = sizeof(WORD) * (fPrefixDataType ? 1 : 0);
RANGE *ptr = reinterpret_cast<RANGE*>(
reinterpret_cast<char*>(pRange)
+ prefixSize);
return static_cast<WORD>(
prefixSize
+ sizeof(RANGE)
+ sizeof(Traits::item) * ptr->ListEntries
+ sizeof(Traits::pair) * ptr->RangeEntries
);
}
RangeInsertItem
RangeInsertItemテンプレート関数は、RANGE型の領域に、新しくアイテムデータを追加します。確保しているメモリ領域はデータ1つ分拡張されます。
template <typename Traits>
STATUS RangeInsertItem(
DHANDLE hRange,
BOOL fPrefixDataType,
WORD EntryNumber,
const typename Traits::item *pData
) {
// メモリハンドルの全体サイズを取得
DWORD memsize = 0;
STATUS status = OSMemGetSize(hRange, &memsize);
if (ERR(status) != NOERROR) {
return status;
}
// RANGE型が持っているはずのサイズを取得
WORD realsize = RangeGetSize<Traits>(
OSLockObject(hRange),
fPrefixDataType
);
OSUnlockObject(hRange);
// ハンドルのサイズが持つべきサイズを下回った場合拡張する
if (memsize < (realsize + sizeof(Traits::item))) {
// メモリをアイテムサイズ1つ分増やす
memsize = realsize + sizeof(Traits::item);
status = OSMemRealloc(hRange, memsize);
if (ERR(status) != NOERROR) {
return status;
}
}
// メモリハンドルをロックしてポインタを取得
char *p0 = reinterpret_cast<char*>(OSLockObject(hRange));
// RANGE型データポインタを取得
DWORD offset = sizeof(WORD) * (fPrefixDataType ? 1 : 0);
RANGE *pRange = reinterpret_cast<RANGE*>(p0 + offset);
// 挿入位置を0~エントリー数までとする
EntryNumber = (EntryNumber > pRange->ListEntries)
? pRange->ListEntries
: EntryNumber;
// 挿入位置までのオフセットを求める
offset += sizeof(RANGE) + sizeof(Traits::item) * EntryNumber;
char *pos = p0 + offset;
// 挿入位置から後ろのメモリをアイテムサイズ1つ分ずらす
memmove(pos + sizeof(Traits::item), pos, realsize - offset);
// 挿入位置にデータをコピーする
memcpy(pos, pData, sizeof(Traits::item));
// RANGE::ListEntriesを1つ増やす
++pRange->ListEntries;
// メモリハンドルをアンロックする
OSUnlockObject(hRange);
return status;
}
・Traitsは、以前の記事で紹介した特性構造体、NumberRangeTraitsかTimeDateRangeTraitsかを指定します。
・hRangeは、RANGE型メモリハンドルを指定します。
・fPrefixDataTypeは、データタイププレフィックスを持つかどうか指定します。
・EntryNumberは、挿入したい位置を指定します。
・pDataは、挿入したいデータを指定します。
RangeInsertPair
RangeInsertPairテンプレート関数は、RANGE型の領域に、新しくペアデータを追加します。確保しているメモリ領域はデータ1つ分拡張されます。
template <typename Traits>
STATUS RangeInsertPair(
DHANDLE hRange,
BOOL fPrefixDataType,
WORD EntryNumber,
const typename Traits::pair *pData
) {
// メモリハンドルの全体サイズを取得
DWORD memsize = 0;
STATUS status = OSMemGetSize(hRange, &memsize);
if (ERR(status) != NOERROR) {
return status;
}
// RANGE型が持っているはずのサイズを取得
WORD realsize = RangeGetSize<Traits>(
OSLockObject(hRange),
fPrefixDataType
);
OSUnlockObject(hRange);
// ハンドルのサイズが持つべきサイズを下回った場合拡張する
if (memsize < (realsize + sizeof(Traits::pair))) {
// メモリをペアサイズ1つ分増やす
memsize = realsize + sizeof(Traits::pair);
status = OSMemRealloc(hRange, memsize);
if (ERR(status) != NOERROR) {
return status;
}
}
// メモリハンドルをロックしてポインタを取得
char *p0 = reinterpret_cast<char*>(OSLockObject(hRange));
// RANGE型データポインタを取得
DWORD offset = sizeof(WORD) * (fPrefixDataType ? 1 : 0);
RANGE *pRange = reinterpret_cast<RANGE*>(p0 + offset);
// 挿入位置を0~エントリー数までとする
EntryNumber = (EntryNumber > pRange->RangeEntries)
? pRange->RangeEntries
: EntryNumber;
// 挿入位置までのオフセットを求める
offset += sizeof(RANGE) + sizeof(Traits::pair) * EntryNumber
+ sizeof(Traits::item) * pRange->ListEntries;
char *pos = p0 + offset;
// 挿入位置から後ろのメモリをペアサイズ1つ分ずらす
memmove(pos + sizeof(Traits::pair), pos, realsize - offset);
// 挿入位置にデータをコピーする
memcpy(pos, pData, sizeof(Traits::pair));
// RANGE::RangeEntriesを1つ増やす
++pRange->RangeEntries;
// メモリハンドルをアンロックする
OSUnlockObject(hRange);
return status;
}
・Traitsは、以前の記事で紹介した特性構造体、NumberRangeTraitsかTimeDateRangeTraitsかを指定します。
・hRangeは、RANGE型メモリハンドルを指定します。
・fPrefixDataTypeは、データタイププレフィックスを持つかどうか指定します。
・EntryNumberは、挿入したい位置を指定します。
・pDataは、挿入したいデータを指定します。
コーディング例
for (int i = 0; i < 10; ++i) {
TIMEDATE time;
OSCurrentTIMEDATE(&time);
TimeDateAdjust(&time, 0, i * 10, 0, 0, 0, 0);
STATUS status = RangeInsertItem<TimeDateRangeTraits>(
hRange,
fPrefixDataType,
i,
&time);
if (ERR(status) != NOERROR) {
std::cerr << "RangeInsertItem error!" << std::endl;
OSMemFree(hRange);
return;
}
}
for (int i = 0; i < 10; ++i) {
TIMEDATE_PAIR pair;
OSCurrentTIMEDATE(&pair.Lower);
TimeDateAdjust(&pair.Lower, 0, i * 12, 0, 0, 0, 0);
OSCurrentTIMEDATE(&pair.Upper);
TimeDateAdjust(&pair.Upper, 0, i * 24, 0, 0, 0, 0);
STATUS status = RangeInsertPair<TimeDateRangeTraits>(
hRange,
fPrefixDataType,
i,
&pair);
if (ERR(status) != NOERROR) {
std::cerr << "RangeInsertPair error!" << std::endl;
OSMemFree(hRange);
return;
}
}
まとめ
2つの配列を同時に扱うRANGE型において、単数アイテム、ペアアイテムを自由に挿入できるロジックは相当のコード削減に繋がると思います。ただし、メモリを直接扱うので、これはこれで相当気を遣いますが。
ちなみに、今回のテンプレート関数名は、LIST型の「ListAddEntry」からいただけば「RangeAddItem」「RangeAddPair」となるところですが、「RANGE(データ型)その3」で紹介したテンプレート関数とかぶるのはもちろん、最後尾に追加できるだけでなく、先頭や途中にも挿入できることから、今回はAddからInsertに変更しています。
2021/7/21 追記
OSMemAlloc関数はダウンサイズさせようとする時、ある程度のバッファを持つため、意図したサイズに減らない場合があります。RANGE型が実際に使用しているサイズを都度計算しないと、意図しない結果になることがわかりました。
注意: コードの利用においてチブル・システムズは一切の責任を負いません。自己責任でご利用をお願いいたします。