reckfellENGINE

Первое нативное расширение на C17

Полный Windows x64 пример: компиляция, регистрация и проверка ABI.

На этой странице

Этот пример подключает C17-библиотеку, регистрирует абстрактный класс DocsCProbe с базой Entity и две константы, затем проверяет их из C#. Движение спрайта, свойства экземпляров и сигналы здесь не реализуются: сначала получаем небольшой полностью проверяемый ABI-контракт.

Условия и структура

Проверенная цель — Windows x64 с текущим ExtensionSDK 1.0.0, конфигурацией float_64 и MSVC с поддержкой C17. Для проверки запуска нужен C# проект с SDK Reckfell 1.0.0. Само расширение написано на C и не требует C++-обёртки.

Расположите файлы так:

Текст
project/
  project.reckfell
  NativeCChecks.cs
  addons/c/
    native_c_probe.c
    native_c_probe.reckfellextension
    native_c_probe.windows.x86_64.dll

Заголовок reckfellextension_interface.h берите из ExtensionSDK/include той сборки, в которой будете запускать DLL. Данные размеров — из парного extension_api.json; в этом примере StringName занимает 8 байт. Не переносите этот размер на другой ABI без проверки.

native_c_probe.c

C++
#include "reckfellextension_interface.h"

// This example is deliberately Windows x64 only, not a general C binding.
typedef struct { void *opaque; } NativeStringName;
_Static_assert(sizeof(NativeStringName) == 8, "Check StringName size against extension_api.json");
_Static_assert(RECKFELLEXTENSION_VARIANT_TYPE_ENTITY_PATH == 22, "Unexpected EntityPath ABI");

static ReckfellExtensionClassLibraryPtr library;
static ReckfellExtensionInterfaceStringNameNewWithUtf8Chars name_new;
static ReckfellExtensionPtrDestructor name_destroy;
static ReckfellExtensionInterfaceClassdbRegisterExtensionClass6 register_class;
static ReckfellExtensionInterfaceClassdbUnregisterExtensionClass unregister_class;
static ReckfellExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant register_constant;

static void free_instance(void *userdata, ReckfellExtensionClassInstancePtr instance) {
    (void)userdata;
    (void)instance;
}
static void initialize(void *userdata, ReckfellExtensionInitializationLevel level) {
    (void)userdata;
    if (level != RECKFELLEXTENSION_INITIALIZATION_SCENE) return;
    NativeStringName name, parent, enum_name, constant;
    name_new(&name, "DocsCProbe");
    name_new(&parent, "Entity");
    name_new(&enum_name, "");
    ReckfellExtensionClassCreationInfo6 info = {0};
    info.is_abstract = 1;
    info.is_exposed = 1;
    info.free_instance_func = free_instance;
    register_class(library, &name, &parent, &info);
    name_new(&constant, "ProductVersion");
    register_constant(library, &name, &enum_name, &constant, 10000, 0);
    name_destroy(&constant);
    name_new(&constant, "EntityPathVariant");
    register_constant(library, &name, &enum_name, &constant, 22, 0);
    name_destroy(&constant);
    name_destroy(&enum_name);
    name_destroy(&parent);
    name_destroy(&name);
}
static void deinitialize(void *userdata, ReckfellExtensionInitializationLevel level) {
    (void)userdata;
    if (level != RECKFELLEXTENSION_INITIALIZATION_SCENE) return;
    NativeStringName name;
    name_new(&name, "DocsCProbe");
    unregister_class(library, &name);
    name_destroy(&name);
}
__declspec(dllexport) ReckfellExtensionBool reckfell_docs_c_init(
    ReckfellExtensionInterfaceGetProcAddress get_proc,
    ReckfellExtensionClassLibraryPtr native_library,
    ReckfellExtensionInitialization *initialization) {
    library = native_library;
    name_new = (ReckfellExtensionInterfaceStringNameNewWithUtf8Chars)get_proc("string_name_new_with_utf8_chars");
    register_class = (ReckfellExtensionInterfaceClassdbRegisterExtensionClass6)get_proc("classdb_register_extension_class6");
    unregister_class = (ReckfellExtensionInterfaceClassdbUnregisterExtensionClass)get_proc("classdb_unregister_extension_class");
    register_constant = (ReckfellExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)get_proc("classdb_register_extension_class_integer_constant");
    ReckfellExtensionInterfaceVariantGetPtrDestructor get_destructor = (ReckfellExtensionInterfaceVariantGetPtrDestructor)get_proc("variant_get_ptr_destructor");
    if (!name_new || !register_class || !unregister_class || !register_constant || !get_destructor) return 0;
    name_destroy = get_destructor(RECKFELLEXTENSION_VARIANT_TYPE_STRING_NAME);
    if (!name_destroy) return 0;
    initialization->minimum_initialization_level = RECKFELLEXTENSION_INITIALIZATION_SCENE;
    initialization->userdata = NULL;
    initialization->initialize = initialize;
    initialization->deinitialize = deinitialize;
    return 1;
}

Точка входа проверяет адреса всех нужных функций до использования. Callback выполняет регистрацию только на уровне Scene, а callback деинициализации снимает её. Каждый созданный временный StringName уничтожается соответствующим деструктором; это не C-строка и не указатель на произвольный UTF-8 буфер.

Info6 обнуляется перед заполнением. Класс абстрактный, поэтому create-instance callback здесь не нужен; free_instance не управляет игровыми экземплярами, так как они не создаются. Для конкретного инстанцируемого класса потребуется отдельная реализация создания, хранения и освобождения состояния. Не копируйте абстрактную заглушку как готовый игровой компонент.

native_c_probe.reckfellextension

Конфигурация
[configuration]
entry_symbol = "reckfell_docs_c_init"
compatibility_minimum = "1.0.0"
reloadable = false

[libraries]
windows.x86_64 = "./native_c_probe.windows.x86_64.dll"

Относительный путь DLL отсчитывается от этого файла. Символ должен точно совпадать с экспортом reckfell_docs_c_init из C-кода.

Соберите только расширение

Откройте x64 Native Tools Command Prompt от Visual Studio, перейдите в addons/c и замените путь SDK своим:

Текст
cl /nologo /TC /std:c17 /LD /W3 /WX /I"C:\Tools\Reckfell\ExtensionSDK\include" native_c_probe.c /Fe:native_c_probe.windows.x86_64.dll

Ожидается DLL с указанным именем. /TC выбирает компиляцию как C, а не C++; /LD создаёт библиотеку. Эта команда не собирает и не изменяет движок. Файл .c не требуется игре после сборки, но описание и DLL должны быть доступны.

NativeCChecks.cs: проверка из проекта

Создайте этот файл в C# проекте и прикрепите к Entity запускаемого уровня. Скрипт проверяет класс и константы, выгружает учебное расширение и завершает приложение; это диагностический уровень, не основной игровой цикл.

C#
using Reckfell;
using System;

public partial class NativeCChecks : Entity
{
    private static void Check(bool ok, string name)
    {
        if (!ok) throw new Exception(name);
        ReckfellApi.Print("NATIVEC_CHECK " + name);
    }
    public override void _Ready()
    {
        int code = 0;
        try
        {
            foreach (string build in new[] { "c" })
            {
                string path = "asset://addons/" + build + "/native_c_probe.reckfellextension";
                var status = ReckfellExtensionManager.LoadExtension(path);
                Check(status is ReckfellExtensionManager.LoadStatus.Ok or ReckfellExtensionManager.LoadStatus.AlreadyLoaded, build + ":load-or-already-loaded");
                Check(ClassDB.ClassExists("DocsCProbe") && ClassDB.GetParentClass("DocsCProbe") == "Entity", build + ":registered-entity-derived-class");
                Check(!ClassDB.CanInstantiate("DocsCProbe"), build + ":abstract-class-not-instantiable");
                Check(ClassDB.ClassGetIntegerConstant("DocsCProbe", "ProductVersion") == 10000 && ClassDB.ClassGetIntegerConstant("DocsCProbe", "EntityPathVariant") == 22,
                    build + ":registered-constants-cross-abi");
                Check(ReckfellExtensionManager.IsExtensionLoaded(path) && Array.IndexOf(ReckfellExtensionManager.GetLoadedExtensions(), path) >= 0, build + ":loaded-path-visible");
                Check(ReckfellExtensionManager.LoadExtension(path) == ReckfellExtensionManager.LoadStatus.AlreadyLoaded, build + ":repeat-load-status");
                Check(ReckfellExtensionManager.UnloadExtension(path) == ReckfellExtensionManager.LoadStatus.Ok, build + ":unload-ok");
                Check(!ClassDB.ClassExists("DocsCProbe") && !ReckfellExtensionManager.IsExtensionLoaded(path), build + ":unload-removes-class-and-path");
            }
        }
        catch (Exception e) { ReckfellApi.PushError(e.ToString()); code = 1; }
        if (code == 0) ReckfellApi.Print("NATIVEC_PASS");
        GetTree().Quit(code);
    }
}

Ожидается восемь строк NATIVEC_CHECK и затем NATIVEC_PASS. Константы должны равняться 10000 и 22. Первый статус может быть AlreadyLoaded, если редактор уже автоматически загрузил описание. После выгрузки класс исчезает из ClassDB; его нельзя создавать или использовать дальше.

Разбор ошибок

ЭтапЧто проверить
Компилятор не находит заголовокПуть /I к текущему SDK
Ошибка имени C-типа или поля Info6Заголовок соответствует коду и целевой сборке
LoadExtension вернул FailedЖурнал, путь описания, архитектура DLL и экспорт символа
Класс не зарегистрированНайдены все функции, callback Scene выполнен, имя класса совпадает
Константа отличаетсяЗагружена нужная DLL, а не старая копия; имена и значения совпадают

Проверено: компиляция опубликованного C17-файла, загрузка в движке, регистрация класса/констант, повторная загрузка и удаление регистрации. Не проверено этим примером: конкретные экземпляры, методы/свойства/сигналы, hot reload, экспорт и другие платформы. Для их добавления расширяйте пример по одному контракту и проверяйте вызов, владение и очистку.

Далее: описание загрузки, данные интерфейса, API менеджера.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.