Ориентация главы
Последнее семейство тома 11: xruntime — расширение модели времени исполнения поверх XRT (описания типов/объекты/вызовы/typed-контейнеры), дающее языкам-хозяевам (скриптовые движки, плагинные системы, фреймворки сериализации) слой фактов о типах, общий для C и XRT. Глава разбирает фундамент: описание типа (xrttype — полная структура описания Id/Kind/ABI-имя/размер-выравнивание/таблица операций/наследование/метаданные полей-методов); идентичность типов (xrtTypeId(ABI 名) (ABI-имя) выводит стабильный ненулевой ID — никогда произвольно назначенное целое; TypeSame сравнивает ID + ABI-имя, TypeIsA идёт по цепочке одиночного наследования); 22 вида времени исполнения (стабильные категории хранения от INVALID до CLASS/ENUM — описывают хранение, не синтаксис); реестр и преобразования (реестр типов как процессный набор фактов; проверка преобразуемости и преобразование значений в runtime_convert). Ключевая дисциплина: модули не владеют описаниями — все описания стабильны по адресу и неизменяемы на время регистрации и запросов (заимствованные метаданные, жизнь на весь процесс).
114.1引入
Зачем языку-хозяину «слой фактов о типах»? Представьте скриптовый движок, встроенный в C: класс Counter из скрипта должен печататься C-отладчиком (нужны имя типа и таблица полей), обходиться фреймворком сериализации (нужны операции над значением: копия/сравнение/хеш/деструктор), отслеживаться сборщиком мусора (нужно перечисление сильных ссылок), наследоваться другим скриптовым модулем (нужна цепочка базовых классов) — каждая способность требует структурных знаний о типе, а switch(type), разбросанный повсюду, — ад сопровождения. xrttype стандартизирует эти знания в одну таблицу описания: скриптовый движок её регистрирует, контейнеры XRT потребляют, отладчики читают — три стороны делят один набор фактов.
Два ключевых проектных решения заслуживают внимания. Первое: ID выводится из ABI-имени — xrtTypeId("app.Counter") порождает стабильный ID, и Id пользовательского типа обязан равняться этому результату — межмодульная, межпроцессная эквивалентность типов имеет опору (произвольные целые сталкиваются уже при загрузке второго модуля). Второе: описания заимствованы, не владеемы — модули не держат память дескрипторов; вызывающий гарантирует стабильность адреса и неизменность на время регистрации и запросов — статические описания на жизнь процесса (естественная форма C) бесплатны, а динамически порождённые описания живут под управлением хозяина.
114.2概念
Описание типа: полная таблица полей
| Поле | Контракт |
|---|---|
Id/AbiName | стабильный ID (обязан равняться xrtTypeId(AbiName)) и непустое, межмодульно уникальное каноническое имя |
Kind | один из 22 стабильных категорий хранения |
Name | непустое отображаемое имя (пригодно для локализованного показа) |
Size/Align | размер и степень-двойки выравнивание значения C-ABI |
InstanceSize/InstanceAlign | размер и выравнивание нагрузки heap-экземпляра (для ссылочных типов объявляется раздельно) |
Ops | таблица операций жизненного цикла значения (копия/сравнение/хеш/формат/трассировка сильных ссылок — опционально) |
InstanceOps | Init/Drop/Trace нагрузки экземпляра (опционально — для объектов главы 115) |
Base | базовый класс (наследуется только CLASS; FINAL наследовать нельзя; глубина ≤ 255) |
Arguments/Fields/Methods/Metadata | заимствованные родовые аргументы/таблица полей/таблица методов/свои метаданные |
Жёсткое правило ссылочных типов: Size == sizeof(ptr), а Align равен выравниванию указателя — ссылка в C ABI есть ровно один указатель, а нагрузка объекта описывается отдельно через InstanceSize (двухслойное разделение значения и экземпляра — фундамент объектной модели главы 115). xrtTypeValidate полностью проверяет описание — нелегальное описание в систему не попадает (ещё один случай перехвата на старте).
22 категории: категории хранения, не синтаксиса
xrttypekind выражает стабильные категории хранения времени исполнения — «какова форма этого значения в памяти», не видимость в языке и не синтаксические алиасы (public/private и typedef — заботы языкового слоя): INVALID/NULL/BOOL/SIGNED_INT/UNSIGNED_INT/FLOAT/STRING/BYTES/TIME/POINTER/CALLABLE/ARRAY/LIST/SET/DICT/RECORD/HANDLE/TYPE/FUTURE/CLASS/ENUM — весь спектр от примитивов до контейнеров (множество среди них) и ссылок на классы. Встроенные типы XRT (xrtTypeInt64()/xrtTypeString()/xrtTypeCallable() и др.) возвращают процессно-канонические описания — хозяевам, регистрирующим типы, также следует использовать процессные описания (требование «равенства указателей» контракта контейнеров главы 117).
Таблица операций над значением: шесть дел Ops
Ops описывает поведение значения (того, что хранится в C ABI): копия (атомарность при отказе), сравнение (порядок), хеш (согласован со сравнением), форматирование (отладочная печать), трассировка сильных ссылок (сотрудничество с GC — если значение держит ссылки на объекты, они должны перечисляться). InstanceOps описывает нагрузку экземпляра (Init/Drop/Trace) — разворачивается в главе 114. Две таблицы операций соответствуют двухслойному разделению значения/экземпляра.
Реестр: процессный набор фактов о типах
Семейство xrtTypeRegistryCreate (Add/At/FindId/FindName) — регистрация описаний типов и поиск по имени; RegistryFindName(AbiName) образца и есть «найти описание по каноническому имени» — потребление реестра. Реестр не копирует описания (заимствует) — жизненный цикл описания остаётся за объявителем.
Преобразования: runtime_convert
runtime_convert отвечает поверх фактов о типах «можно ли это значение превратить в тот тип»: xrtTypeCanConvert (преобразуемость — точное совпадение/расширение), xrtTypeCanWiden (числовое расширение вроде int32→int64), xrtTypeConvert (исполняет преобразование), xrtTypeToString/TypeFormat (значение → текст). Смысл слоя преобразований: динамическая типизация языка-хозяева («использовать это произвольное значение как int») получает декларативный фундамент — проверка и исполнение разделены, пути отказа явные.
Место xruntime в семействе расширений
114.3示例
Первая полная программа: регистрация типа и операции над значением
Программа ниже из examples/runtime/type — полный цикл описания, регистрации и операций над значением:
#include <stdio.h> #include <xruntime.h> /* 展示内建类型、值操作、注册表和协议描述验证的常用路径。 */ int main(void) { const xrttype* pType = xrtTypeInt64(); xrttyperegistry* pRegistry = xrtTypeRegistryCreate(); xrttype ProtocolType = { .Kind = XRT_TYPE_PROTOCOL, .Flags = XRT_TYPE_FLAG_REFERENCE | XRT_TYPE_FLAG_NULLABLE, .Name = XRT_STR_INIT("Printable"), .AbiName = XRT_STR_INIT("example.Printable"), .Size = sizeof(ptr), .Align = _Alignof(ptr), .InstanceSize = sizeof(ptr), .InstanceAlign = _Alignof(ptr) }; xrtprotocol Protocol = { &ProtocolType, 0u, NULL }; int64 iLeft = 7; int64 iRight = 11; int iCompare; uint64 iHash; ProtocolType.Id = xrtTypeId(ProtocolType.AbiName); if ( (pRegistry == NULL) || !xrtTypeValidate(pType) || !xrtTypeValidate(xrtTypeBool32()) || !xrtProtocolValidate(&Protocol) || !xrtTypeRegistryAdd(pRegistry, pType) || (xrtTypeRegistryAt(pRegistry, 0u) != pType) || (xrtTypeRegistryFindName(pRegistry, pType->AbiName) != pType) || !xrtTypeCompareValue(pType, &iLeft, &iRight, &iCompare) || !xrtTypeHashValue(pType, &iLeft, &iHash) ) { xrtTypeRegistryDestroy(pRegistry); return 1; } printf( "type=%.*s id=%llu size=%zu compare=%d hash=%llu\n", (int)pType->Name.Size, pType->Name.Data, (unsigned long long)pType->Id, pType->Size, iCompare, (unsigned long long)iHash ); if ( !xrtTypeRegistryRemove(pRegistry, pType) ) { xrtTypeRegistryDestroy(pRegistry); return 2; } xrtTypeRegistryDestroy(pRegistry); return 0; }
Что только что произошло. (1) Описание типа регистрируется в реестр, извлекается обоими путями RegistryAt(0) и FindName(AbiName) — поиск типа по каноническому имени есть стандартная поза стыковки модулей хозяина (скриптовый модуль A регистрирует Counter; модуль B берёт по имени). (2) xrtTypeCompareValue(&Type, &左, &右, &结果) (левое, правое, результат) — родовое сравнение значений, ведомое описанием типа: знать, каков тип значения, не нужно — решает Ops в таблице описания (хеш аналогично — фундамент динамических значений в хеш-контейнерах). (3) Печатаемые Id/Size приходят из описания — TypeValidate гарантирует их самосогласованность (Size=sizeof(ptr) у ссылочных и т.п.). Эта строка вывода — исполнимое доказательство «слоя фактов о типах»: код C, держа таблицу описания, обработал значение, которого не знает.
Вторая полная программа: слой преобразований
Вторая программа из examples/runtime/convert — форма потребления проверок преобразования:
#include <stdio.h> #include <xruntime.h> /* 展示无损拓宽和带范围检查的显式转换。 */ int main(void) { int16 iSource = -32000; int64 iWide = 0; double fSource = 127.75; int8 iNarrow = 0; bool bSource = true; int32 iBool32 = 0; if ( !xrtTypeConvert(xrtTypeInt16(), &iSource, xrtTypeInt64(), &iWide, XTYPE_CONVERT_WIDEN) || !xrtTypeConvert(xrtTypeFloat64(), &fSource, xrtTypeInt8(), &iNarrow, XTYPE_CONVERT_EXPLICIT) || !xrtTypeConvert(xrtTypeBool(), &bSource, xrtTypeBool32(), &iBool32, XTYPE_CONVERT_WIDEN) ) { return 1; } printf("wide=%lld narrow=%d bool32=%d\n", (long long)iWide, (int)iNarrow, (int)iBool32); return 0; }
Что только что произошло. (1) Формы проверок CanConvert/CanWiden — динамически-типовая проверка хозяина «можно ли так использовать это значение»; widen (без потерь) и обычный convert (возможно с потерями) — две разные проверки — точная семантика не смешивается. (2) Исполнение Convert — сперва проверка, потом исполнение, путь отказа явный (не «попробуем как-нибудь» неявное преобразование). (3) Образец convert_string — компаньон текстового направления (мост строка↔тип — конкретизация за контрактными карточками runtime_type_string/type_future). Это разделение проверка/исполнение — версия типов времени исполнения для «сперва проверь, потом переводи» из разбора чисел главы 26.
114.4契约
- Заимствование описаний: модули не владеют дескрипторами; адреса стабильны и неизменны на время регистрации и запросов; статические описания на жизнь процесса бесплатны.
- Вывод ID: Id пользовательского типа обязан равняться
xrtTypeId(AbiName); TypeSame сравнивает два фактора Id + ABI-имя. - Правила наследования: наследуется только CLASS; FINAL не наследуется; размер/выравнивание производного не меньше любого базового; глубина ≤ 255.
- Жёсткое правило ссылок: у ссылочных типов Size=sizeof(ptr), Align=выравнивание указателя; нагрузка описывается отдельно InstanceSize.
- Семантика категорий: 22 категории — стабильные категории хранения; они не выражают ни видимость в языке, ни синтаксические алиасы.
- Два слоя операций: Ops (значение: копия/сравнение/хеш/формат/трассировка) и InstanceOps (экземпляр: Init/Drop/Trace) раздельны.
- Валидация вперёд: TypeValidate проверяет полностью — нелегальные описания в систему не входят.
- Встроенные описания:
xrtTypeInt64()и др. возвращают процессно-канонические — операции контейнеров требуют равенства указателей описаний (глава 117). - Реестр: Add/Remove/At/FindName; описания не копируются.
- Преобразования: проверка CanConvert/CanWiden отделена от исполнения Convert; ToString/Format — текстовое направление.
- Обрезка:
XRUNTIME_FEATURE_RUNTIME_TYPEзависит только от core — минимальный фундамент.
Типичные ошибки
Симптом: два модуля с типами Id = 42 встречаются — TypeSame сталкивается, контейнеры и слой вызова ведут себя хаотично.
Причина: стабильность Id идёт от вывода из ABI-имени — одно каноническое имя даёт один Id (межмодульно, межпроцессно); произвольные целые такой гарантии не несут.
xrttype Type = { .Id = 42u, ... }; /* назначено наугад: столкновение предрешено */xrttype Type = {
.Id = xrtTypeId(XRT_STR_LITERAL("app.Counter")),
.AbiName = XRT_STR_INIT("app.Counter"), /* имя и Id в паре */
...
};Симптом: описания, взятые из реестра, висят — стековый кадр умер, память описания с ним.
Причина: регистрация заимствует без копирования — описания обязаны быть стабильными на жизнь процесса (статическое хранилище или управляемая хозяином долгоживущая куча).
void register_types(void) { xrttype T = { ... }; /* на стеке */ xrtTypeRegistryAdd(&Reg, &T); /* висячий по возврату */ }
static const 类型方法表 kCounterMethods[] = { ... }; static xrttype Type = { ... }; /* статическое хранилище */ /* или объект типа хозяина держит описание (жизнь совпадает с типом) */
Симптом: Validate отвергает; или позже объектный/контейнерный слой выходит за границы.
Причина: хранение ссылочного типа в C ABI — всегда ровно один указатель; Size/Align обязаны объявляться честно; размер нагрузки идёт в InstanceSize/InstanceAlign (двухслойное разделение нельзя смешивать).
.Id = ..., .Kind = XRT_TYPE_CLASS, .Size = sizeof(counter), .Align = _Alignof(counter), /* размер нагрузки попал в поля значения */
.Kind = XRT_TYPE_CLASS, .Size = sizeof(ptr), .Align = _Alignof(ptr), /* значение = указатель */ .InstanceSize = sizeof(counter), .InstanceAlign = _Alignof(counter), /* нагрузка */
Практические задания
Напечатайте Kind/Size/Align встроенных описаний xrtTypeInt64()/xrtTypeString()/xrtTypeCallable() — сверить с таблицей категорий. Критерии приёмки: форма хранения каждой категории сходится с таблицей; TypeValidate проходит всё.
Base(FINAL=false)→Middle→Leaf, три слоя CLASS: проверить цепочные решения TypeIsA (Leaf is-a Base), отказ наследования FINAL, границы глубины. Критерии приёмки: три решения is-a верны; нелегальное наследование отвергается на Validate/регистрации.
Определите ENUM-тип с нагрузкой (Ok/Error/WithValue — три case) + Metadata с таблицей имён case; реализуйте родительскую печать print_value(Type, Value) (switch Kind → диспетчеризация форматирования). Критерии приёмки: значения перечисления печатают имя case; принтер работает на смешанной нагрузке из пяти встроенных типов (ноль кода, специфичного для типа).
Итоги
| Тема | Кратко |
|---|---|
| Позиционирование | слой фактов о типах, общий для C/XRT/хозяев — единственный источник знаний о типах |
| Вывод ID | xrtTypeId(AbiName) стабилен и ненулевой; TypeSame = Id + ABI-имя, два фактора |
| 22 категории | стабильные категории хранения (формы значений) — не синтаксис языка |
| Двухслойные размеры | значение (Size/Align) и экземпляр (InstanceSize/Align) раздельны; значение ссылочного = указатель |
| Два слоя операций | Ops (значение) / InstanceOps (экземпляр) — зеркало двухслойных размеров |
| Наследование | только CLASS; FINAL не наследуется; глубина 255; is-a по одиночной цепи |
| Заимствование описаний | регистрация не копирует; стабильно на жизнь процесса; Validate вперёд |
| Встроенные описания | семейство xrtTypeInt64() процессно-канонично — контейнеры требуют равенства указателей |
| Реестр | Add/Remove/At/FindName — стыковка по каноническому имени |
| Преобразования | проверки CanConvert/CanWiden, исполнение Convert, ToString для текста |
📖 Справочники API в этой главе:xruntime-runtime_type (60 записей)xruntime-runtime_convert (6 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки