見出し画像

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型が実際に使用しているサイズを都度計算しないと、意図しない結果になることがわかりました。

注意: コードの利用においてチブル・システムズは一切の責任を負いません。自己責任でご利用をお願いいたします。


この記事が気に入ったらサポートをしてみませんか?