From 1275fa7156eb90947475b67cbafbbc9872c6c379 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 9 Apr 2024 18:02:17 +0100 Subject: [PATCH] Added an example --- .gitignore | 2 + example/CMakeLists.txt | 33 ++ example/animal.cpp | 25 + example/animal.h | 19 + example/app/bin/main.dart | 22 + example/app/bin/test_leak.dart | 12 + example/app/lib/Tests.dart | 429 ++++++++++++++++++ example/app/pubspec.lock | 60 +++ example/app/pubspec.yaml | 14 + example/bindings_global.h | 8 + example/build_bindings.sh | 11 + example/dartagnan.json | 5 + .../generated/AnimalBindings/CMakeLists.txt | 22 + .../AnimalBindings/dart/ffi/Animal_c.cpp | 118 +++++ .../AnimalBindings/dart/ffi/Animal_c.h | 37 ++ .../dart/ffi/c_AnimalBindings.h | 16 + .../AnimalBindings/dart/lib/Bindings.dart | 5 + .../dart/lib/LibraryLoader.dart | 41 ++ .../AnimalBindings/dart/lib/src/Animal.dart | 43 ++ .../dart/lib/src/TypeHelpers.dart | 16 + .../AnimalBindings/dart/pubspec.yaml | 13 + example/license_template | 4 + example/typesystem.xml | 11 + 23 files changed, 966 insertions(+) create mode 100644 example/CMakeLists.txt create mode 100644 example/animal.cpp create mode 100644 example/animal.h create mode 100644 example/app/bin/main.dart create mode 100644 example/app/bin/test_leak.dart create mode 100644 example/app/lib/Tests.dart create mode 100644 example/app/pubspec.lock create mode 100644 example/app/pubspec.yaml create mode 100644 example/bindings_global.h create mode 100644 example/build_bindings.sh create mode 100644 example/dartagnan.json create mode 100644 example/generated/AnimalBindings/CMakeLists.txt create mode 100644 example/generated/AnimalBindings/dart/ffi/Animal_c.cpp create mode 100644 example/generated/AnimalBindings/dart/ffi/Animal_c.h create mode 100644 example/generated/AnimalBindings/dart/ffi/c_AnimalBindings.h create mode 100644 example/generated/AnimalBindings/dart/lib/Bindings.dart create mode 100644 example/generated/AnimalBindings/dart/lib/LibraryLoader.dart create mode 100644 example/generated/AnimalBindings/dart/lib/src/Animal.dart create mode 100644 example/generated/AnimalBindings/dart/lib/src/TypeHelpers.dart create mode 100644 example/generated/AnimalBindings/dart/pubspec.yaml create mode 100644 example/license_template create mode 100644 example/typesystem.xml diff --git a/.gitignore b/.gitignore index 8b01219..fb7ba76 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ compile_commands.json .cache main.aot CTestCostData.txt +libAnimal.so +animal_export.h diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..2130b3e --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,33 @@ +# This file is part of Dartagnan. +# +# SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10) + +project(Animal LANGUAGES CXX) + +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) + +set(SOURCES + animal.cpp + + # For the sake of simplicity, we also add the generated binding glue here + # Otherwise you'd need to build ./generated/AnimalBindings/CMakeLists.txt and get + # a 2nd C++ library. + generated/AnimalBindings/dart/ffi/Animal_c.cpp +) + + +# The Animal lib is the client C++ library, which we'll generate bindings for +add_library(Animal SHARED ${SOURCES}) +include(GenerateExportHeader) +generate_export_header(Animal) + +set_property(TARGET Animal PROPERTY CXX_STANDARD 14) + +target_include_directories(Animal + PUBLIC + $ +) diff --git a/example/animal.cpp b/example/animal.cpp new file mode 100644 index 0000000..2013e16 --- /dev/null +++ b/example/animal.cpp @@ -0,0 +1,25 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT + +#include "animal.h" + +#include + +void Animal::eat() +{ + std::cout << "eat from C++\n"; + openMouth(); + closeMouth(); +} + +void Animal::openMouth() +{ + std::cout << "openMouth from C++\n"; +} + +void Animal::closeMouth() +{ + std::cout << "closeMouth from C++\n"; +} diff --git a/example/animal.h b/example/animal.h new file mode 100644 index 0000000..9cb79c8 --- /dev/null +++ b/example/animal.h @@ -0,0 +1,19 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT + +#pragma once + +#include "animal_export.h" + +class ANIMAL_EXPORT Animal +{ +public: + // non-virtual! for example purposes + void eat(); + +protected: + virtual void openMouth(); + virtual void closeMouth(); +}; diff --git a/example/app/bin/main.dart b/example/app/bin/main.dart new file mode 100644 index 0000000..28241f0 --- /dev/null +++ b/example/app/bin/main.dart @@ -0,0 +1,22 @@ +import 'package:AnimalBindings/Bindings.dart' as AnimalBindings; + +class MyDartAlligator extends AnimalBindings.Animal { + @override + void openMouth() { + print("openMouth from Dart"); + } +} + +main() { + final alligator = MyDartAlligator(); + + /// This will call C++ eat() via FFI + /// which in turn will call into Dart to invoke openMouth() + /// closeMouth() will be C++ to C++ directly, as it's not overridden in Dart + alligator.eat(); + + // Prints: + // eat from C++ + // openMouth from Dart + // closeMouth from C++ +} diff --git a/example/app/bin/test_leak.dart b/example/app/bin/test_leak.dart new file mode 100644 index 0000000..0bb7498 --- /dev/null +++ b/example/app/bin/test_leak.dart @@ -0,0 +1,12 @@ +import 'package:ffi/ffi.dart'; + +void main(List args) { + bool shouldLeak = args.contains("leak") || args.contains("--leak"); + + if (shouldLeak) { + print("Leak!"); + "foo".toNativeUtf8(); + } else { + print("No leak! Pass --leak to leak."); + } +} diff --git a/example/app/lib/Tests.dart b/example/app/lib/Tests.dart new file mode 100644 index 0000000..b8ffb63 --- /dev/null +++ b/example/app/lib/Tests.dart @@ -0,0 +1,429 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT + +import 'dart:math'; + +import 'package:TestBindings/Bindings.dart' as TestTargetNS; +import 'package:TestBindings/Bindings.dart'; +import 'package:TestBindings/Bindings_ExplicitNamespace1.dart' + as ExplicitNamespace1; +import 'package:TestBindings/Bindings_ExplicitNamespace2.dart' + as ExplicitNamespace2; +import 'package:ffi/ffi.dart'; +import 'package:test/test.dart'; + +class Level3Derived extends TestTargetNS.MyDerivedClass { + int bar() { + return 50; + } + + int virtualInBaseDerivedAndDart() { + return 200; + } +} + +class DartDerived extends TestTargetNS.MyBaseClass { + int bar() { + // overrides MyBaseClass.bar() + return 100; + } + + SimpleStruct virtualReturningStruct() { + var s = SimpleStruct(); + s.value = 51; + return s; + } +} + +class DerivedFromPureAbstract extends PureAbstract { + DerivedFromPureAbstract() : super() {} + + int virtualReceivingPointer(SimpleStruct? s) { + if (s == null) return 0; + return 1; + } +} + +class Level3DerivedFromPureAbstract + extends TestTargetNS.DerivedFromPureAbstract { + Level3DerivedFromPureAbstract() : super() {} + Level3DerivedFromPureAbstract.ctor2(int v) : super.ctor2(v) {} + + int foo() { + return 200; + } + + @override + int receivesValueStruct(SimpleStruct s) { + // dereference, to check that it doesn't crash + print(s.value); + return 2; + } +} + +void runTests() { + var mybaseclass = TestTargetNS.MyBaseClass(); + + test('static-method-return-int', () { + expect(TestTargetNS.POD.calculateStatic(), 42); + }); + + test('method-return-int', () { + final result = mybaseclass.foo(); + expect(result, 42); + }); + + test('method-return-bool-false', () { + expect(mybaseclass.getBoolFalse(), false); + }); + + test('method-return-bool-true', () { + expect(mybaseclass.getBoolTrue(), true); + }); + + mybaseclass.hello(); + + /// [const-char] Pass to static + TestTargetNS.MyBaseClass.print("Hello-World"); + + test('POD-pointer-return-int', () { + POD podPtr = POD.returnsPointerToPOD(); + expect(podPtr.calculate(), 6); + podPtr.release(); + }); + + test('POD-ref-return-int', () { + POD podRef = POD.returnsReferenceToPOD(); + expect(podRef.calculate(), 6); + }); + + test('POD-value-return-int', () { + POD podValue = POD.returnsPOD(); + expect(podValue.calculate(), 6000); + }); + + test('POD-value-return-const-ref-int', () { + expect(POD().intByConstRef(), 1); + }); + + test('static-method-returning-const-char-ptr', () { + String actual = TestTargetNS.MyBaseClass.returnsConstChar(); + expect(actual, "Hello"); + }); + + test('method-returning-enum', () { + expect(mybaseclass.receivesEnum(MyBaseClass_MyEnum.MyEnumerator3), 30); + }); + + test('anonymous-enum', () { + expect(MyBaseClass.AnonymousEnumerator1, 1); + expect(MyBaseClass.AnonymousEnumerator2, 2); + expect(MyBaseClass.AnonymousEnumerator3, 3); + }); + + test('namespace-function-returning-namespace-enum', () { + expect(TestTargetNS.namespaceFunctionReturningNamespaceEnum(), + TestTargetNS.TestTargetNS_NameSpaceLevelEnum.NameSpaceLevelEnum_Value); + }); + + test('receives-two-ints', () { + expect(mybaseclass.sum(1, 2), 3); + }); + + test('receives-two-ints', () { + expect(mybaseclass.setBool(true), true); + }); + + test('receives-bool', () { + expect(mybaseclass.setBool(false), false); + }); + + test('returns-QString', () { + expect(TestTargetNS.MyBaseClass.returnsQString().toDartString(), "hello"); + }); + + test('receives-QString', () { + final c = MyBaseClass(); + final QString s = c.receivesAndReturnsQString("hello"); + expect(s.size(), 5); + expect(s.toDartString(), "hello"); + + c.release(); + }); + + test('returns-char', () { + expect(POD.returnsChar(), "c"); + }); + + test('receives-two-pods-by-value', () { + final a = POD(); + final b = POD(); + expect(TestTargetNS.POD.receivesPODs(a, b), 3); + }); + + test('receives-two-pods-by-const-ref', () { + final a = POD(); + final b = POD(); + expect(TestTargetNS.POD.receivesConstRefPODs(a, b), 3); + }); + + test('receives-two-pods-by-ptr', () { + final a = POD(); + final b = POD(); + expect(TestTargetNS.POD.receivesPointerToPODs(a, b), 3); + }); + + test('receives-two-pods-by-const-ptr', () { + final a = POD(); + final b = POD(); + expect(TestTargetNS.POD.receivesConstPointerToPODs(a, b), 3); + }); + + test('receives-two-pods-by-ref', () { + final a = POD(); + final b = POD(); + expect(TestTargetNS.POD.receivesRefPODs(a, b), 20); + expect(a.v1, 10); + }); + + test('read-int-members', () { + final a = POD(); + expect(a.v1, 1); + expect(a.v2, 2); + expect(a.v3, 3); + expect(a.v4_const, 4); + }); + + test('read-set-int-members', () { + final a = POD(); + expect(a.v1, 1); + final int newValue = 41; + a.v1 = newValue; + expect(a.v1, newValue); + }); + + test('namespace-level-function', () { + expect(TestTargetNS.namespaceLevelGlobalFunction(), 41); + }); + + test('global-enum', () { + expect(GlobalEnum.GlobalEnum_Value, 30); + }); + + test('globalFunction', () { + expect(globalFunction(), 42); + }); + + test('virtual-derived-in-dart', () { + var dartDerived = DartDerived(); + expect(dartDerived.callsVirtual(), 100); + SimpleStruct simpleStruct = dartDerived.nonVirtualReturningStruct(); + expect(simpleStruct.value, 51); + dartDerived.release(); + }); + test('virtual-returning-bool', () { + expect(mybaseclass.virtualReturningBool(true), true); + expect(mybaseclass.virtualReturningBool(false), false); + }); + + test('function-returns-pure-virtual', () { + TestTargetNS.PureAbstract abstract = TestTargetNS.returnsPureAbstract(); + expect(abstract.foo(), 42); + expect(receivingPureAbstract(abstract), 42); + abstract.release(); + }); + + test('virtuals-calling-themselves-in-infinit-loop', () { + var derived = MyDerivedClass(); + derived.show(); // Would crash before. + expect(true, true); + derived.release(); + }); + + test('virtual-with-optional-arg', () { + expect(mybaseclass.virtualReceivingOptionalArgument(optional: 10), 10); + }); + + test('instance-created-in-cpp', () { + // The C++ creates a new MyDerivedClass and passes it by argument. + // Dart's fromCache() will fail and it will create a dart wrapper instance + var derived = MyDerivedClass(); + expect(derived.alsoCallsVirtual(), 10); + derived.release(); + }); + + test('static-member', () { + // Tests that classes can have static members and they work + expect(MyBaseClass.s_staticMember, 1); + MyBaseClass.s_staticMember = 10; + expect(MyBaseClass.s_staticMember, 10); + }); + + test('destructors-are-called', () { + // 1. Allocate in C++ and release manually in dart + expect(DestructorCounter.s_instanceCount, 0); + var instance = DestructorCounter.newOnTheHeap(); + expect(DestructorCounter.s_instanceCount, 1); + instance.release(); + expect(DestructorCounter.s_instanceCount, 0); + DestructorCounter.newOnTheStack(); + expect(DestructorCounter.s_instanceCount, 1); + + final anotherInstance = DestructorCounter(); + expect(DestructorCounter.s_instanceCount, 2); + +// Gets to 0 when the isolate shutsdown + }); + + test("template", () { + var intTemplate = TestTargetNS.MyTemplate(); + expect(intTemplate.returnInt(), 1); + }); + + test("template-with-two-args", () { + var t = TestTargetNS.MyTemplateWithTwoArgs(); + expect(t.returnInt(), 1); + }); + + test("template-with-template-receiving-template", () { + var t = MyTemplate(); + expect(t.receivesTemplate(t), 30); + }); + + test("returns-QList", () { + final vector = mybaseclass.returnsVector(); + expect(vector.size(), 3); + // expect(vector.contains(2), true); TODO + vector.clear(); + expect(vector.size(), 0); + expect(vector.isEmpty(), true); + // expect(vector.contains(2), false); TODO + + for (int i = 0; i < vector.size(); ++i) { + expect(vector.at(i), i + 1); + } + }); + + test("returns-template-argument-as-ptr", () { + // Tests that QList::at works if T is a pointer. We had a crash regarding this. + final list = MyBaseClass.returnsListOfStructPtr(); + final SimpleStruct s = list.at(0); + expect(s.value, 1); + s.release(); + }); + + test("signal-handler", () { + var obj = MyObject(parent: null); + bool signalReceived = false; + obj.onValueChanged(() { + signalReceived = true; + }); + obj.valueChanged(5); + expect(signalReceived, true); + obj.release(); + }); + + test('StructAsDefaultArg', () { + var s = SimpleObjectStruct(); + s.receivesStructWithDefaultArg(arg__1: s); + }); + + test('ctor-pointer-arg-is-nullable', () { + var c = MyBaseClass.ctor2(null); + c.release(); + }); + + test('overridden-non-virtual', () { + var derived = MyDerivedClass(); + expect(mybaseclass.nonVirtualButOverridden(), 1); + expect(derived.nonVirtualButOverridden(), 2); + derived.release(); + }); + + test('derived-from-abstract-class', () { + var v = DerivedFromPureAbstract(); + expect(v.returnsInt(), 5); + + expect(v.callVirtualReceivingPointer(null), 0); + + v.release(); + }); + + test('protected-enum', () { + var derived = MyDerivedClass(); + expect(derived.receivesProtectedEnum(1), 1); + derived.release(); + }); + + test('injected-method', () { + // Tests that we can do an =3.0.0 <4.0.0" diff --git a/example/app/pubspec.yaml b/example/app/pubspec.yaml new file mode 100644 index 0000000..39f3631 --- /dev/null +++ b/example/app/pubspec.yaml @@ -0,0 +1,14 @@ +name: DartBindingsTest +description: A test app to test dart bindings + +environment: + sdk: '>=2.17.0 <3.0.0' + +dependencies: + AnimalBindings: + path: '../generated/AnimalBindings/dart/' + ffi: ^2.1.0 + +dev_dependencies: + pedantic: ^1.9.0 + diff --git a/example/bindings_global.h b/example/bindings_global.h new file mode 100644 index 0000000..0cdf575 --- /dev/null +++ b/example/bindings_global.h @@ -0,0 +1,8 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT + +#pragma once + +#include "animal.h" diff --git a/example/build_bindings.sh b/example/build_bindings.sh new file mode 100644 index 0000000..f4dd564 --- /dev/null +++ b/example/build_bindings.sh @@ -0,0 +1,11 @@ +# This file is part of Dartagnan. +# +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-License-Identifier: MIT + + ../build-dev/3rdparty/shiboken/sources/shiboken6/generator/shiboken6 \ + --license-file=license_template \ + --output-directory=generated \ + --generator-set=dart --skip-deprecated --clang-option=-DDARTAGNAN_BINDINGS_RUN bindings_global.h typesystem.xml && \ + clang-format -i ./generated/AnimalBindings/dart/ffi/Animal_c.cpp ./generated/AnimalBindings/dart/ffi/Animal_c.h + diff --git a/example/dartagnan.json b/example/dartagnan.json new file mode 100644 index 0000000..959e14b --- /dev/null +++ b/example/dartagnan.json @@ -0,0 +1,5 @@ +{ + "exportsHeaderName": "animal_export.h", + "exportsMacroName": "ANIMAL_EXPORT", + "bindingsLibrary": "Animal" +} diff --git a/example/generated/AnimalBindings/CMakeLists.txt b/example/generated/AnimalBindings/CMakeLists.txt new file mode 100644 index 0000000..3392b35 --- /dev/null +++ b/example/generated/AnimalBindings/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) + +project(AnimalBindings LANGUAGES CXX) + +set(SOURCES + dart/ffi/Animal_c.cpp +) + +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +find_package(Qt6Widgets) + +add_library(AnimalBindings SHARED ${SOURCES}) + +include(GenerateExportHeader) +generate_export_header(AnimalBindings EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/animal_export.h" EXPORT_MACRO_NAME ANIMAL_EXPORT) +set_property(TARGET AnimalBindings PROPERTY CXX_STANDARD 14) + +target_link_libraries(AnimalBindings Qt6::Core Qt6::Widgets) +target_include_directories(AnimalBindings PUBLIC $ $ +$) + diff --git a/example/generated/AnimalBindings/dart/ffi/Animal_c.cpp b/example/generated/AnimalBindings/dart/ffi/Animal_c.cpp new file mode 100644 index 0000000..1137b5b --- /dev/null +++ b/example/generated/AnimalBindings/dart/ffi/Animal_c.cpp @@ -0,0 +1,118 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +#include "Animal_c.h" + + +#include + +#include + + +namespace Dartagnan { + +typedef int (*CleanupCallback)(void *thisPtr); +static CleanupCallback s_cleanupCallback = nullptr; + +template +struct ValueWrapper +{ + T value; +}; + +} +namespace AnimalBindings_wrappersNS { +Animal_wrapper::Animal_wrapper() + : ::Animal() +{ +} +void Animal_wrapper::closeMouth() +{ + if (m_closeMouthCallback) { + const void *thisPtr = this; + m_closeMouthCallback(const_cast(thisPtr)); + } else { + ::Animal::closeMouth(); + } +} +void Animal_wrapper::closeMouth_nocallback() +{ + ::Animal::closeMouth(); +} +void Animal_wrapper::eat() +{ + ::Animal::eat(); +} +void Animal_wrapper::openMouth() +{ + if (m_openMouthCallback) { + const void *thisPtr = this; + m_openMouthCallback(const_cast(thisPtr)); + } else { + ::Animal::openMouth(); + } +} +void Animal_wrapper::openMouth_nocallback() +{ + ::Animal::openMouth(); +} +Animal_wrapper::~Animal_wrapper() +{ +} + +} +static Animal *fromPtr(void *ptr) +{ + return reinterpret_cast(ptr); +} +static AnimalBindings_wrappersNS::Animal_wrapper *fromWrapperPtr(void *ptr) +{ + return reinterpret_cast(ptr); +} +extern "C" { +void c_Animal_Finalizer(void *cppObj) +{ + delete reinterpret_cast(cppObj); +} +void *c_Animal__constructor() +{ + auto ptr = new AnimalBindings_wrappersNS::Animal_wrapper(); + return reinterpret_cast(ptr); +} +// closeMouth() +void c_Animal__closeMouth(void *thisObj) +{ + fromWrapperPtr(thisObj)->closeMouth_nocallback(); +} +// eat() +void c_Animal__eat(void *thisObj) +{ + fromPtr(thisObj)->eat(); +} +// openMouth() +void c_Animal__openMouth(void *thisObj) +{ + fromWrapperPtr(thisObj)->openMouth_nocallback(); +} +void c_Animal__destructor(void *thisObj) +{ + delete fromPtr(thisObj); +} +void c_Animal__registerVirtualMethodCallback(void *ptr, void *callback, int methodId) +{ + auto wrapper = fromWrapperPtr(ptr); + switch (methodId) { + case 1: + wrapper->m_closeMouthCallback = + reinterpret_cast( + callback); + break; + case 3: + wrapper->m_openMouthCallback = + reinterpret_cast( + callback); + break; + } +} +} diff --git a/example/generated/AnimalBindings/dart/ffi/Animal_c.h b/example/generated/AnimalBindings/dart/ffi/Animal_c.h new file mode 100644 index 0000000..3a93f3b --- /dev/null +++ b/example/generated/AnimalBindings/dart/ffi/Animal_c.h @@ -0,0 +1,37 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +#include "animal_export.h" +#include + +namespace AnimalBindings_wrappersNS { +class Animal_wrapper : public ::Animal +{ +public: + ~Animal_wrapper(); + Animal_wrapper(); + virtual void closeMouth(); + virtual void closeMouth_nocallback(); + void eat(); + virtual void openMouth(); + virtual void openMouth_nocallback(); + typedef void (*Callback_closeMouth)(void *); + Callback_closeMouth m_closeMouthCallback = nullptr; + typedef void (*Callback_openMouth)(void *); + Callback_openMouth m_openMouthCallback = nullptr; +}; +} +extern "C" { +// Animal::Animal() +ANIMAL_EXPORT void *c_Animal__constructor(); +// Animal::closeMouth() +ANIMAL_EXPORT void c_Animal__closeMouth(void *thisObj); +// Animal::eat() +ANIMAL_EXPORT void c_Animal__eat(void *thisObj); +// Animal::openMouth() +ANIMAL_EXPORT void c_Animal__openMouth(void *thisObj); +ANIMAL_EXPORT void c_Animal__destructor(void *thisObj); +ANIMAL_EXPORT void c_Animal__registerVirtualMethodCallback(void *ptr, void *callback, int methodId); +ANIMAL_EXPORT void c_Animal_Finalizer(void *cppObj); +} diff --git a/example/generated/AnimalBindings/dart/ffi/c_AnimalBindings.h b/example/generated/AnimalBindings/dart/ffi/c_AnimalBindings.h new file mode 100644 index 0000000..028bcec --- /dev/null +++ b/example/generated/AnimalBindings/dart/ffi/c_AnimalBindings.h @@ -0,0 +1,16 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +#include + +// Animal::Animal() +void * c_Animal__constructor(); +// Animal::closeMouth() +void c_Animal__closeMouth(void *thisObj); +// Animal::eat() +void c_Animal__eat(void *thisObj); +// Animal::openMouth() +void c_Animal__openMouth(void *thisObj); +void c_Animal__destructor(void *thisObj); +void c_Animal__registerVirtualMethodCallback(void *ptr, void *callback, int methodId); void c_Animal_Finalizer(void *cppObj); \ No newline at end of file diff --git a/example/generated/AnimalBindings/dart/lib/Bindings.dart b/example/generated/AnimalBindings/dart/lib/Bindings.dart new file mode 100644 index 0000000..2b0465a --- /dev/null +++ b/example/generated/AnimalBindings/dart/lib/Bindings.dart @@ -0,0 +1,5 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +export 'src/Animal.dart' show Animal; diff --git a/example/generated/AnimalBindings/dart/lib/LibraryLoader.dart b/example/generated/AnimalBindings/dart/lib/LibraryLoader.dart new file mode 100644 index 0000000..7f321bf --- /dev/null +++ b/example/generated/AnimalBindings/dart/lib/LibraryLoader.dart @@ -0,0 +1,41 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +import 'dart:ffi' as ffi; +import 'dart:io' show Platform; + +String bindingsLibraryName(String name) { + if (Platform.isWindows) return "${name}.dll"; + if (Platform.isMacOS) return "lib${name}.dylib"; + return "lib${name}.so"; +} + +class Library { + var _dylib; + + ffi.DynamicLibrary get dylib { + return _dylib; + } + + static var _library = null; + + factory Library.instance() { + // Singleton impl. + if (_library == null) _library = Library._(); + return _library; + } + + Library._() { + // DYLD_LIBRARY_PATH doesn't work by default on newer macOS. Instead + // introduce our own env variable for the same use case + var bindingsPath = Platform.environment["DARTAGNAN_BINDINGSLIB_PATH"] ?? ""; + + var libraryPath = bindingsLibraryName("Animal"); + if (!bindingsPath.isEmpty) { + libraryPath = bindingsPath + "/" + libraryPath; + } + + _dylib = ffi.DynamicLibrary.open(libraryPath); + } +} diff --git a/example/generated/AnimalBindings/dart/lib/src/Animal.dart b/example/generated/AnimalBindings/dart/lib/src/Animal.dart new file mode 100644 index 0000000..19737f3 --- /dev/null +++ b/example/generated/AnimalBindings/dart/lib/src/Animal.dart @@ -0,0 +1,43 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +import 'dart:ffi' as ffi;import 'package:ffi/ffi.dart';import 'TypeHelpers.dart';import '../Bindings.dart';import '../LibraryLoader.dart';var _dylib = Library.instance().dylib;final _finalizerFunc = (String name) {return _dylib.lookup>(name);}; + +Map _finalizers = {}; + +class Animal implements ffi.Finalizable{static var s_dartInstanceByCppPtr = Map(); +var _thisCpp = null;bool _needsAutoDelete = false;get thisCpp => _thisCpp;set thisCpp (var ptr) {_thisCpp = ptr;ffi.Pointer ptrvoid = ptr.cast(); +if (_needsAutoDelete) { +final String finalizerName = getFinalizerName();if (!_finalizers.keys.contains(runtimeType)) {_finalizers[finalizerName] = ffi.NativeFinalizer(_finalizerFunc(finalizerName).cast()); +}_finalizers[finalizerName]!.attach(this, ptrvoid); +}}static bool isCached(var cppPointer) { return s_dartInstanceByCppPtr.containsKey(cppPointer.address);}factory Animal.fromCache(var cppPointer, [needsAutoDelete = false]) {return (s_dartInstanceByCppPtr[cppPointer.address] ?? +Animal.fromCppPointer(cppPointer, needsAutoDelete)) as Animal;}Animal.fromCppPointer(var cppPointer, [this._needsAutoDelete = false]) {thisCpp = cppPointer;}Animal.init() {}String getFinalizerName() {return "c_Animal_Finalizer";}//Animal() +Animal() {final voidstar_Func_void func = _dylib.lookup>('c_Animal__constructor').asFunction();thisCpp = func();Animal.s_dartInstanceByCppPtr[thisCpp.address] = this;registerCallbacks();}// closeMouth() + closeMouth() {final void_Func_voidstar func = _dylib.lookup>(cFunctionSymbolName(1)).asFunction();func(thisCpp); +}static void closeMouth_calledFromC(ffi.Pointer thisCpp) { +var dartInstance = Animal.s_dartInstanceByCppPtr[thisCpp.address] ;if (dartInstance == null) {print("Dart instance not found for Animal::closeMouth()! (${thisCpp.address})");throw Error(); +}dartInstance.closeMouth(); +}// eat() + eat() {final void_Func_voidstar func = _dylib.lookup>('c_Animal__eat').asFunction();func(thisCpp); +}// openMouth() + openMouth() {final void_Func_voidstar func = _dylib.lookup>(cFunctionSymbolName(3)).asFunction();func(thisCpp); +}static void openMouth_calledFromC(ffi.Pointer thisCpp) { +var dartInstance = Animal.s_dartInstanceByCppPtr[thisCpp.address] ;if (dartInstance == null) {print("Dart instance not found for Animal::openMouth()! (${thisCpp.address})");throw Error(); +}dartInstance.openMouth(); +}void release() {final void_Func_voidstar func = _dylib.lookup>('c_Animal__destructor').asFunction();func(thisCpp);}String cFunctionSymbolName(int methodId) { switch (methodId) { + case 1: + return "c_Animal__closeMouth"; case 3: + return "c_Animal__openMouth";} +return "";}static String methodNameFromId(int methodId) { + switch (methodId) { + case 1: + return "closeMouth"; case 3: + return "openMouth"; } throw Error();} +void registerCallbacks() { +assert(thisCpp != null); +final RegisterMethodIsReimplementedCallback registerCallback = _dylib +.lookup>('c_Animal__registerVirtualMethodCallback').asFunction(); +final callback1 = ffi.Pointer.fromFunction(Animal.closeMouth_calledFromC ); +registerCallback(thisCpp, callback1, 1);final callback3 = ffi.Pointer.fromFunction(Animal.openMouth_calledFromC ); +registerCallback(thisCpp, callback3, 3);}} \ No newline at end of file diff --git a/example/generated/AnimalBindings/dart/lib/src/TypeHelpers.dart b/example/generated/AnimalBindings/dart/lib/src/TypeHelpers.dart new file mode 100644 index 0000000..343c0f1 --- /dev/null +++ b/example/generated/AnimalBindings/dart/lib/src/TypeHelpers.dart @@ -0,0 +1,16 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT +import 'dart:ffi' as ffi; +import 'package:ffi/ffi.dart'; +// tag=1053 + +typedef void_Func_voidstar = void Function(ffi.Pointer); +typedef void_Func_voidstar_FFI = ffi.Void Function(ffi.Pointer); +typedef RegisterMethodIsReimplementedCallback = void Function(ffi.Pointer, ffi.Pointer, int); +typedef RegisterMethodIsReimplementedCallback_FFI = ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Int32); +typedef SignalHandler = void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer); +typedef SignalHandler_FFI = ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer); +typedef voidstar_Func_void = ffi.Pointer Function(); +typedef voidstar_Func_void_FFI = ffi.Pointer Function(); \ No newline at end of file diff --git a/example/generated/AnimalBindings/dart/pubspec.yaml b/example/generated/AnimalBindings/dart/pubspec.yaml new file mode 100644 index 0000000..804a15e --- /dev/null +++ b/example/generated/AnimalBindings/dart/pubspec.yaml @@ -0,0 +1,13 @@ +name: AnimalBindings +description: Bindings + +environment: + sdk: '>=2.17.0 <3.0.0' + +dependencies: + ffi: + intl: + meta: + +dev_dependencies: + pedantic: ^1.9.0 diff --git a/example/license_template b/example/license_template new file mode 100644 index 0000000..c98593b --- /dev/null +++ b/example/license_template @@ -0,0 +1,4 @@ +/// This file is part of Dartagnan. +/// +/// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +/// SPDX-License-Identifier: MIT diff --git a/example/typesystem.xml b/example/typesystem.xml new file mode 100644 index 0000000..4b1afe3 --- /dev/null +++ b/example/typesystem.xml @@ -0,0 +1,11 @@ + + + + + + + + + + +