classModule_Tutorial :public Module { public: Module_Tutorial() : Module("tutorial") { // module name, when used from das file ModuleLibrary lib(this); lib.addBuiltInModule();
Это структура разбирает данные о типе OT с помощью std::type_traits и устанавливает его свойства, на основе которых daScript знает, что можно/нельзя делать с типом.
Байндинг класса при перекомпиляции “увидит”, что теперь класс нетривиальный, и правильно переопределит его свойства. Теперь класс не может быть локальным, не может быть скопирован или перемещён:
Соотвественно, тот же код на daScript выдаст ошибки компиляции, при попытке создать класс без инициализации:
//можно создать класс на хипе:
var a <- new MyClass("hello heap")
printMyClass( *a ) //42
//можно создать временный объект с помощью using:
using("hello temp") <| $(varc5: MyClass explicit)
printMyClass(c5) //42
//можно создать временный объект на стеке и передать его в качестве параметра
//(конструктор класса ничем не отличается от обычной функции)
printMyClass(MyClass("hello temp"))
//нельзя создать объект, требующий перемещения (массив из одного элемента)
printMyClass([[MyClass()]])
//нельзя переместить объект
var c1 <- MyClass()
//нельзя скопировать объект
var c1 = MyClass()
daScript достаточно консервативно определяет, что объект нельзя копировать/перемещать, вообще говоря, если у класса есть конструктор копирования, то его можно разрешить копировать, если просто явно переопределить функцию canCopy в аннотации-обёртке класса
bool isCopyConstructable = std::is_copy_constructible<MyClass>::value; boolcanCopy()constoverride{ return isCopyConstructable; } boolcanClone()constoverride{ return isCopyConstructable; } boolcanMove()constoverride{ return isCopyConstructable; } //но нельзя создавать локальный переменные, так как они позволяет не инициализировать класс boolisLocal()constoverride{ returnfalse; }
Если можно построить объект из другого объекта, то можно и copy/clone/move?
//теперь можно copy/clone/move
var a <- new MyClass("hello heap")
var b = new MyClass()
*b = *a
*b := *a
*b <- *a
Также в unsafe блоке теперь можно делать небезопасные, но интересные штуки:
struct Params
a: MyClass
unsafe
//создаём локальную переменную на стеке
var m = MyClass("hello_local")
//создаём контейнер неинициализированных объектов
// которые можно построить позже в этой памяти
// (аналог placement new в c++)
var n = [[MyClass(m)]]
//создаём структуру из неинициализированных объектов
//( аналог stackframe)
var s : Params
printMyClass(s.a)
//s.a |> virtualFunction() //пока нельзя обращаться к объекту
компилятор начнёт ругаться на то, что не определён шаблон cast_arg<MyClass>::to. daScript-функции представляют свои аргументы и результаты в виде 128-битного типа vec4f, так что для кастомных типов необходимо описать способ преобразования с помощью частичной специализации этого шаблона.
Другие примеры возможных способов определения преобразования:
//если тип standart-layout и меньше 128 байт -- можно просто скопировать память template <> structcast_arg<const ImVec2 &> { static __forceinline ImVec2 to( Context & ctx, SimNode * node ){ vec4f res = node->eval(ctx); ImVec2 v2; memcpy(&v2,&res,sizeof(ImVec2)); return v2; } };
//для типов-хэндлеров можно указать способ приведения хэндлера к какому-нибудь базовому типу // (каст указателей можно рассматривать как частный случай хэндлеров, уже определенных явно) template <> structcast_arg<ax::NodeEditor::NodeId> { static __forceinline ax::NodeEditor::NodeId to( Context & ctx, SimNode * node ){ vec4f res = node->eval(ctx); return ax::NodeEditor::NodeId(cast<int32_t>::to(res)); } }; template <> structcast_res<ax::NodeEditor::NodeId> { static __forceinline vec4f from( ax::NodeEditor::NodeId node, Context * ){ return cast<int32_t>::from(int32_t(node.Get())); } };
//для типов-прокси можно определить способ построения прокси из базового типа/извлечения базового типа template <> structcast_arg<const sf::String &> { static __forceinline sf::String to( Context & ctx, SimNode * node ){ char * pstr = node->evalPtr(ctx); return sf::String(pstr ? pstr : ""); } }; template <> structcast_res<sf::String> { static __forceinline vec4f from( const sf::String & str, Context * context ){ auto text = context->stringHeap->allocateString(str); return cast<char *>::from(text); } };
Из пары примеров выше видно, что для привязки функций, возвращающих тип в качестве результата, необходимо определить специализацию шаблона cast_res с функцией from. Это верно для standard layout структур, но для сложного класса (с созданием временного объекта на хипе, по аналогии с sf::String) daScript бросает assert:
addExtern(getMyClass_ExtFuncCall)::failed this function should be bound with addExtern<DAS_BIND_FUNC(getMyClass_ExtFuncCall), SimNode_ExtFuncCallAndCopyOrMove> likely cast<> is implemented for the return type, and it should not
говорит этот ассерт о том, что вместо того, чтобы создавать временный объект, что тормознуто, лучше использовать специальную ноду языка, которая возвращает уже созданный объект. Такое себе принуждение к оптимизации.
using _method_2 = das::das_call_member< MyClass(MyClass::*)() const, &MyClass::getMyClass >;
//makeExtern<DAS_CALL_METHOD(_method_2), SimNode_ExtFuncCall >(lib, "getMyClass_ExtFuncCall", "das::das_call_member< MyClass(MyClass::*)() const, &MyClass::getMyClass >::invoke") // ->addToModule(*this, SideEffects::worstDefault); //work with pod type, but not if type has something not trivial
Вся эта шаблонная магия привязок генерится не руками, а генератором привязок dasClangBind. Распознавание инфы о типах сделано на уровне самого кода daScript, а не генератора, чтобы сам код генератора и сгенерированный код был более простым и однообразным. Но все примеры обёрток, сделанные dasClingBind, сделаны для библиотек с C-интерфейсом, которые почти не требуют ручного вмешательства. Но как только дело доходит до реального C++ кода, вылезает всё и сразу. Описанные в статье приёмы позволяют побороть большую часть сложности, и нагенерировать что-нибудь серьёзное, типа привязок классов Unreal Engine (с небольшими доработками напильником).
Примеры привязок либ с c-интерфейсом через dasClangBind:
Другие подходы: Automatic Language Bindings — размышления о способах генерации привязок к языкам от автора sokol gfx (тоже c-style, с помощью clang json) Using C Libraries in Zig — прозрачный импорт C из zig. с Си (не С++) вообще все достаточно просто Binding Nim to C++ std::list — читерский подход в nim, без интерпретации и с транспиляцией в C++ можно просто встраивать и использовать куски плюсового кода. Circle — “бэтменский” альтернативный компилятор с встроенными compile-time фичами, включая рефлексию. Автор публикует прогресс в твиттере cppyy: Automatic Python-C++ bindings — хардкор с использованием интерактивного компилятора C++ cling, прозрачный парсинг, компиляция и генерация привязок на лету