Files
Just-DataInspector/Utilities/Src/MatH5.cpp
T
DeepSeek-V4.1-Flash f44f8021e3 Release 产物 1.74MB → 796KB(去掉 <filesystem> 与 zlib)+ .mat 加载进度不再停住
一、体积膨胀的真因(用 -Wl,-Map 逐对象/逐节对比上一提交定位,只有两个)
1) <filesystem>(致命,约 +900KB):MatH5::Open 用 std::filesystem::file_size 取文件大小,
   MinGW 的 libstdc++ 因此链入 fs_path/fs_dir/fs_ops/c++locale/codecvt/collate_members/
   cow-wstring-inst/cxx11-wlocale-inst/fstream-inst/cp-demangle 整套 locale/iostream 机器
   (归档成员 415→538);还把 64KB 静态表从 .bss 挤成 .data(1.8KB→73.8KB)。
   修法:改用 _wfopen_s + _fseeki64/_ftelli64 取大小。
2) -lz(约 46KB):只需要"解开 HDF5 分块"。修法:MatH5 内置紧凑 inflate
   (RFC1950/1951:固定/动态 Huffman、存储块、长度-距离回抄),Makefile 与探针脚本移除 -lz。
顺带把 UploadHaloLayer 里 64KB 的预乘 alpha 查表换成 (v*a+128)*257>>16(无除法、无表)。

二、压到 800KB 以下的两项无损措施
1) 图标资源无损重编码:ico 里 64/48/32/16 四帧原为未压缩 32bpp DIB(合计 32KB),
   用自写工具(GDI+ 编码)改为 PNG 帧(ICO 的 PNG 条目 Vista+ 全支持,本文件本就有 256 PNG 帧):
   43,334 → 17,404 字节。像素逐点校验:差异 ≤85 像素、最大分量差 ≤3(编码取整),肉眼不可辨;
   并从**真实产物** LoadImageW 逐尺寸(16/32/48/64)验证可加载。
2) RELEASE_OPT 由 -Os 改为 -Oz(省约 6KB);实测动画帧 p50 7.73ms(-Os) vs 7.77ms(-Oz),
   无可辨识差异。
放弃的手段:-ffunction-sections/-fdata-sections + --gc-sections(反而 +9KB)、LTO(工具链 ICE)、
-fno-asynchronous-unwind-tables(链接失败)、-fno-exceptions(会破坏 catch (bad_alloc))。
结果:Build/Release/Target.exe = 795,648 字节。

三、.mat 加载进度不更新(已加载大小 / 总大小 / 百分比都停在 0)
真因:LoadMatSourceGroups 只在结束时回调一次 ratio=1.0,且从不设置 hasBytes;
读 42MB 文件与解码 19 个样本流(解压+拷贝,占大头)期间进度恒为 0。
修法:
- MatHdf5::Open 新增 OpenProgress(bytesRead,totalBytes),按 4MB 分块读取并回报;
- MatLoader 用 ProgressReporter 把进度拆成 0.00–0.55 读文件(hasBytes=true)与
  0.55–0.98 解码样本流(每解完一个流推进 done/total),末段 1.0;
- 取消与出错分开:取消时清空 error,不弹错误框。
实测:回调 29 次、字节数变化 11 次、比例单调、终值 1.000、bytes=42,386,344/42,386,344
(= 文件大小);Application 侧断言 m_loadingBytesRead == m_loadingTotalBytes == 文件大小。

四、验证(全部离屏,不占用鼠标/键盘/屏幕)
- matcheck 21/21(新增进度断言)、matui 15/15(新增图标 + 加载字节断言);
- 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、
  datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、
  panelstutter 8/8、stress 6/6;
- make release / make debug 零警告。

文档:Documents/agent_mem.md 追加本轮小节(含 5 条新规则:MinGW 下 <filesystem> 是体积炸弹、
链一整条库前先算账、膨胀要看节表而非 .o 大小、图标可无损重编码并用产物校验、
长解析必须按阶段回报进度且 hasBytes 只在有字节时置真)。
2026-10-19 02:37:52 +08:00

1335 lines
38 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// HDF5 只读子集:MATLAB 7.3 `.mat` 解析用(见 MatH5.hpp 的说明)。
#include "MatH5.hpp"
#include <algorithm>
#include <cstdio>
#include <cstring>
namespace jdi
{
namespace
{
constexpr std::uint16_t kMessageDataspace = 0x0001;
constexpr std::uint16_t kMessageLink = 0x0006;
constexpr std::uint16_t kMessageDatatype = 0x0003;
constexpr std::uint16_t kMessageLayout = 0x0008;
constexpr std::uint16_t kMessageFilter = 0x000B;
constexpr std::uint16_t kMessageAttribute = 0x000C;
constexpr std::uint16_t kMessageContinuation = 0x0010;
constexpr std::uint16_t kMessageSymbolTable = 0x0011;
std::uint64_t ReadLE(const std::uint8_t* data, std::size_t size)
{
std::uint64_t value = 0;
for (std::size_t index = 0; index < size; ++index)
{
value |= static_cast<std::uint64_t>(data[index]) << (8 * index);
}
return value;
}
// ==================================================================
// 内置 inflate(RFC 1950 / 1951)
//
// ⚠️ **不能用 zlib(`-lz`)**:链接 libz.a 会让 Release 产物多出约 46KB,
// 而这里只需要「解压 HDF5 分块」这一个功能,自带的紧凑实现只要几 KB。
// ⚠️ 更不能为了取文件大小包含 `<filesystem>`:它会连带拉进 locale / codecvt /
// iostream / cp-demangle 等一整套 libstdc++ 代码,实测让产物从 787KB 涨到 1.7MB。
// ==================================================================
/** @brief 位流读取器(LSB 优先,与 DEFLATE 一致)。 */
struct BitReader
{
const std::uint8_t* data = nullptr;
std::size_t size = 0;
std::size_t position = 0;
std::uint32_t buffer = 0;
int bits = 0;
bool error = false;
bool Fill(int count)
{
while (bits < count)
{
if (position >= size)
{
error = true;
return false;
}
buffer |= static_cast<std::uint32_t>(data[position++]) << bits;
bits += 8;
}
return true;
}
std::uint32_t Read(int count)
{
if (count <= 0)
{
return 0;
}
if (!Fill(count))
{
return 0;
}
const std::uint32_t value = buffer & ((1u << count) - 1u);
buffer >>= count;
bits -= count;
return value;
}
void Align() { buffer = 0; bits = 0; }
};
/** @brief 规范 Huffman 表(只存计数与按长度排好序的符号)。 */
struct HuffmanTable
{
std::uint16_t count[16] = {};
std::uint16_t symbol[320] = {};
};
bool BuildHuffman(HuffmanTable& table, const std::uint8_t* lengths, int total)
{
for (int index = 0; index < 16; ++index)
{
table.count[index] = 0;
}
for (int index = 0; index < total; ++index)
{
++table.count[lengths[index]];
}
if (table.count[0] == total)
{
return true; // 全部长度为 0:该表没有任何码(动态块里可能是距离表)
}
int left = 1;
for (int length = 1; length < 16; ++length)
{
left <<= 1;
left -= table.count[length];
if (left < 0)
{
return false; // 超订(over-subscribed)
}
}
std::uint16_t offsets[16] = {};
offsets[1] = 0;
for (int length = 1; length < 15; ++length)
{
offsets[length + 1] = static_cast<std::uint16_t>(offsets[length] + table.count[length]);
}
for (int index = 0; index < total; ++index)
{
if (lengths[index] != 0)
{
table.symbol[offsets[lengths[index]]++] = static_cast<std::uint16_t>(index);
}
}
return true;
}
int DecodeSymbol(BitReader& reader, const HuffmanTable& table)
{
int code = 0;
int first = 0;
int index = 0;
for (int length = 1; length < 16; ++length)
{
code |= static_cast<int>(reader.Read(1));
if (reader.error)
{
return -1;
}
const int count = table.count[length];
if (code - first < count)
{
return table.symbol[index + (code - first)];
}
index += count;
first = (first + count) << 1;
code <<= 1;
}
return -1;
}
const std::uint16_t kLengthBase[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
67, 83, 99, 115, 131, 163, 195, 227, 258};
const std::uint8_t kLengthExtra[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
const std::uint16_t kDistanceBase[30] = {1, 2, 3, 4, 5, 7, 9, 13,
17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073,
4097, 6145, 8193, 12289, 16385, 24577};
const std::uint8_t kDistanceExtra[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
/** @brief 解压 zlib 流(含 2 字节头与 4 字节 Adler32 尾)。 */
bool Inflate(const std::uint8_t* source, std::size_t sourceSize,
std::vector<std::uint8_t>& out)
{
if (sourceSize < 6)
{
return false;
}
// zlib 头:CMF/FLG。只接受 deflate(CM = 8),且不接受预设字典。
const std::uint8_t cmf = source[0];
const std::uint8_t flg = source[1];
if ((cmf & 0x0F) != 8 || ((static_cast<unsigned>(cmf) * 256u + flg) % 31u) != 0 ||
(flg & 0x20) != 0)
{
return false;
}
BitReader reader;
reader.data = source + 2;
reader.size = sourceSize - 6; // 末尾 4 字节是 Adler32
out.clear();
HuffmanTable fixedLiterals;
HuffmanTable fixedDistances;
bool fixedReady = false;
for (int block = 0; block < 100000; ++block)
{
const std::uint32_t final = reader.Read(1);
const std::uint32_t type = reader.Read(2);
if (reader.error)
{
return false;
}
if (type == 0)
{
// 存储块(未压缩):对齐后 4 字节长度 + 反码 + 原始字节。
reader.Align();
if (reader.position + 4 > reader.size)
{
return false;
}
const std::uint16_t length = static_cast<std::uint16_t>(
reader.data[reader.position] | (reader.data[reader.position + 1] << 8));
reader.position += 4;
if (reader.position + length > reader.size)
{
return false;
}
out.insert(out.end(), reader.data + reader.position,
reader.data + reader.position + length);
reader.position += length;
}
else if (type == 1 || type == 2)
{
HuffmanTable literals;
HuffmanTable distances;
if (type == 1)
{
if (!fixedReady)
{
std::uint8_t lengths[288];
for (int index = 0; index < 144; ++index)
{
lengths[index] = 8;
}
for (int index = 144; index < 256; ++index)
{
lengths[index] = 9;
}
for (int index = 256; index < 280; ++index)
{
lengths[index] = 7;
}
for (int index = 280; index < 288; ++index)
{
lengths[index] = 8;
}
if (!BuildHuffman(fixedLiterals, lengths, 288))
{
return false;
}
std::uint8_t distanceLengths[30];
for (int index = 0; index < 30; ++index)
{
distanceLengths[index] = 5;
}
if (!BuildHuffman(fixedDistances, distanceLengths, 30))
{
return false;
}
fixedReady = true;
}
literals = fixedLiterals;
distances = fixedDistances;
}
else
{
const int literalCount = static_cast<int>(reader.Read(5)) + 257;
const int distanceCount = static_cast<int>(reader.Read(5)) + 1;
const int codeCount = static_cast<int>(reader.Read(4)) + 4;
if (reader.error || literalCount > 286 || distanceCount > 30)
{
return false;
}
static const std::uint8_t kOrder[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
11, 4, 12, 3, 13, 2, 14, 1, 15};
std::uint8_t codeLengths[19] = {};
for (int index = 0; index < codeCount; ++index)
{
codeLengths[kOrder[index]] = static_cast<std::uint8_t>(reader.Read(3));
}
HuffmanTable codeTable;
if (reader.error || !BuildHuffman(codeTable, codeLengths, 19))
{
return false;
}
std::uint8_t lengths[288 + 30] = {};
int produced = 0;
while (produced < literalCount + distanceCount)
{
const int symbol = DecodeSymbol(reader, codeTable);
if (symbol < 0)
{
return false;
}
if (symbol < 16)
{
lengths[produced++] = static_cast<std::uint8_t>(symbol);
continue;
}
int repeat = 0;
std::uint8_t value = 0;
if (symbol == 16)
{
if (produced == 0)
{
return false;
}
value = lengths[produced - 1];
repeat = 3 + static_cast<int>(reader.Read(2));
}
else if (symbol == 17)
{
repeat = 3 + static_cast<int>(reader.Read(3));
}
else
{
repeat = 11 + static_cast<int>(reader.Read(7));
}
if (reader.error || produced + repeat > literalCount + distanceCount)
{
return false;
}
for (int index = 0; index < repeat; ++index)
{
lengths[produced++] = value;
}
}
if (!BuildHuffman(literals, lengths, literalCount) ||
!BuildHuffman(distances, lengths + literalCount, distanceCount))
{
return false;
}
}
for (int symbolIndex = 0; symbolIndex < 100000000; ++symbolIndex)
{
const int symbol = DecodeSymbol(reader, literals);
if (symbol < 0)
{
return false;
}
if (symbol < 256)
{
out.push_back(static_cast<std::uint8_t>(symbol));
continue;
}
if (symbol == 256)
{
break; // 块结束
}
const int lengthCode = symbol - 257;
if (lengthCode >= 29)
{
return false;
}
const std::size_t length =
kLengthBase[lengthCode] + reader.Read(kLengthExtra[lengthCode]);
const int distanceCode = DecodeSymbol(reader, distances);
if (distanceCode < 0 || distanceCode >= 30 || reader.error)
{
return false;
}
const std::size_t distance =
kDistanceBase[distanceCode] + reader.Read(kDistanceExtra[distanceCode]);
if (reader.error || distance == 0 || distance > out.size())
{
return false;
}
const std::size_t start = out.size() - distance;
for (std::size_t index = 0; index < length; ++index)
{
out.push_back(out[start + index]);
}
}
}
else
{
return false; // 类型 3 非法
}
if (final != 0)
{
return true;
}
}
return false;
}
} // namespace
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
{
return (offset < m_data.size()) ? m_data[static_cast<std::size_t>(offset)] : 0;
}
std::uint16_t MatHdf5::U16(std::uint64_t offset) const
{
return static_cast<std::uint16_t>(U8(offset)) | (static_cast<std::uint16_t>(U8(offset + 1)) << 8);
}
std::uint32_t MatHdf5::U32(std::uint64_t offset) const
{
return static_cast<std::uint32_t>(U16(offset)) | (static_cast<std::uint32_t>(U16(offset + 2)) << 16);
}
std::uint64_t MatHdf5::U64(std::uint64_t offset) const
{
return static_cast<std::uint64_t>(U32(offset)) | (static_cast<std::uint64_t>(U32(offset + 4)) << 32);
}
std::uint64_t MatHdf5::Addr(std::uint64_t offset) const
{
return (m_offsetSize == 8) ? U64(offset) : U32(offset);
}
std::uint64_t MatHdf5::Resolve(std::uint64_t address) const
{
if (address == 0 || address == 0xFFFFFFFFFFFFFFFFull)
{
return 0;
}
return address + m_base;
}
bool MatHdf5::Open(const std::wstring& path, std::wstring& error, const OpenProgress& progress)
{
error.clear();
Close();
// ⚠️ 取文件大小一律用 C 运行库(`_fseeki64` / `_ftelli64`),**不要用 `<filesystem>`**:
// 后者会把 libstdc++ 的 locale / codecvt / iostream / cp-demangle 整套拉进产物,
// 实测让 Release 二进制从 787KB 涨到 1.7MB(用户 2026-10-19 报告)。
FILE* file = nullptr;
if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || file == nullptr)
{
error = L"无法打开文件。";
return false;
}
if (_fseeki64(file, 0, SEEK_END) != 0)
{
std::fclose(file);
error = L"无法读取文件大小。";
return false;
}
const long long size = _ftelli64(file);
if (size <= 0 || _fseeki64(file, 0, SEEK_SET) != 0)
{
std::fclose(file);
error = L"无法读取文件大小。";
return false;
}
m_data.resize(static_cast<std::size_t>(size));
// 按块读入并回报进度(加载遮罩层的"已加载大小 / 总大小 / 百分比"由它驱动)。
constexpr std::size_t kChunk = 4u << 20;
std::size_t offset = 0;
while (offset < m_data.size())
{
const std::size_t want = std::min(kChunk, m_data.size() - offset);
const std::size_t got = std::fread(m_data.data() + offset, 1, want, file);
offset += got;
if (got != want)
{
break;
}
if (progress && !progress(offset, m_data.size()))
{
std::fclose(file);
Close();
error.clear(); // 用户取消:不显示错误
return false;
}
}
std::fclose(file);
if (offset != m_data.size())
{
error = L"读取文件失败。";
return false;
}
// 超级块:签名可能出现在 0、512、1024…(MATLAB 用 512 字节 user block)。
std::size_t signature = m_data.size();
for (std::size_t index = 0; index + 8 <= m_data.size() && index <= 4096; index += 512)
{
if (m_data[index] == 0x89 && m_data[index + 1] == 'H' && m_data[index + 2] == 'D' &&
m_data[index + 3] == 'F')
{
signature = index;
break;
}
}
if (signature == m_data.size())
{
error = L"不是 HDF5(MATLAB 7.3)格式。";
return false;
}
const std::uint8_t version = U8(signature + 8);
if (version == 0 || version == 1)
{
m_offsetSize = U8(signature + 13);
m_lengthSize = U8(signature + 14);
m_base = U64(signature + 24);
const std::uint64_t rootEntry = signature + 24 + 32;
m_root = Resolve(Addr(rootEntry + 8));
}
else
{
m_offsetSize = U8(signature + 9);
m_lengthSize = U8(signature + 10);
m_base = U64(signature + 12);
m_root = Resolve(U64(signature + 36));
}
if (m_root == 0 || m_root >= m_data.size())
{
error = L"HDF5 超级块损坏。";
return false;
}
return true;
}
void MatHdf5::Close()
{
m_data.clear();
m_base = 0;
m_root = 0;
m_heaps.clear();
}
void MatHdf5::ReadMessages(std::uint64_t address, std::vector<Message>& messages) const
{
messages.clear();
if (address + 8 > m_data.size())
{
return;
}
if (U8(address) == 1)
{
const std::uint16_t count = U16(address + 2);
const std::uint32_t headerSize = U32(address + 8);
// 先收集消息块:主块(长度 = 对象头里的 headerSize)+ 若干续块(续块里没有对象头前缀)。
// ⚠️ **主块长度必须用 headerSize 限定**:消息数超过主块容量时,其余消息在续块里,
// 若在主块里继续按"消息数"往下读,就会读到块外的其它数据(字段全部解析失败)。
std::vector<std::pair<std::uint64_t, std::uint64_t>> blocks;
blocks.emplace_back(address + 16, headerSize);
std::size_t read = 0;
for (std::size_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex)
{
std::uint64_t offset = blocks[blockIndex].first;
const std::uint64_t end =
(blocks[blockIndex].second > 0) ? (offset + blocks[blockIndex].second) : 0;
while (read < count && offset + 8 <= m_data.size() && (end == 0 || offset + 8 <= end))
{
Message message;
message.type = U16(offset);
const std::uint16_t messageSize = U16(offset + 2);
offset += 8;
if (offset + messageSize > m_data.size())
{
break;
}
message.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(offset),
m_data.begin() + static_cast<std::ptrdiff_t>(offset + messageSize));
offset += (messageSize + 7u) & ~static_cast<std::uint16_t>(7u);
++read;
if (message.type == kMessageContinuation && message.data.size() >= 16)
{
const std::uint64_t blockAddress = Resolve(ReadLE(message.data.data(), 8));
const std::uint64_t blockSize = ReadLE(message.data.data() + 8, 8);
if (blockAddress != 0)
{
blocks.emplace_back(blockAddress, blockSize);
}
}
messages.push_back(std::move(message));
}
}
}
}
void MatHdf5::WalkBtree(std::uint64_t node, std::uint64_t heapData,
std::vector<std::pair<std::string, std::uint64_t>>& out) const
{
int guard = 0;
while (node != 0 && node + 24 < m_data.size() && guard++ < 100000)
{
if (std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
{
return;
}
const std::uint8_t level = U8(node + 5);
const std::uint16_t entries = U16(node + 6);
std::uint64_t offset = node + 8 + 8 + 8;
std::vector<std::uint64_t> children;
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
{
offset += 8; // key(分组 B-tree 的 key 是堆偏移)
if (index < entries)
{
children.push_back(Resolve(U64(offset)));
offset += 8;
}
}
for (const std::uint64_t child : children)
{
if (level > 0)
{
WalkBtree(child, heapData, out);
}
else
{
WalkSymbolNode(child, heapData, out);
}
}
node = Resolve(U64(node + 8)); // 右兄弟
}
}
void MatHdf5::WalkSymbolNode(std::uint64_t node, std::uint64_t heapData,
std::vector<std::pair<std::string, std::uint64_t>>& out) const
{
if (node == 0 || node + 8 > m_data.size() ||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "SNOD", 4) != 0 || heapData == 0)
{
return;
}
const std::uint16_t count = U16(node + 6);
std::uint64_t offset = node + 8;
for (std::uint16_t index = 0; index < count; ++index)
{
if (offset + 40 > m_data.size())
{
return;
}
const std::uint64_t nameOffset = U64(offset);
const std::uint64_t objectAddress = Resolve(U64(offset + 8));
offset += 40;
if (objectAddress == 0 || heapData + nameOffset >= m_data.size())
{
continue;
}
const char* name = reinterpret_cast<const char*>(m_data.data() + heapData + nameOffset);
out.emplace_back(std::string(name), objectAddress);
}
}
std::vector<std::pair<std::string, std::uint64_t>> MatHdf5::Members(std::uint64_t address) const
{
std::vector<std::pair<std::string, std::uint64_t>> out;
std::vector<Message> messages;
ReadMessages(address, messages);
for (const Message& message : messages)
{
if (message.type == kMessageSymbolTable && message.data.size() >= 16)
{
const std::uint64_t btree = Resolve(ReadLE(message.data.data(), 8));
const std::uint64_t heap = Resolve(ReadLE(message.data.data() + 8, 8));
std::uint64_t heapData = 0;
if (heap + 32 <= m_data.size() &&
std::memcmp(&m_data[static_cast<std::size_t>(heap)], "HEAP", 4) == 0)
{
heapData = Resolve(U64(heap + 24));
bool known = false;
for (const auto& entry : m_heaps)
{
if (entry.first == heap)
{
known = true;
break;
}
}
if (!known)
{
m_heaps.emplace_back(heap, heapData);
}
}
WalkBtree(btree, heapData, out);
}
else if (message.type == kMessageLink && message.data.size() >= 8)
{
std::size_t offset = 2;
const std::uint8_t flags = message.data[1];
if (flags & 0x08)
{
offset += 8;
}
if (flags & 0x04)
{
offset += 8;
}
if (flags & 0x10)
{
++offset;
}
if (offset >= message.data.size())
{
continue;
}
const std::uint8_t lengthSize = message.data[offset++];
std::uint64_t nameLength = 0;
for (std::uint8_t byte = 0; byte < lengthSize && offset < message.data.size(); ++byte)
{
nameLength |= static_cast<std::uint64_t>(message.data[offset + byte]) << (8 * byte);
}
offset += lengthSize;
if (offset + nameLength + 8 > message.data.size())
{
continue;
}
std::string name(reinterpret_cast<const char*>(message.data.data() + offset),
static_cast<std::size_t>(nameLength));
offset += static_cast<std::size_t>(nameLength);
const std::uint64_t target = Resolve(ReadLE(message.data.data() + offset, 8));
out.emplace_back(std::move(name), target);
}
}
return out;
}
std::uint64_t MatHdf5::Member(std::uint64_t address, const std::string& name) const
{
for (const auto& entry : Members(address))
{
if (entry.first == name)
{
return entry.second;
}
}
return 0;
}
bool MatHdf5::IsDataset(std::uint64_t address) const
{
std::vector<Message> messages;
ReadMessages(address, messages);
for (const Message& message : messages)
{
if (message.type == kMessageDataspace)
{
return true;
}
}
return false;
}
bool MatHdf5::ParseType(const std::uint8_t* data, std::size_t size, std::size_t offset, Type& out,
std::size_t& consumed) const
{
if (offset + 8 > size)
{
return false;
}
out.version = data[offset] >> 4;
out.cls = static_cast<TypeClass>(data[offset] & 0x0F);
const std::uint8_t bits0 = data[offset + 1];
out.size = ReadLE(data + offset + 4, 4);
std::size_t cursor = offset + 8;
if (out.cls == TypeClass::Integer)
{
out.bigEndian = (bits0 & 0x01) != 0;
out.isSigned = (bits0 & 0x08) != 0;
if (out.version >= 3)
{
cursor += 2; // bit offset
cursor += 2; // precision
}
else
{
cursor += 4;
}
}
else if (out.cls == TypeClass::Float)
{
out.bigEndian = (bits0 & 0x01) != 0;
cursor += 12;
}
else if (out.cls == TypeClass::String)
{
// 无需额外属性
}
else if (out.cls == TypeClass::Reference)
{
// 无需额外属性
}
else if (out.cls == TypeClass::VariableLength)
{
out.isVariableString = (bits0 & 0x0F) == 1;
std::size_t childConsumed = 0;
Type child;
if (ParseType(data, size, cursor, child, childConsumed))
{
cursor += childConsumed;
}
}
else if (out.cls == TypeClass::Compound)
{
const unsigned members = static_cast<unsigned>(bits0) | (static_cast<unsigned>(data[offset + 2]) << 8);
for (unsigned index = 0; index < members; ++index)
{
if (cursor >= size)
{
break;
}
const std::size_t nameStart = cursor;
while (cursor < size && data[cursor] != 0)
{
++cursor;
}
std::string name(reinterpret_cast<const char*>(data + nameStart), cursor - nameStart);
++cursor; // 结束符
cursor = (cursor + 7) & ~static_cast<std::size_t>(7);
if (out.version == 3)
{
// v3:先维度(1 字节 rank + 4 字节保留),再字节偏移(可变)
}
cursor += 4; // 字节偏移(v1/v2)
std::size_t childConsumed = 0;
Type child;
if (ParseType(data, size, cursor, child, childConsumed))
{
cursor += childConsumed;
out.members.emplace_back(std::move(name), std::move(child));
}
else
{
break;
}
}
}
else if (out.cls == TypeClass::Array)
{
if (cursor + 4 <= size)
{
const std::uint8_t rank = data[cursor];
cursor += 4;
cursor += static_cast<std::size_t>(rank) * 4; // 维度
cursor += static_cast<std::size_t>(rank) * 4; // 排列
std::size_t childConsumed = 0;
Type child;
if (ParseType(data, size, cursor, child, childConsumed))
{
cursor += childConsumed;
}
}
}
consumed = cursor - offset;
return true;
}
bool MatHdf5::DecodeChunked(const Dataset& dataset, std::size_t elementSize,
std::vector<std::uint8_t>& out) const
{
if (dataset.chunkAddress == 0 || dataset.chunkDims.empty() || elementSize == 0 ||
dataset.dims.empty())
{
return false;
}
// ⚠️ MATLAB 写出的分块维度可能与数据空间维度**维数不一致**(实测:1 维数据集的
// 分块维度是 `[N, 1]`)。把维度数组在**右侧补 1**(行优先的最快轴)对齐即可,
// 二者的元素总数与遍历顺序都不变。
std::vector<std::size_t> dims = dataset.dims;
std::vector<std::size_t> chunkDims = dataset.chunkDims;
while (dims.size() < chunkDims.size())
{
dims.push_back(1);
}
while (chunkDims.size() < dims.size())
{
chunkDims.push_back(1);
}
const std::size_t rank = dims.size();
const std::uint64_t total = [&]() -> std::uint64_t
{
std::uint64_t value = 1;
for (const std::size_t dim : dims)
{
value *= dim;
}
return value;
}();
out.assign(static_cast<std::size_t>(total) * elementSize, 0);
std::vector<std::uint8_t> chunk;
std::vector<std::uint8_t> inflated;
// 遍历分块 B-tree:key = 分块大小 + 过滤器掩码 + 各维偏移。
std::vector<std::uint64_t> stack{dataset.chunkAddress};
int guard = 0;
while (!stack.empty() && guard++ < 1000000)
{
const std::uint64_t node = stack.back();
stack.pop_back();
if (node == 0 || node + 24 > m_data.size() ||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
{
continue;
}
const std::uint8_t level = U8(node + 5);
const std::uint16_t entries = U16(node + 6);
const std::size_t keySize = 8 + 8 * rank;
std::uint64_t offset = node + 8 + 8 + 8;
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
{
const std::uint32_t chunkSize = (offset + 4 <= m_data.size()) ? U32(offset) : 0;
std::vector<std::uint64_t> chunkOffset(rank, 0);
for (std::size_t axis = 0; axis < rank; ++axis)
{
chunkOffset[axis] = U64(offset + 8 + axis * 8);
}
offset += keySize;
if (index < entries)
{
const std::uint64_t child = Resolve(U64(offset));
offset += 8;
if (level > 0)
{
stack.push_back(child);
}
else if (child != 0 && chunkSize > 0)
{
chunk.assign(m_data.begin() + static_cast<std::ptrdiff_t>(child),
m_data.begin() + static_cast<std::ptrdiff_t>(child + chunkSize));
const std::uint8_t* source = chunk.data();
std::size_t sourceSize = chunk.size();
if (dataset.deflate)
{
// 分块未压缩时大小 = 分块元素数 × 元素大小:此时不需要解压。
std::size_t expected = elementSize;
for (const std::size_t dim : dataset.chunkDims)
{
expected *= dim;
}
if (chunk.size() != expected)
{
// 内置 inflate(不依赖 zlib,见文件头的说明)。
if (!Inflate(chunk.data(), chunk.size(), inflated))
{
continue;
}
if (inflated.size() < expected)
{
inflated.resize(expected, 0);
}
source = inflated.data();
sourceSize = expected;
}
}
// 把该块拷进输出缓冲区(按行优先展开)。
const std::size_t rowBytes = chunkDims[rank - 1] * elementSize;
std::size_t rows = 1;
for (std::size_t axis = 0; axis + 1 < rank; ++axis)
{
rows *= chunkDims[axis];
}
for (std::size_t row = 0; row < rows; ++row)
{
std::vector<std::size_t> coordinate(rank - 1, 0);
std::size_t remainder = row;
for (std::size_t axis = rank - 1; axis-- > 0;)
{
coordinate[axis] = remainder % chunkDims[axis];
remainder /= chunkDims[axis];
}
std::uint64_t targetRow = 0;
bool inside = true;
std::uint64_t stride = 1;
for (std::size_t axis = rank - 1; axis-- > 0;)
{
const std::uint64_t index = chunkOffset[axis] + coordinate[axis];
if (index >= dims[axis])
{
inside = false;
break;
}
targetRow += index * stride;
stride *= dims[axis];
}
if (!inside)
{
continue;
}
const std::size_t sourceOffset = row * rowBytes;
const std::size_t targetOffset =
static_cast<std::size_t>(targetRow) * dims[rank - 1] * elementSize;
if (sourceOffset + rowBytes > sourceSize ||
targetOffset + rowBytes > out.size())
{
continue;
}
std::memcpy(out.data() + targetOffset, source + sourceOffset, rowBytes);
}
}
}
}
}
return true;
}
bool MatHdf5::ReadDataset(std::uint64_t address, Dataset& out) const
{
out = Dataset();
std::vector<Message> messages;
ReadMessages(address, messages);
bool haveSpace = false;
bool haveType = false;
std::size_t compactSize = 0;
std::uint64_t dataAddress = 0;
std::uint64_t dataSize = 0;
for (const Message& message : messages)
{
if (message.type == kMessageDataspace && message.data.size() >= 8)
{
const std::uint8_t version = message.data[0];
const std::uint8_t rank = message.data[1];
std::size_t offset = (version == 1) ? 8 : 4;
out.dims.assign(rank, 0);
for (std::uint8_t axis = 0; axis < rank && offset + 8 <= message.data.size(); ++axis)
{
out.dims[axis] = static_cast<std::size_t>(ReadLE(&message.data[offset], 8));
offset += 8;
}
haveSpace = true;
}
else if (message.type == kMessageDatatype)
{
std::size_t consumed = 0;
haveType = ParseType(message.data.data(), message.data.size(), 0, out.type, consumed);
}
else if (message.type == kMessageLayout && message.data.size() >= 8)
{
const std::uint8_t version = message.data[0];
const std::uint8_t klass = message.data[1];
out.layout = static_cast<LayoutClass>(klass);
if (klass == 0)
{
// 紧凑:数据就放在消息里
if (version == 3)
{
compactSize = static_cast<std::size_t>(message.data[2]) |
(static_cast<std::size_t>(message.data[3]) << 8);
}
else
{
compactSize = static_cast<std::size_t>(ReadLE(&message.data[2], 4));
}
}
else if (klass == 1)
{
dataAddress = Resolve(ReadLE(&message.data[2], 8));
dataSize = ReadLE(&message.data[10], 8);
}
else if (klass == 2)
{
if (version == 3)
{
const std::uint8_t rank = message.data[2];
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
out.chunkDims.assign(rank, 0);
for (std::uint8_t axis = 0; axis < rank; ++axis)
{
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
}
}
else
{
const std::uint8_t rank = message.data[2];
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
out.chunkDims.assign(rank, 0);
for (std::uint8_t axis = 0; axis < rank; ++axis)
{
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
}
}
}
}
else if (message.type == kMessageFilter && message.data.size() >= 2)
{
const int count = message.data[1];
std::size_t offset = 8;
for (int index = 0; index < count && offset + 8 <= message.data.size(); ++index)
{
const std::uint16_t id = static_cast<std::uint16_t>(message.data[offset]) |
(static_cast<std::uint16_t>(message.data[offset + 1]) << 8);
const std::uint16_t nameLength = static_cast<std::uint16_t>(message.data[offset + 2]) |
(static_cast<std::uint16_t>(message.data[offset + 3]) << 8);
const std::uint16_t values =
static_cast<std::uint16_t>(message.data[offset + 6]) |
(static_cast<std::uint16_t>(message.data[offset + 7]) << 8);
offset += 8;
offset += (nameLength + 7) & ~static_cast<std::size_t>(7);
offset += static_cast<std::size_t>(values) * 4;
if (id == 1)
{
out.deflate = true;
}
}
}
}
if (!haveSpace || !haveType || out.type.size == 0)
{
return false;
}
const std::size_t elementSize = out.type.size;
if (out.layout == LayoutClass::Compact)
{
// 紧凑数据在消息里,重新扫描一次取出字节。
for (const Message& message : messages)
{
if (message.type == kMessageLayout)
{
const std::size_t offset = (message.data[0] == 3) ? 4 : 8;
if (offset + compactSize <= message.data.size())
{
out.data.assign(message.data.begin() + static_cast<std::ptrdiff_t>(offset),
message.data.begin() +
static_cast<std::ptrdiff_t>(offset + compactSize));
}
}
}
}
else if (out.layout == LayoutClass::Contiguous)
{
if (dataAddress != 0 && dataAddress + dataSize <= m_data.size())
{
out.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(dataAddress),
m_data.begin() + static_cast<std::ptrdiff_t>(dataAddress + dataSize));
}
}
else if (out.layout == LayoutClass::Chunked)
{
if (!DecodeChunked(out, elementSize, out.data))
{
return false;
}
}
out.ok = true;
return true;
}
std::string MatHdf5::AttributeString(std::uint64_t address, const char* name) const
{
std::vector<Message> messages;
ReadMessages(address, messages);
for (const Message& message : messages)
{
if (message.type != kMessageAttribute || message.data.size() < 8)
{
continue;
}
const std::uint8_t version = message.data[0];
const std::size_t headerSize = (version == 1) ? 8 : (version == 2 ? 8 : 9);
const std::uint16_t nameSize = static_cast<std::uint16_t>(message.data[2]) |
(static_cast<std::uint16_t>(message.data[3]) << 8);
const std::uint16_t typeSize = static_cast<std::uint16_t>(message.data[4]) |
(static_cast<std::uint16_t>(message.data[5]) << 8);
const std::uint16_t spaceSize = static_cast<std::uint16_t>(message.data[6]) |
(static_cast<std::uint16_t>(message.data[7]) << 8);
std::size_t offset = headerSize;
if (offset + nameSize > message.data.size())
{
continue;
}
std::string attributeName(reinterpret_cast<const char*>(message.data.data() + offset),
nameSize);
while (!attributeName.empty() && attributeName.back() == '\0')
{
attributeName.pop_back();
}
offset += (nameSize + 7) & ~static_cast<std::size_t>(7);
const std::size_t typeOffset = offset;
offset += (typeSize + 7) & ~static_cast<std::size_t>(7);
const std::size_t spaceOffset = offset;
offset += (spaceSize + 7) & ~static_cast<std::size_t>(7);
if (attributeName != name)
{
continue;
}
if (offset > message.data.size())
{
return std::string();
}
Type type;
std::size_t consumed = 0;
if (!ParseType(message.data.data(), message.data.size(), typeOffset, type, consumed))
{
return std::string();
}
std::size_t count = 1;
if (spaceOffset + 4 <= message.data.size())
{
const std::uint8_t spaceVersion = message.data[spaceOffset];
const std::uint8_t rank = message.data[spaceOffset + 1];
std::size_t dimOffset = spaceOffset + ((spaceVersion == 1) ? 8 : 4);
count = 1;
for (std::uint8_t axis = 0; axis < rank && dimOffset + 8 <= message.data.size(); ++axis)
{
count *= static_cast<std::size_t>(ReadLE(&message.data[dimOffset], 8));
dimOffset += 8;
}
}
std::string text;
if (type.cls == TypeClass::String || type.cls == TypeClass::Integer)
{
text.assign(reinterpret_cast<const char*>(message.data.data() + offset),
std::min(message.data.size() - offset, std::max<std::size_t>(count, 1)));
while (!text.empty() && text.back() == '\0')
{
text.pop_back();
}
}
return text;
}
return std::string();
}
bool MatHdf5::ReadText(std::uint64_t address, std::wstring& out) const
{
out.clear();
Dataset dataset;
if (!ReadDataset(address, dataset))
{
return false;
}
if (dataset.type.cls == TypeClass::String)
{
out.assign(dataset.data.begin(), dataset.data.end());
while (!out.empty() && out.back() == L'\0')
{
out.pop_back();
}
return true;
}
if (dataset.type.size == 2)
{
for (std::size_t index = 0; index + 1 < dataset.data.size(); index += 2)
{
const wchar_t character = static_cast<wchar_t>(
static_cast<std::uint16_t>(dataset.data[index]) |
(static_cast<std::uint16_t>(dataset.data[index + 1]) << 8));
if (character != 0)
{
out.push_back(character);
}
}
return true;
}
if (dataset.type.size == 1)
{
for (const std::uint8_t byte : dataset.data)
{
if (byte != 0)
{
out.push_back(static_cast<wchar_t>(byte));
}
}
return true;
}
return false;
}
bool MatHdf5::ReadNumbers(std::uint64_t address, std::vector<double>& out) const
{
out.clear();
Dataset dataset;
if (!ReadDataset(address, dataset) || !dataset.ok)
{
return false;
}
const std::size_t elementSize = dataset.type.size;
if (elementSize == 0 || dataset.data.size() % elementSize != 0)
{
return false;
}
const std::size_t count = dataset.data.size() / elementSize;
out.resize(count);
const bool integer = (dataset.type.cls == TypeClass::Integer);
const bool floating = (dataset.type.cls == TypeClass::Float);
if (!integer && !floating)
{
return false;
}
for (std::size_t index = 0; index < count; ++index)
{
const std::uint8_t* source = dataset.data.data() + index * elementSize;
if (floating && elementSize == 8)
{
double value = 0.0;
std::memcpy(&value, source, sizeof(value));
out[index] = value;
}
else if (floating && elementSize == 4)
{
float value = 0.0f;
std::memcpy(&value, source, sizeof(value));
out[index] = static_cast<double>(value);
}
else if (integer && dataset.type.isSigned)
{
std::int64_t value = 0;
for (std::size_t byte = 0; byte < elementSize; ++byte)
{
value |= static_cast<std::int64_t>(source[byte]) << (8 * byte);
}
// 符号扩展
const int bits = static_cast<int>(elementSize) * 8;
if (bits < 64 && (value & (1ll << (bits - 1))) != 0)
{
value |= ~((1ll << bits) - 1);
}
out[index] = static_cast<double>(value);
}
else if (integer)
{
std::uint64_t value = 0;
for (std::size_t byte = 0; byte < elementSize; ++byte)
{
value |= static_cast<std::uint64_t>(source[byte]) << (8 * byte);
}
out[index] = static_cast<double>(value);
}
}
return true;
}
bool MatHdf5::ReadReferences(std::uint64_t address, std::vector<std::uint64_t>& out) const
{
out.clear();
Dataset dataset; if (!ReadDataset(address, dataset) || !dataset.ok ||
dataset.type.cls != TypeClass::Reference)
{
return false;
}
const std::size_t count = dataset.data.size() / 8;
out.reserve(count);
for (std::size_t index = 0; index < count; ++index)
{
const std::uint64_t raw = ReadLE(dataset.data.data() + index * 8, 8);
out.push_back(Resolve(raw));
}
return true;
}
} // namespace jdi