Ориентация главы

Последнее семейство тома 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таблица операций жизненного цикла значения (копия/сравнение/хеш/формат/трассировка сильных ссылок — опционально)
InstanceOpsInit/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 в семействе расширений

1Система типов (эта глава)слой фактов xrttype - 22 категории/таблицы операций/наследование/реестр/преобразования
2Объекты и графы объектов (глава 115)счётные объекты/слабые ссылки/сбор циклов в безопасных точках
3Динамические вызовы (глава 116)сигнатуры/кадры вызова/множественные возвраты - среда callable
4Typed-контейнеры (глава 117)семейство контейнеров, ведомое описаниями типов - контейнерная грань для значений хозяина

114.3示例

Первая полная программа: регистрация типа и операции над значением

Программа ниже из examples/runtime/type — полный цикл описания, регистрации и операций над значением:

extlibs/xruntime/examples/runtime/type/main.c源码 ↗
#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXRUNTIME_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xruntime.h impl.c extlibs/xruntime/examples/runtime/type/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出类型名、稳定 ID、大小、比较与散列结果的自检行)

Что только что произошло. (1) Описание типа регистрируется в реестр, извлекается обоими путями RegistryAt(0) и FindName(AbiName) — поиск типа по каноническому имени есть стандартная поза стыковки модулей хозяина (скриптовый модуль A регистрирует Counter; модуль B берёт по имени). (2) xrtTypeCompareValue(&Type, &左, &右, &结果) (левое, правое, результат) — родовое сравнение значений, ведомое описанием типа: знать, каков тип значения, не нужно — решает Ops в таблице описания (хеш аналогично — фундамент динамических значений в хеш-контейнерах). (3) Печатаемые Id/Size приходят из описания — TypeValidate гарантирует их самосогласованность (Size=sizeof(ptr) у ссылочных и т.п.). Эта строка вывода — исполнимое доказательство «слоя фактов о типах»: код C, держа таблицу описания, обработал значение, которого не знает.

Вторая полная программа: слой преобразований

Вторая программа из examples/runtime/convert — форма потребления проверок преобразования:

extlibs/xruntime/examples/runtime/convert/main.c源码 ↗
#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXRUNTIME_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xruntime.h impl.c extlibs/xruntime/examples/runtime/convert/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出转换判定与执行结果的自检行)

Что только что произошло. (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 — минимальный фундамент.

Типичные ошибки

Ловушка 1: произвольно назначенный Id у пользовательского типа:Ловушка 1: произвольно назначенный Id у пользовательского типа

Симптом: два модуля с типами 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 в паре */
	...
};
Ловушка 2: таблица описания на стеке ушла в реестр:Ловушка 2: таблица описания на стеке ушла в реестр

Симптом: описания, взятые из реестра, висят — стековый кадр умер, память описания с ним.

Причина: регистрация заимствует без копирования — описания обязаны быть стабильными на жизнь процесса (статическое хранилище или управляемая хозяином долгоживущая куча).

错误写法
void register_types(void) {
	xrttype T = { ... };            /* на стеке */
	xrtTypeRegistryAdd(&Reg, &T);   /* висячий по возврату */
}
正确写法
static const 类型方法表 kCounterMethods[] = { ... };
static xrttype Type = { ... };        /* статическое хранилище */
/* или объект типа хозяина держит описание (жизнь совпадает с типом) */
Ловушка 3: у ссылочного типа объявлен не-указательный Size:Ловушка 3: у ссылочного типа объявлен не-указательный Size

Симптом: 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/хозяев — единственный источник знаний о типах
Вывод IDxrtTypeId(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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки