diff --git a/src/Generation/Generator/Classes.cs b/src/Generation/Generator/Classes.cs index 2db0b8115..a8cd94c35 100644 --- a/src/Generation/Generator/Classes.cs +++ b/src/Generation/Generator/Classes.cs @@ -20,6 +20,7 @@ public static void Generate(IEnumerable classes, string path) //Standard generators new Generator.Internal.ClassMethods(publisher), new Generator.Internal.ClassStruct(publisher), + new Generator.Internal.ClassHandle(publisher), new Generator.Public.ClassConstructors(publisher), new Generator.Public.ClassMethods(publisher), new Generator.Public.ClassFunctions(publisher), diff --git a/src/Generation/Generator/Generator/Internal/ClassHandle.cs b/src/Generation/Generator/Generator/Internal/ClassHandle.cs new file mode 100644 index 000000000..3d20482e1 --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/ClassHandle.cs @@ -0,0 +1,32 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class ClassHandle : Generator +{ + private readonly Publisher _publisher; + + public ClassHandle(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Class obj) + { + if (obj.Fundamental) + return; + + if (obj.Parent is null) + return; //Do not generate a handle for GObject.Object itself + + var source = Renderer.Internal.ClassHandle.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: Class.GetInternalHandleName(obj), + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Public/Class/ClassFramework.cs b/src/Generation/Generator/Generator/Public/Class/ClassFramework.cs index 7e883efcb..f7fea64df 100644 --- a/src/Generation/Generator/Generator/Public/Class/ClassFramework.cs +++ b/src/Generation/Generator/Generator/Public/Class/ClassFramework.cs @@ -16,6 +16,9 @@ public void Generate(GirModel.Class obj) if (obj.Fundamental) return; + if (obj.Parent is null) + return; //Do not generate Framework for GObject.Object itsel + var source = Renderer.Public.ClassFramework.Render(obj); var codeUnit = new CodeUnit( Project: Namespace.GetCanonicalName(obj.Namespace), diff --git a/src/Generation/Generator/Generator/Public/Interface/InterfaceFramework.cs b/src/Generation/Generator/Generator/Public/Interface/InterfaceFramework.cs deleted file mode 100644 index 89b4241e1..000000000 --- a/src/Generation/Generator/Generator/Public/Interface/InterfaceFramework.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Generator.Model; - -namespace Generator.Generator.Public; - -internal class InterfaceFramework : Generator -{ - private readonly Publisher _publisher; - - public InterfaceFramework(Publisher publisher) - { - _publisher = publisher; - } - - public void Generate(GirModel.Interface obj) - { - var source = Renderer.Public.InterfaceFramework.Render(obj); - var codeUnit = new CodeUnit( - Project: Namespace.GetCanonicalName(obj.Namespace), - Name: $"{obj.Name}.Framework", - Source: source, - IsInternal: false - ); - - _publisher.Publish(codeUnit); - } -} diff --git a/src/Generation/Generator/Interfaces.cs b/src/Generation/Generator/Interfaces.cs index 4cfcc4c8f..8a4174499 100644 --- a/src/Generation/Generator/Interfaces.cs +++ b/src/Generation/Generator/Interfaces.cs @@ -11,7 +11,6 @@ public static void Generate(IEnumerable interfaces, string p var generators = new List>() { new Generator.Internal.InterfaceMethods(publisher), - new Generator.Public.InterfaceFramework(publisher), new Generator.Public.InterfaceMethods(publisher), new Generator.Public.InterfaceProperties(publisher), diff --git a/src/Generation/Generator/Model/Class.cs b/src/Generation/Generator/Model/Class.cs index d44d48a9a..40c2801cb 100644 --- a/src/Generation/Generator/Model/Class.cs +++ b/src/Generation/Generator/Model/Class.cs @@ -10,6 +10,15 @@ public static string GetFullyQualifiedInternalStructName(GirModel.Class @class) public static string GetInternalStructName(GirModel.Class @class) => @class.Name + "Data"; + + public static string GetInternalHandleName(GirModel.Class @class) + => @class.Name + "Handle"; + + public static string GetFullyQualifiedInternalHandleName(GirModel.Class @class) + => Namespace.GetInternalName(@class.Namespace) + "." + GetInternalHandleName(@class); + + public static string GetFullyQualifiedPublicName(GirModel.Class @class) + => Namespace.GetPublicName(@class.Namespace) + "." + @class.Name; public static bool HidesConstructor(GirModel.Class? cls, GirModel.Constructor constructor) { @@ -84,4 +93,16 @@ private static bool ParameterMatch(GirModel.Parameter[] p1, GirModel.Parameter[] return true; } + + public static bool IsInitiallyUnowned(GirModel.Class cls) => IsNamedInitiallyUnowned(cls.Name) || InheritsInitiallyUnowned(cls); + + private static bool InheritsInitiallyUnowned(GirModel.Class @class) + { + if (@class.Parent is null) + return false; + + return IsNamedInitiallyUnowned(@class.Parent.Name) || InheritsInitiallyUnowned(@class.Parent); + } + + private static bool IsNamedInitiallyUnowned(string name) => name == "InitiallyUnowned"; } diff --git a/src/Generation/Generator/Renderer/Internal/Class/ClassHandle.cs b/src/Generation/Generator/Renderer/Internal/Class/ClassHandle.cs new file mode 100644 index 000000000..1182ed71f --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Class/ClassHandle.cs @@ -0,0 +1,48 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class ClassHandle +{ + public static string Render(GirModel.Class cls) + { + var handleName = Class.GetInternalHandleName(cls); + + return $$""" + using System; + using System.Linq; + using System.Runtime.InteropServices; + using System.Runtime.Versioning; + + #nullable enable + + namespace {{Namespace.GetInternalName(cls.Namespace)}}; + + public partial class {{handleName}} : GObject.Internal.ObjectHandle + { + public {{handleName}}(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle){ } + + public static {{handleName}} For(bool owned, GObject.ConstructArgument[] constructArguments) where T : {{Class.GetFullyQualifiedPublicName(cls)}}, GObject.GTypeProvider + { + // We can't check if a reference is floating via "g_object_is_floating" here + // as the function could be "lying" depending on the intent of framework writers. + // E.g. A Gtk.Window created via "g_object_new_with_properties" returns an unowned + // reference which is not marked as floating as the gtk toolkit "owns" it. + // For this reason we just delegate the problem to the caller and require a + // definition whether the ownership of the new object will be transferred to us or not. + + var ptr = GObject.Internal.Object.NewWithProperties( + objectType: T.GetGType(), + nProperties: (uint) constructArguments.Length, + names: constructArguments.Select(x => x.Name).ToArray(), + values: GObject.Internal.ValueArray2OwnedHandle.Create(constructArguments.Select(x => x.Value).ToArray()) + ); + + return new {{handleName}}(ptr, owned); + } + } + """; + } + + +} diff --git a/src/Generation/Generator/Renderer/Internal/ClassMethods.cs b/src/Generation/Generator/Renderer/Internal/Class/ClassMethods.cs similarity index 100% rename from src/Generation/Generator/Renderer/Internal/ClassMethods.cs rename to src/Generation/Generator/Renderer/Internal/Class/ClassMethods.cs diff --git a/src/Generation/Generator/Renderer/Internal/ClassStruct.cs b/src/Generation/Generator/Renderer/Internal/Class/ClassStruct.cs similarity index 100% rename from src/Generation/Generator/Renderer/Internal/ClassStruct.cs rename to src/Generation/Generator/Renderer/Internal/Class/ClassStruct.cs diff --git a/src/Generation/Generator/Renderer/Internal/Framework/FrameworkTypeRegistration.cs b/src/Generation/Generator/Renderer/Internal/Framework/FrameworkTypeRegistration.cs index 9047ca51e..c32788967 100644 --- a/src/Generation/Generator/Renderer/Internal/Framework/FrameworkTypeRegistration.cs +++ b/src/Generation/Generator/Renderer/Internal/Framework/FrameworkTypeRegistration.cs @@ -15,6 +15,8 @@ public static string Render(GirModel.Namespace ns) using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using GObject; +using GObject.Internal; namespace {Namespace.GetInternalName(ns)}; @@ -40,24 +42,29 @@ internal static void RegisterTypes() .Join(Environment.NewLine)} }} - private static void Register(Func getType, params OSPlatform[] supportedPlatforms) where T : class + private static void Register(params OSPlatform[] supportedPlatforms) where T : InstanceFactory, GTypeProvider {{ - try - {{ + try + {{ +#if NET7_0_OR_GREATER if(supportedPlatforms.Any(RuntimeInformation.IsOSPlatform)) - GObject.Internal.TypeDictionary.Add(typeof(T), new GObject.Type(getType())); - }} - catch(System.Exception e) - {{ - Debug.WriteLine($""Could not register type '{{nameof(T)}}': {{e.Message}}""); - }} - }} + GObject.Internal.DynamicInstanceFactory.Register(T.GetGType(), T.Create); +#else + if (supportedPlatforms.Any(RuntimeInformation.IsOSPlatform)) + GObject.Internal.DynamicInstanceFactory.Register(GTypeProviderHelper.GetGType(), InstanceFactoryHelper.Create); +#endif + }} + catch(System.Exception e) + {{ + Debug.WriteLine($""Could not register type: {{e.Message}}""); + }} + }} }}"; } private static string RenderRegistration(GirModel.ComplexType type) { - return @$"Register<{ComplexType.GetFullyQualified(type)}>(Internal.{type.Name}.{Function.GetGType}{RenderPlatforms(type as GirModel.PlatformDependent)});"; + return $"Register<{ComplexType.GetFullyQualified(type)}>({RenderPlatforms(type as GirModel.PlatformDependent)});"; } private static string RenderPlatforms(GirModel.PlatformDependent? platformDependent) @@ -76,6 +83,6 @@ private static string RenderPlatforms(GirModel.PlatformDependent? platformDepend if (platformDependent.SupportsWindows) statements.Add("OSPlatform.Windows"); - return ", " + string.Join(", ", statements); + return string.Join(", ", statements); } } diff --git a/src/Generation/Generator/Renderer/Public/Class/ClassFramework.cs b/src/Generation/Generator/Renderer/Public/Class/ClassFramework.cs index 0afbf52eb..08decf41f 100644 --- a/src/Generation/Generator/Renderer/Public/Class/ClassFramework.cs +++ b/src/Generation/Generator/Renderer/Public/Class/ClassFramework.cs @@ -13,7 +13,6 @@ public static string Render(GirModel.Class cls) return $@" using System; -using GObject; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -26,7 +25,8 @@ namespace {Namespace.GetPublicName(cls.Namespace)}; {PlatformSupportAttribute.Render(cls as GirModel.PlatformDependent)} public {@sealed}partial class {cls.Name} {RenderInheritance(cls)} {{ - {RenderParentConstructors(cls)} + {$"protected internal { cls.Name }({Class.GetFullyQualifiedInternalHandleName(cls)} handle) : base(handle) {{ }}"} + {RenderPublicConstructor(cls)} }}"; } @@ -36,7 +36,7 @@ private static string RenderInheritance(GirModel.Class cls) var interfaces = cls.Implements.Select(ComplexType.GetFullyQualified); var elements = new List(interfaces); - + if (parentClass is not null) elements.Insert(0, parentClass); @@ -45,46 +45,10 @@ private static string RenderInheritance(GirModel.Class cls) : $": {string.Join(", ", elements)}"; } - private static string RenderParentConstructors(GirModel.Class cls) - { - if (cls.Parent is null) - return string.Empty; - - var constructors = new List() - { - $@"protected internal { cls.Name }(IntPtr ptr, bool ownedRef) : base(ptr, ownedRef) {{}}", - }; - - if (IsInitiallyUnowned(cls)) - { - constructors.Add($@" -// As initially unowned objects always start with a floating reference -// we can safely set the ""owned"" parameter to false. -protected internal {cls.Name}(params ConstructArgument[] constructArguments) : base(owned: false, constructArguments) {{}}"); - constructors.Add($"public {cls.Name}() : this(Array.Empty()) {{}}"); - } - else if (InheritsInitiallyUnowned(cls)) - { - constructors.Add($"protected internal {cls.Name}(params ConstructArgument[] constructArguments) : base(constructArguments) {{}}"); - constructors.Add($"public {cls.Name}() : this(Array.Empty()) {{}}"); - } - else - { - constructors.Add($"protected internal {cls.Name}(bool owned, params ConstructArgument[] constructArguments) : base(owned, constructArguments) {{}}"); - } - - return constructors.Join(Environment.NewLine); - } - - private static bool IsInitiallyUnowned(GirModel.Class cls) => IsNamedInitiallyUnowned(cls.Name); - - private static bool InheritsInitiallyUnowned(GirModel.Class @class) + private static string RenderPublicConstructor(GirModel.Class cls) { - if (@class.Parent is null) - return false; - - return IsNamedInitiallyUnowned(@class.Parent.Name) || InheritsInitiallyUnowned(@class.Parent); + return Class.IsInitiallyUnowned(cls) + ? $" public {cls.Name}() : this({Class.GetFullyQualifiedInternalHandleName(cls)}.For<{cls.Name}>(false, Array.Empty())) {{ }}" + : $" public {cls.Name}() : this({Class.GetFullyQualifiedInternalHandleName(cls)}.For<{cls.Name}>(true, Array.Empty())) {{ }}"; } - - private static bool IsNamedInitiallyUnowned(string name) => name == "InitiallyUnowned"; } diff --git a/src/Generation/Generator/Renderer/Public/Class/ClassFunctions.cs b/src/Generation/Generator/Renderer/Public/Class/ClassFunctions.cs index b1bfddcc7..0e9be7b9d 100644 --- a/src/Generation/Generator/Renderer/Public/Class/ClassFunctions.cs +++ b/src/Generation/Generator/Renderer/Public/Class/ClassFunctions.cs @@ -20,13 +20,45 @@ namespace {Namespace.GetPublicName(cls.Namespace)}; // AUTOGENERATED FILE - DO NOT MODIFY -public partial class {cls.Name} : GObject.GTypeProvider +public partial class {cls.Name} : GObject.GTypeProvider, GObject.InstanceFactory {{ - {FunctionRenderer.Render(cls.TypeFunction)} + +#if NET7_0_OR_GREATER + static GObject.Type GTypeProvider.GetGType() +#else + public static GObject.Type GetGType() +#endif + {{ + return {RenderGetGType(cls.TypeFunction)}; + }} + +#if NET7_0_OR_GREATER + static object GObject.InstanceFactory.Create(IntPtr handle, bool ownsHandle) +#else + public static object Create(IntPtr handle, bool ownsHandle) +#endif + {{ + return CreateIntern(handle, ownsHandle); + }} + + private static GObject.Object CreateIntern(IntPtr handle, bool ownsHandle) + {{ + return {RenderObjectFactory(cls)}; + }} {cls.Functions .Select(FunctionRenderer.Render) .Join(Environment.NewLine)} }}"; } + + private static string RenderGetGType(GirModel.Function function) + { + return $"{Namespace.GetInternalName(function.Namespace)}.{function.Parent!.Name}.{Function.GetName(function)}()"; + } + + private static string RenderObjectFactory(GirModel.Class cls) + { + return $"new {cls.Name}(new {Class.GetFullyQualifiedInternalHandleName(cls)}(handle, ownsHandle))"; + } } diff --git a/src/Generation/Generator/Renderer/Public/ForeignTypedRecord.cs b/src/Generation/Generator/Renderer/Public/ForeignTypedRecord.cs index e3531734c..9f1929141 100644 --- a/src/Generation/Generator/Renderer/Public/ForeignTypedRecord.cs +++ b/src/Generation/Generator/Renderer/Public/ForeignTypedRecord.cs @@ -24,31 +24,41 @@ namespace {Namespace.GetPublicName(record.Namespace)}; // AUTOGENERATED FILE - DO NOT MODIFY {PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} -public partial class {name} : GLib.BoxedRecord, IDisposable +public partial class {name} : GLib.BoxedRecord, GObject.GTypeProvider, GObject.InstanceFactory, IDisposable {{ public {internalHandleName} Handle {{ get; }} public {name}({internalHandleName} handle) {{ Handle = handle; - Initialize(); }} - //TODO: This is a workaround constructor as long as we are - //not having https://github.com/gircore/gir.core/issues/397 - private {name}(IntPtr ptr, bool ownsHandle) : this(ownsHandle - ? new {Model.ForeignTypedRecord.GetFullyQuallifiedOwnedHandle(record)}(ptr) - : new {Model.ForeignTypedRecord.GetFullyQuallifiedUnownedHandle(record)}(ptr).OwnedCopy()){{ }} +#if NET7_0_OR_GREATER + static object GObject.InstanceFactory.Create(IntPtr handle, bool ownsHandle) +#else + public static object Create(IntPtr handle, bool ownsHandle) +#endif + {{ + var safeHandle = ownsHandle + ? new {Record.GetFullyQualifiedInternalOwnedHandle(record)}(handle) + : {Record.GetFullyQualifiedInternalOwnedHandle(record)}.FromUnowned(handle); - // Implement this to perform additional steps in the constructor - partial void Initialize(); + return new {name}(safeHandle); + }} System.IntPtr GLib.BoxedRecord.GetHandle() {{ return Handle.DangerousGetHandle(); }} - {FunctionRenderer.Render(record.TypeFunction)} +#if NET7_0_OR_GREATER + static GObject.Type GObject.GTypeProvider.GetGType() +#else + public static GObject.Type GetGType() +#endif + {{ + return {RenderGetGType(record.TypeFunction!)}; + }} public void Dispose() {{ @@ -56,4 +66,9 @@ public void Dispose() }} }}"; } + + private static string RenderGetGType(GirModel.Function function) + { + return $"{Namespace.GetInternalName(function.Namespace)}.{function.Parent!.Name}.{Function.GetName(function)}()"; + } } diff --git a/src/Generation/Generator/Renderer/Public/Interface/InterfaceFramework.cs b/src/Generation/Generator/Renderer/Public/Interface/InterfaceFramework.cs deleted file mode 100644 index 981cbbac4..000000000 --- a/src/Generation/Generator/Renderer/Public/Interface/InterfaceFramework.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Generator.Model; - -namespace Generator.Renderer.Public; - -internal static class InterfaceFramework -{ - public static string Render(GirModel.Interface iface) - { - return $@" -using System; -using GObject; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -#nullable enable - -namespace {Namespace.GetPublicName(iface.Namespace)}; - -// AUTOGENERATED FILE - DO NOT MODIFY - -{PlatformSupportAttribute.Render(iface as GirModel.PlatformDependent)} -public partial interface {iface.Name} : GLib.IHandle -{{ -/* - IntPtr GLib.IHandle.Handle - {{ - get - {{ - System.Diagnostics.Debug.Assert( - condition: GetType().IsAssignableTo(typeof(GObject.Object)), - message: $""Interface '{{nameof({iface.Name})}}' can only be implemented on GObject-based classes"" - ); - - return ((GObject.Object)this).Handle; - }} - }} -*/ -}}"; - } -} diff --git a/src/Generation/Generator/Renderer/Public/OpaqueTypedRecord.cs b/src/Generation/Generator/Renderer/Public/OpaqueTypedRecord.cs index ffd186001..e6f68a4d7 100644 --- a/src/Generation/Generator/Renderer/Public/OpaqueTypedRecord.cs +++ b/src/Generation/Generator/Renderer/Public/OpaqueTypedRecord.cs @@ -30,7 +30,7 @@ namespace {Namespace.GetPublicName(record.Namespace)}; // AUTOGENERATED FILE - DO NOT MODIFY {PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} -public sealed partial class {name} : GLib.BoxedRecord, IEquatable<{name}>, IDisposable +public sealed partial class {name} : GLib.BoxedRecord, GObject.GTypeProvider, GObject.InstanceFactory, IEquatable<{name}>, IDisposable {{ public {internalHandleName} Handle {{ get; }} @@ -38,23 +38,33 @@ public sealed partial class {name} : GLib.BoxedRecord, IEquatable<{name}>, IDisp {{ Handle = handle; Handle.SetMemoryPressure(); - Initialize(); }} - //TODO: This is a workaround constructor as long as we are - //not having https://github.com/gircore/gir.core/issues/397 - private {name}(IntPtr ptr, bool ownsHandle) : this(ownsHandle - ? new {Model.OpaqueTypedRecord.GetFullyQuallifiedOwnedHandle(record)}(ptr) - : new {Model.OpaqueTypedRecord.GetFullyQuallifiedUnownedHandle(record)}(ptr).OwnedCopy()){{ }} +#if NET7_0_OR_GREATER + static object GObject.InstanceFactory.Create(IntPtr handle, bool ownsHandle) +#else + public static object Create(IntPtr handle, bool ownsHandle) +#endif + {{ + var safeHandle = ownsHandle + ? new {Record.GetFullyQualifiedInternalOwnedHandle(record)}(handle) + : {Record.GetFullyQualifiedInternalOwnedHandle(record)}.FromUnowned(handle); - // Implement this to perform additional steps in the constructor - partial void Initialize(); + return new {name}(safeHandle); + }} {record.Constructors .Select(ConstructorRenderer.Render) .Join(Environment.NewLine)} - {FunctionRenderer.Render(record.TypeFunction)} +#if NET7_0_OR_GREATER + static GObject.Type GObject.GTypeProvider.GetGType() +#else + public static GObject.Type GetGType() +#endif + {{ + return {RenderGetGType(record.TypeFunction!)}; + }} {record.Functions .Select(FunctionRenderer.Render) @@ -100,4 +110,9 @@ public void Dispose() }} }}"; } + + private static string RenderGetGType(GirModel.Function function) + { + return $"{Namespace.GetInternalName(function.Namespace)}.{function.Parent!.Name}.{Function.GetName(function)}()"; + } } diff --git a/src/Generation/Generator/Renderer/Public/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/TypedRecord.cs index ae8a65f34..2438999a3 100644 --- a/src/Generation/Generator/Renderer/Public/TypedRecord.cs +++ b/src/Generation/Generator/Renderer/Public/TypedRecord.cs @@ -31,34 +31,44 @@ namespace {Namespace.GetPublicName(record.Namespace)}; // AUTOGENERATED FILE - DO NOT MODIFY {PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} -public sealed partial class {name} : GLib.BoxedRecord, IEquatable<{name}>, IDisposable +public sealed partial class {name} : GLib.BoxedRecord, GObject.GTypeProvider, GObject.InstanceFactory, IEquatable<{name}>, IDisposable {{ public {internalHandleName} Handle {{ get; }} public {name}({internalHandleName} handle) {{ Handle = handle; - Initialize(); }} public {name}() : this({Model.TypedRecord.GetFullyQuallifiedManagedHandle(record)}.Create()) {{ }} - //TODO: This is a workaround constructor as long as we are - //not having https://github.com/gircore/gir.core/issues/397 - private {name}(IntPtr ptr, bool ownsHandle) : this(ownsHandle - ? new {Model.OpaqueTypedRecord.GetFullyQuallifiedOwnedHandle(record)}(ptr) - : new {Model.OpaqueTypedRecord.GetFullyQuallifiedUnownedHandle(record)}(ptr).OwnedCopy()){{ }} +#if NET7_0_OR_GREATER + static object GObject.InstanceFactory.Create(IntPtr handle, bool ownsHandle) +#else + public static object Create(IntPtr handle, bool ownsHandle) +#endif + {{ + var safeHandle = ownsHandle + ? new {Record.GetFullyQualifiedInternalOwnedHandle(record)}(handle) + : {Record.GetFullyQualifiedInternalOwnedHandle(record)}.FromUnowned(handle); - // Implement this to perform additional steps in the constructor - partial void Initialize(); + return new {name}(safeHandle); + }} {record.Constructors .Select(ConstructorRenderer.Render) .Join(Environment.NewLine)} - {FunctionRenderer.Render(record.TypeFunction)} +#if NET7_0_OR_GREATER + static GObject.Type GObject.GTypeProvider.GetGType() +#else + public static GObject.Type GetGType() +#endif + {{ + return {RenderGetGType(record.TypeFunction!)}; + }} {record.Fields .Select(f => RenderField(record, f)) @@ -108,6 +118,11 @@ public void Dispose() }} }}"; } + + private static string RenderGetGType(GirModel.Function function) + { + return $"{Namespace.GetInternalName(function.Namespace)}.{function.Parent!.Name}.{Function.GetName(function)}()"; + } private static string RenderField(GirModel.Record record, GirModel.Field field) { diff --git a/src/Libs/GObject-2.0/Public/GTypeProvider.cs b/src/Libs/GLib-2.0/GObject/Public/GTypeProvider.cs similarity index 100% rename from src/Libs/GObject-2.0/Public/GTypeProvider.cs rename to src/Libs/GLib-2.0/GObject/Public/GTypeProvider.cs diff --git a/src/Libs/GLib-2.0/GObject/Public/InstanceFactory.cs b/src/Libs/GLib-2.0/GObject/Public/InstanceFactory.cs new file mode 100644 index 000000000..460f25cbe --- /dev/null +++ b/src/Libs/GLib-2.0/GObject/Public/InstanceFactory.cs @@ -0,0 +1,10 @@ +using System; + +namespace GObject; + +public interface InstanceFactory +{ +#if NET7_0_OR_GREATER + static abstract object Create(IntPtr handle, bool ownsHandle); +#endif +} diff --git a/src/Libs/GLib-2.0/Public/IHandle.cs b/src/Libs/GLib-2.0/Public/IHandle.cs deleted file mode 100644 index 86ce1145d..000000000 --- a/src/Libs/GLib-2.0/Public/IHandle.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace GLib; - -public interface IHandle -{ - IntPtr Handle { get; } -} diff --git a/src/Libs/GObject-2.0/Internal/BoxedWrapper.cs b/src/Libs/GObject-2.0/Internal/BoxedWrapper.cs index 373efac44..4f425a799 100644 --- a/src/Libs/GObject-2.0/Internal/BoxedWrapper.cs +++ b/src/Libs/GObject-2.0/Internal/BoxedWrapper.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Reflection; namespace GObject.Internal; @@ -8,34 +6,11 @@ public class BoxedWrapper { public static object WrapHandle(IntPtr handle, bool ownsHandle, Type gtype) { - System.Type trueType = TypeDictionary.GetSystemType(gtype); - if (handle == IntPtr.Zero) - throw new NullReferenceException($"Failed to wrap handle as type <{trueType}>. Null handle passed to WrapHandle."); - - // Get constructor for the true type - var ctr = GetBoxedConstructor(trueType); - - if (ctr is null) - throw new Exception($"Type {trueType} does not define an IntPtr constructor. This could mean improperly defined bindings"); - - var result = ctr.Invoke(new object[] { handle, ownsHandle }); + throw new NullReferenceException("Failed to wrap boxed handle as a NULL handle was given."); - if (result == null) - throw new Exception($"Type {trueType}'s factory method returned a null object. This could mean improperly defined bindings"); - - return result; - } - - private static ConstructorInfo? GetBoxedConstructor(System.Type type) - { - // Create using 'IntPtr, ownsHandle' constructor - ConstructorInfo? ctor = type.GetConstructor( - System.Reflection.BindingFlags.NonPublic - | System.Reflection.BindingFlags.Public - | System.Reflection.BindingFlags.Instance, - null, new[] { typeof(IntPtr), typeof(bool) }, null - ); - return ctor; + var createInstance = DynamicInstanceFactory.GetInstanceFactory(gtype); + + return createInstance(handle, ownsHandle); } } diff --git a/src/Libs/GObject-2.0/Internal/CreateInstance.cs b/src/Libs/GObject-2.0/Internal/CreateInstance.cs new file mode 100644 index 000000000..fd8ec3df4 --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/CreateInstance.cs @@ -0,0 +1,5 @@ +using System; + +namespace GObject.Internal; + +public delegate object CreateInstance(IntPtr handle, bool ownsHandle); diff --git a/src/Libs/GObject-2.0/Internal/DynamicInstanceFactory.cs b/src/Libs/GObject-2.0/Internal/DynamicInstanceFactory.cs new file mode 100644 index 000000000..76f0cb74b --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/DynamicInstanceFactory.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace GObject.Internal; + +public static class DynamicInstanceFactory +{ + private static readonly Dictionary InstanceFactories = new(); + + public static void Register(Type type, CreateInstance handleWrapper) + { + InstanceFactories.Add(type, handleWrapper); + } + + internal static object Create(IntPtr handle, bool ownsHandle) + { + var type = GetType(handle); + var createInstance = GetInstanceFactory(type); + return createInstance(handle, ownsHandle); + } + + internal static CreateInstance GetInstanceFactory(Type gtype) + { + if (InstanceFactories.TryGetValue(gtype, out CreateInstance? factory)) + return factory; + + // If gtype is not in the type dictionary, walk up the + // tree until we find a type that is. As all objects are + // descended from GObject, we will eventually find a parent + // type that is registered. + + while (!InstanceFactories.TryGetValue(gtype, out factory)) + { + gtype = new Type(Functions.TypeParent(gtype.Value)); + if (gtype.Value == (nuint) BasicType.Invalid || + gtype.Value == (nuint) BasicType.None) + throw new Exception("Could not retrieve parent type - is the typeid valid?"); + } + + return factory; + } + + private static unsafe Type GetType(IntPtr handle) + { + var gclass = Unsafe.AsRef((void*) handle).GClass; + var gtype = Unsafe.AsRef((void*) gclass).GType; + + if (gtype == 0) + throw new Exception("Could not retrieve type from class struct - is the struct valid?"); + + return new Type(gtype); + } +} diff --git a/src/Libs/GObject-2.0/Internal/GTypeProviderHelper.cs b/src/Libs/GObject-2.0/Internal/GTypeProviderHelper.cs index c118e82aa..b3618ba17 100644 --- a/src/Libs/GObject-2.0/Internal/GTypeProviderHelper.cs +++ b/src/Libs/GObject-2.0/Internal/GTypeProviderHelper.cs @@ -7,14 +7,14 @@ namespace GObject.Internal; //TODO: Remove this class once support for dotnet 6 is dropped public static class GTypeProviderHelper { - public static Type GetGType() where T : GObject.Object, GTypeProvider + public static Type GetGType() where T : GTypeProvider { - var getGTypeMethod = typeof(T).GetMethod(nameof(GObject.Object.GetGType)); + var getGTypeMethod = typeof(T).GetMethod("GetGType"); if (getGTypeMethod is null) - throw new Exception($"Method {nameof(GObject.Object.GetGType)} not found on {typeof(T).Name}"); + throw new Exception($"Method 'GetGType' not found on {typeof(T).Name}"); - return (Type) (getGTypeMethod.Invoke(null, null) ?? throw new Exception($"Method {nameof(GObject.Object.GetGType)} on {typeof(T).Name} did not return a result")); + return (Type) (getGTypeMethod.Invoke(null, null) ?? throw new Exception($"Method 'GetGType' on {typeof(T).Name} did not return a result")); } } #endif diff --git a/src/Libs/GObject-2.0/Internal/InstanceCache.cs b/src/Libs/GObject-2.0/Internal/InstanceCache.cs new file mode 100644 index 000000000..55bafe48e --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/InstanceCache.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace GObject.Internal; + +internal static class InstanceCache +{ + private static readonly Dictionary Cache = new(); + + public static bool TryGetObject(IntPtr handle, [NotNullWhen(true)] out GObject.Object? obj) + { + if (Cache.TryGetValue(handle, out ToggleRef? toggleRef)) + { + if (toggleRef.Object is not null) + { + obj = toggleRef.Object; + return true; + } + } + + obj = null; + return false; + } + + public static void Add(IntPtr handle, GObject.Object obj) + { + lock (Cache) + { + Cache[handle] = new ToggleRef(obj); + } + + Debug.WriteLine($"Handle {handle}: Added object of type '{obj.GetType()}' to {nameof(InstanceCache)}"); + } + + public static void Remove(IntPtr handle) + { + lock (Cache) + { + if (Cache.Remove(handle, out var toggleRef)) + toggleRef.Dispose(); + } + + Debug.WriteLine($"Handle {handle}: Removed object from {nameof(InstanceCache)}."); + } +} diff --git a/src/Libs/GObject-2.0/Internal/InstanceFactoryHelper.cs b/src/Libs/GObject-2.0/Internal/InstanceFactoryHelper.cs new file mode 100644 index 000000000..d8d748a40 --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/InstanceFactoryHelper.cs @@ -0,0 +1,20 @@ +using System; + +namespace GObject.Internal; + +#if NET6_0 + +//TODO: Remove this class once support for dotnet 6 is dropped +public static class InstanceFactoryHelper +{ + public static object Create(IntPtr handle, bool ownsHandle) where T : InstanceFactory + { + var createMethod = typeof(T).GetMethod(nameof(Create)); + + if (createMethod is null) + throw new Exception($"Method {nameof(Create)} not found on {typeof(T).Name}"); + + return createMethod.Invoke(null, new object[]{ handle, ownsHandle }) ?? throw new Exception($"Method {nameof(Create)} on {typeof(T).Name} did not return a result"); + } +} +#endif diff --git a/src/Libs/GObject-2.0/Internal/InstanceWrapper.cs b/src/Libs/GObject-2.0/Internal/InstanceWrapper.cs new file mode 100644 index 000000000..ce79b3c17 --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/InstanceWrapper.cs @@ -0,0 +1,24 @@ +using System; + +namespace GObject.Internal; + +public static class InstanceWrapper +{ + public static object? WrapNullableHandle(IntPtr handle, bool ownedRef) + { + return handle == IntPtr.Zero + ? null + : WrapHandle(handle, ownedRef); + } + + public static object WrapHandle(IntPtr handle, bool ownedRef) + { + if (handle == IntPtr.Zero) + throw new NullReferenceException("Failed to wrap handle: Null handle passed to WrapHandle."); + + if (InstanceCache.TryGetObject(handle, out var obj)) + return obj; + + return DynamicInstanceFactory.Create(handle, ownedRef); + } +} diff --git a/src/Libs/GObject-2.0/Internal/ObjectHandle.cs b/src/Libs/GObject-2.0/Internal/ObjectHandle.cs index c7b8070d1..ec8213a3c 100644 --- a/src/Libs/GObject-2.0/Internal/ObjectHandle.cs +++ b/src/Libs/GObject-2.0/Internal/ObjectHandle.cs @@ -1,23 +1,50 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; namespace GObject.Internal; public class ObjectHandle : SafeHandle { - public IntPtr Handle => IsInvalid ? IntPtr.Zero : DangerousGetHandle(); + public override bool IsInvalid => handle == IntPtr.Zero; - public ObjectHandle(IntPtr handle, object obj, bool ownedRef) : base(IntPtr.Zero, true) + public ObjectHandle(IntPtr handle, bool ownsHandle) : base(IntPtr.Zero, true) { - ObjectMapper.Map(handle, obj, ownedRef); SetHandle(handle); + OwnReference(ownsHandle); } - public sealed override bool IsInvalid => handle == IntPtr.Zero; + private void OwnReference(bool ownedRef) + { + if (!ownedRef) + { + // - Unowned GObjects need to be refed to bind them to this instance + // - Unowned InitiallyUnowned floating objects need to be ref_sinked + // - Unowned InitiallyUnowned non-floating objects need to be refed + // As ref_sink behaves like ref in case of non floating instances we use it for all 3 cases + Object.RefSink(handle); + } + else + { + //In case we own the ref because the ownership was fully transfered to us we + //do not need to ref the object at all. + + Debug.Assert(!Internal.Object.IsFloating(handle), $"Handle {handle}: Owned floating references are not possible."); + } + } - protected sealed override bool ReleaseHandle() + internal void Cache(GObject.Object obj) { - ObjectMapper.Unmap(handle); + InstanceCache.Add(handle, obj); + } + + protected override bool ReleaseHandle() + { + RemoveMemoryPressure(); + InstanceCache.Remove(handle); return true; } + + protected internal virtual void AddMemoryPressure() { } + protected virtual void RemoveMemoryPressure() { } } diff --git a/src/Libs/GObject-2.0/Internal/ObjectMapper.ToggleRef.cs b/src/Libs/GObject-2.0/Internal/ObjectMapper.ToggleRef.cs deleted file mode 100644 index efe9d2c51..000000000 --- a/src/Libs/GObject-2.0/Internal/ObjectMapper.ToggleRef.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Diagnostics; - -namespace GObject.Internal; - -public partial class ObjectMapper -{ - private class ToggleRef : IDisposable - { - private object _reference; - private readonly ToggleNotify _callback; - private readonly IntPtr _handle; - - public object? Object - { - get - { - if (_reference is not WeakReference weakRef) - return _reference; - - if (weakRef.Target is { } target) - return target; - - return null; - } - } - - /// - /// Initializes a toggle ref. The given object must be already owned by C# as the owned - /// reference is exchanged with a toggling reference meaning the toggle reference is taking control - /// over the reference. - /// This object saves a strong reference to the given object which prevents it from beeing garbage - /// collected. This strong reference is hold as long as there are other than our own toggling ref - /// on the given object. - /// If our toggeling ref is the last ref on the given object the strong reference is changed into a - /// weak reference. This allows the garbage collector to free the C# object which must result in the - /// call of the Dispose method of the ToggleRef. The Dispose mehtod removes the added toggle reference - /// and thus frees the last reference to the C object. - /// - public ToggleRef(IntPtr handle, object obj, bool ownedRef) - { - _reference = obj; - _callback = ToggleReference; - _handle = handle; - - OwnReference(ownedRef); - RegisterToggleRef(); - } - - private void RegisterToggleRef() - { - Internal.Object.AddToggleRef(_handle, _callback, IntPtr.Zero); - Internal.Object.Unref(_handle); - } - - private void OwnReference(bool ownedRef) - { - if (!ownedRef) - { - // - Unowned GObjects need to be refed to bind them to this instance - // - Unowned InitiallyUnowned floating objects need to be ref_sinked - // - Unowned InitiallyUnowned non-floating objects need to be refed - // As ref_sink behaves like ref in case of non floating instances we use it for all 3 cases - Internal.Object.RefSink(_handle); - } - else - { - //In case we own the ref because the ownership was fully transfered to us we - //do not need to ref the object at all. - - Debug.Assert(!Internal.Object.IsFloating(_handle), $"Handle {_handle}: Owned floating references are not possible."); - } - } - - private void ToggleReference(IntPtr data, IntPtr @object, bool isLastRef) - { - if (!isLastRef && _reference is WeakReference weakRef) - { - if (weakRef.Target is { } weakObj) - _reference = weakObj; - else - throw new Exception($"Handle {_handle}: Could not toggle reference to strong. It got garbage collected."); - } - else if (isLastRef && _reference is not WeakReference) - { - _reference = new WeakReference(_reference); - } - } - - public void Dispose() - { - var sourceFunc = new GLib.Internal.SourceFuncAsyncHandler(() => - { - Internal.Object.RemoveToggleRef(_handle, _callback, IntPtr.Zero); - return false; - }); - GLib.Internal.MainContext.Invoke(GLib.Internal.MainContextUnownedHandle.NullHandle, sourceFunc.NativeCallback, IntPtr.Zero); - } - } -} diff --git a/src/Libs/GObject-2.0/Internal/ObjectMapper.cs b/src/Libs/GObject-2.0/Internal/ObjectMapper.cs deleted file mode 100644 index e50a8775f..000000000 --- a/src/Libs/GObject-2.0/Internal/ObjectMapper.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using GLib; - -namespace GObject.Internal; - -public static partial class ObjectMapper -{ - private static readonly Dictionary WrapperObjects = new(); - - public static int ObjectCount => WrapperObjects.Count; - - public static bool TryGetObject(IntPtr handle, [NotNullWhen(true)] out T? obj) where T : class, IHandle - { - if (WrapperObjects.TryGetValue(handle, out ToggleRef? weakRef)) - { - if (weakRef.Object is not null) - { - obj = (T) weakRef.Object; - return true; - } - } - - obj = null; - return false; - } - - public static void Map(IntPtr handle, object obj, bool ownedRef) - { - lock (WrapperObjects) - { - WrapperObjects[handle] = new ToggleRef(handle, obj, ownedRef); - } - - Debug.WriteLine($"Handle {handle}: Mapped object of type '{obj.GetType()}' as owned ref '{ownedRef}'."); - } - - public static void Unmap(IntPtr handle) - { - lock (WrapperObjects) - { - if (WrapperObjects.Remove(handle, out var toggleRef)) - toggleRef.Dispose(); - } - - Debug.WriteLine($"Handle {handle}: Unmapped object."); - } -} diff --git a/src/Libs/GObject-2.0/Internal/ObjectWrapper.cs b/src/Libs/GObject-2.0/Internal/ObjectWrapper.cs deleted file mode 100644 index 48640d48b..000000000 --- a/src/Libs/GObject-2.0/Internal/ObjectWrapper.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.CompilerServices; -using GLib; - -namespace GObject.Internal; - -public static class ObjectWrapper -{ - public static T? WrapNullableHandle(IntPtr handle, bool ownedRef) where T : class, IHandle - { - return handle == IntPtr.Zero - ? null - : WrapHandle(handle, ownedRef); - } - - public static T WrapHandle(IntPtr handle, bool ownedRef) where T : class, IHandle - { - Debug.Assert( - condition: typeof(T).IsClass && typeof(T).IsAssignableTo(typeof(GObject.Object)), - message: "Type 'T' must be a GObject-based class" - ); - - if (handle == IntPtr.Zero) - throw new NullReferenceException($"Failed to wrap handle as type <{typeof(T).FullName}>. Null handle passed to WrapHandle."); - - if (ObjectMapper.TryGetObject(handle, out T? obj)) - return obj; - - //In case of classes prefer the type reported by the gobject type system over - //the expected type as often an API returns a less derived class in it's public - //API then the actual one. - Type gtype = GetTypeFromInstance(handle); - - Debug.Assert( - condition: Functions.TypeName(gtype.Value).ConvertToString() == Functions.TypeNameFromInstance(new TypeInstanceUnownedHandle(handle)).ConvertToString(), - message: "GType name of instance and class do not match" - ); - - System.Type trueType = TypeDictionary.GetSystemType(gtype); - ConstructorInfo? ctor = GetObjectConstructor(trueType); - - if (ctor == null) - throw new Exception($"Type {typeof(T).FullName} does not define an IntPtr constructor. This could mean improperly defined bindings"); - - return (T) ctor.Invoke(new object[] { handle, ownedRef }); - } - - public static T? WrapNullableInterfaceHandle(IntPtr handle, bool ownedRef) where T : class, IHandle - { - return handle == IntPtr.Zero - ? null - : WrapInterfaceHandle(handle, ownedRef); - } - - public static T WrapInterfaceHandle(IntPtr handle, bool ownedRef) where T : class, IHandle - { - Debug.Assert( - condition: typeof(T).IsClass && typeof(T).IsAssignableTo(typeof(GObject.Object)), - message: "Type 'T' must be a GObject-based class" - ); - - if (handle == IntPtr.Zero) - throw new NullReferenceException($"Failed to wrap handle as type <{typeof(T).FullName}>. Null handle passed to WrapHandle."); - - if (ObjectMapper.TryGetObject(handle, out T? obj)) - return obj; - - //In case of interfaces prefer the given type over the type reported by the gobject - //type system as the reported type is probably not part of the public API. Otherwise the - //class itself would be returned and not an interface. - ConstructorInfo? ctor = GetObjectConstructor(typeof(T)); - - if (ctor == null) - throw new Exception($"Type {typeof(T).FullName} does not define an IntPtr constructor. This could mean improperly defined bindings"); - - return (T) ctor.Invoke(new object[] { handle, ownedRef }); - } - - private static unsafe Type GetTypeFromInstance(IntPtr handle) - { - var gclass = Unsafe.AsRef((void*) handle).GClass; - var gtype = Unsafe.AsRef((void*) gclass).GType; - - if (gtype == 0) - throw new Exception("Could not retrieve type from class struct - is the struct valid?"); - - return new Type(gtype); - } - - private static ConstructorInfo? GetObjectConstructor(System.Type type) - { - // Create using 'IntPtr' constructor - ConstructorInfo? ctor = type.GetConstructor( - System.Reflection.BindingFlags.NonPublic - | System.Reflection.BindingFlags.Public - | System.Reflection.BindingFlags.Instance, - null, new[] { typeof(IntPtr), typeof(bool) }, null - ); - return ctor; - } -} diff --git a/src/Libs/GObject-2.0/Internal/SubclassRegistrar.cs b/src/Libs/GObject-2.0/Internal/SubclassRegistrar.cs new file mode 100644 index 000000000..2c2a52cfb --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/SubclassRegistrar.cs @@ -0,0 +1,77 @@ +using System.Diagnostics; + +namespace GObject.Internal; + +/// + /// Registers a custom subclass with the GObject type system. + /// + public static class SubclassRegistrar + { + public static Type Register() + where TSubclass : InstanceFactory + where TParent : GTypeProvider + { + var newType = RegisterNewGType(); + DynamicInstanceFactory.Register(newType, TSubclass.Create); + + return newType; + } + + private static Type RegisterNewGType() + where TSubclass : InstanceFactory + where TParent : GTypeProvider + { + var parentType = TParent.GetGType(); + var parentTypeInfo = TypeQueryOwnedHandle.Create(); + Functions.TypeQuery(parentType, parentTypeInfo); + + if (parentTypeInfo.GetType() == 0) + throw new TypeRegistrationException("Could not query parent type"); + + Debug.WriteLine($"Registering new type {typeof(TSubclass).FullName} with parent {typeof(TParent).FullName}"); + + // Create TypeInfo + //TODO: Callbacks for "ClassInit" and "InstanceInit" are disabled because if multiple instances + //of the same type are created, the typeInfo object can get garbagec collected in the mean time + //and with it the instances of "DoClassInit" and "DoInstanceInit". If the callback occurs the + //runtime can't do the call anymore and crashes with: + //A callback was made on a garbage collected delegate of type 'GObject-2.0!GObject.Internal.InstanceInitFunc::Invoke' + //Fix this by caching the garbage collected instances somehow + var handle = TypeInfoOwnedHandle.Create(); + handle.SetClassSize((ushort) parentTypeInfo.GetClassSize()); + handle.SetInstanceSize((ushort) parentTypeInfo.GetInstanceSize()); + //handle.SetClassInit(); + //handle.SetInstanceInit(); + + var qualifiedName = QualifyName(typeof(TSubclass)); + var typeid = Functions.TypeRegisterStatic(parentType, GLib.Internal.NonNullableUtf8StringOwnedHandle.Create(qualifiedName), handle, 0); + + if (typeid == 0) + throw new TypeRegistrationException("Type Registration Failed!"); + + return new Type(typeid); + } + + private static string QualifyName(System.Type type) + => type.ToString() + .Replace(".", string.Empty) + .Replace("+", string.Empty) + .Replace("`", string.Empty) + .Replace("[", "_") + .Replace("]", string.Empty) + .Replace(" ", string.Empty) + .Replace(",", "_"); + + /* TODO: Enable if init functions are supported again + // Default Handler for class initialisation. + private static void DoClassInit(IntPtr gClass, IntPtr classData) + { + Console.WriteLine("Subclass type class initialised!"); + } + // Default Handler for instance initialisation. + private static void DoInstanceInit(IntPtr gClass, IntPtr classData) + { + Console.WriteLine("Subclass instance initialised!"); + } + */ + } diff --git a/src/Libs/GObject-2.0/Internal/ToggleRef.cs b/src/Libs/GObject-2.0/Internal/ToggleRef.cs new file mode 100644 index 000000000..92f828419 --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/ToggleRef.cs @@ -0,0 +1,75 @@ +using System; + +namespace GObject.Internal; + + internal class ToggleRef : IDisposable + { + private readonly IntPtr _handle; + private readonly ToggleNotify _callback; + + private object _reference; + + public GObject.Object? Object + { + get + { + if(_reference is WeakReference weakRef) + return (GObject.Object?) weakRef.Target; + + return (GObject.Object) _reference; + } + } + + /// + /// Initializes a toggle ref. The given object must be already owned by C# as the owned + /// reference is exchanged with a toggling reference meaning the toggle reference is taking control + /// over the reference. + /// This object saves a strong reference to the given object which prevents it from beeing garbage + /// collected. This strong reference is hold as long as there are other than our own toggling ref + /// on the given object. + /// If our toggeling ref is the last ref on the given object the strong reference is changed into a + /// weak reference. This allows the garbage collector to free the C# object which must result in the + /// call of the Dispose method of the ToggleRef. The Dispose method removes the added toggle reference + /// and thus frees the last reference to the C object. + /// + public ToggleRef(GObject.Object obj) + { + _reference = obj; + _handle = obj.GetHandle(); + + _callback = ToggleReference; + + RegisterToggleRef(); + } + + private void RegisterToggleRef() + { + Internal.Object.AddToggleRef(_handle, _callback, IntPtr.Zero); + Internal.Object.Unref(_handle); + } + + private void ToggleReference(IntPtr data, IntPtr @object, bool isLastRef) + { + if (!isLastRef && _reference is WeakReference weakRef) + { + if (weakRef.Target is { } weakObj) + _reference = weakObj; + else + throw new Exception($"Handle {_handle}: Could not toggle reference to strong. It got garbage collected."); + } + else if (isLastRef && _reference is not WeakReference) + { + _reference = new WeakReference(_reference); + } + } + + public void Dispose() + { + var sourceFunc = new GLib.Internal.SourceFuncAsyncHandler(() => + { + Internal.Object.RemoveToggleRef(_handle, _callback, IntPtr.Zero); + return false; + }); + GLib.Internal.MainContext.Invoke(GLib.Internal.MainContextUnownedHandle.NullHandle, sourceFunc.NativeCallback, IntPtr.Zero); + } + } diff --git a/src/Libs/GObject-2.0/Internal/TypeDictionary.cs b/src/Libs/GObject-2.0/Internal/TypeDictionary.cs deleted file mode 100644 index 82088b822..000000000 --- a/src/Libs/GObject-2.0/Internal/TypeDictionary.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace GObject.Internal; - -/// -/// This exception is thrown when a is not found in -/// the type dictionary. -/// -public class TypeNotFoundException : Exception -{ - public TypeNotFoundException(System.Type managedType) - : base($"Type {managedType.FullName} not registered in type dictionary") { } -} - - - -/// -/// The global type dictionary which maps between the .NET Type System and -/// the GType dynamic type system. -/// -public static class TypeDictionary -{ - private static readonly Dictionary _systemTypeDict = new(); - private static readonly Dictionary _reverseTypeDict = new(); - - /// - /// Add a new mapping of (System.Type, GObject.Type) to the type dictionary. - /// - /// A managed type that has not already been registered - /// The gtype retrieved from the object's get type method or from registration. - public static void Add(System.Type systemType, Type type) - { - // Check we have not already registered - Debug.Assert( - condition: !_systemTypeDict.ContainsKey(systemType), - message: $"Type {nameof(systemType)} is already registered in the type dictionary." - ); - - _systemTypeDict[systemType] = type; - _reverseTypeDict[type] = systemType; - } - - /// - /// For a given managed GObject-based class, retrieve the corresponding gtype. - /// - /// The type of a class that is equal or derived from - /// The equivalent GType - /// The given type is not registered in the type dictionary. The caller should register it themselves. - internal static Type GetGType(System.Type type) - { - Debug.Assert( - condition: type.IsAssignableTo(typeof(GObject.Object)), - message: $"Parameter {type} is not a GObject or subclass of GObject" - ); - - if (!_systemTypeDict.TryGetValue(type, out Type gType)) - throw new TypeNotFoundException(type); - - return gType; - } - - /// - /// For a given gtype, retrieve the corresponding managed type. - /// - /// A type from the GType type system - /// The equivalent managed type - internal static System.Type GetSystemType(Type gtype) - { - if (_reverseTypeDict.TryGetValue(gtype, out System.Type? sysType)) - return sysType; - - // If gtype is not in the type dictionary, walk up the - // tree until we find a type that is. As all objects are - // descended from GObject, we will eventually find a parent - // type that is registered. - - while (!_reverseTypeDict.TryGetValue(gtype, out sysType)) - { - gtype = new Type(Functions.TypeParent(gtype.Value)); - if (gtype.Value == (nuint) BasicType.Invalid || - gtype.Value == (nuint) BasicType.None) - throw new Exception("Could not retrieve parent type - is the typeid valid?"); - } - - // Store for future lookups - _reverseTypeDict[gtype] = sysType; - - return sysType; - } - - // These may be unneeded - keep for now - internal static bool ContainsGType(Type gtype) - => _reverseTypeDict.ContainsKey(gtype); - - internal static bool ContainsSystemType(System.Type type) - => _systemTypeDict.ContainsKey(type); -} diff --git a/src/Libs/GObject-2.0/Internal/TypeRegistrar.cs b/src/Libs/GObject-2.0/Internal/TypeRegistrar.cs deleted file mode 100644 index 6c71d6e0e..000000000 --- a/src/Libs/GObject-2.0/Internal/TypeRegistrar.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace GObject.Internal; - -/// -/// Thrown when type registration with GType fails -/// -internal class TypeRegistrationException : Exception -{ - public TypeRegistrationException(string message) : base(message) { } -} - -/// -/// A set of utility functions to register new types with the -/// GType dynamic type system. -/// -public static class TypeRegistrar -{ - /// - /// Registers with GType a new child class of 'parentType'. - /// - /// The name of the class - /// The parent class to derive from - /// The newly registered type - /// The type could not be registered - internal static Type RegisterGType(string qualifiedName, Type parentType) - { - var typeQuery = TypeQueryOwnedHandle.Create(); - Functions.TypeQuery(parentType, typeQuery); - - if (typeQuery.GetType() == 0) - throw new TypeRegistrationException("Could not query parent type"); - - Debug.WriteLine($"Registering new type {qualifiedName} with parent {parentType.ToString()}"); - - // Create TypeInfo - //TODO: Callbacks for "ClassInit" and "InstanceInit" are disabled because if multiple instances - //of the same type are created, the typeInfo object can get garbagec collected in the mean time - //and with it the instances of "DoClassInit" and "DoInstanceInit". If the callback occurs the - //runtime can't do the call anymore and crashes with: - //A callback was made on a garbage collected delegate of type 'GObject-2.0!GObject.Internal.InstanceInitFunc::Invoke' - //Fix this by caching the garbage collected instances somehow - var handle = TypeInfoOwnedHandle.Create(); - handle.SetClassSize((ushort) typeQuery.GetClassSize()); - handle.SetInstanceSize((ushort) typeQuery.GetInstanceSize()); - //handle.SetClassInit(); - //handle.SetInstanceInit(); - - var typeid = Functions.TypeRegisterStatic(parentType, GLib.Internal.NonNullableUtf8StringOwnedHandle.Create(qualifiedName), handle, 0); - - if (typeid == 0) - throw new TypeRegistrationException("Type Registration Failed!"); - - return new Type(typeid); - } - - /* TODO: Enable if init functions are supported again - // Default Handler for class initialisation. - private static void DoClassInit(IntPtr gClass, IntPtr classData) - { - Console.WriteLine("Subclass type class initialised!"); - } - - // Default Handler for instance initialisation. - private static void DoInstanceInit(IntPtr gClass, IntPtr classData) - { - Console.WriteLine("Subclass instance initialised!"); - } - */ -} diff --git a/src/Libs/GObject-2.0/Internal/TypeRegistrationException.cs b/src/Libs/GObject-2.0/Internal/TypeRegistrationException.cs new file mode 100644 index 000000000..52180a160 --- /dev/null +++ b/src/Libs/GObject-2.0/Internal/TypeRegistrationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace GObject.Internal; + +/// +/// Thrown when type registration with GType fails +/// +public class TypeRegistrationException : Exception +{ + internal TypeRegistrationException(string message) : base(message) { } +} diff --git a/src/Libs/GObject-2.0/Public/Closure.cs b/src/Libs/GObject-2.0/Public/Closure.cs index 70a64a03d..1082493df 100644 --- a/src/Libs/GObject-2.0/Public/Closure.cs +++ b/src/Libs/GObject-2.0/Public/Closure.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; namespace GObject; diff --git a/src/Libs/GObject-2.0/Public/IObject.cs b/src/Libs/GObject-2.0/Public/IObject.cs deleted file mode 100644 index 1569b7111..000000000 --- a/src/Libs/GObject-2.0/Public/IObject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.ComponentModel; - -namespace GObject; - -public interface IObject -{ - -} diff --git a/src/Libs/GObject-2.0/Public/Object.Registration.cs b/src/Libs/GObject-2.0/Public/Object.Registration.cs deleted file mode 100644 index 68c98aa1f..000000000 --- a/src/Libs/GObject-2.0/Public/Object.Registration.cs +++ /dev/null @@ -1,58 +0,0 @@ -using GObject.Internal; - -namespace GObject; - -public partial class Object -{ - private Type GetGTypeOrRegister(System.Type type) - { - if (TypeDictionary.ContainsSystemType(type)) - return TypeDictionary.GetGType(type); - - // We are not in the type dictionary, which means we are - // an unregistered managed subclass type. There are two ways - // to register subclasses: Dynamically (reflection) and - // Statically (by source generation). - - // Static registration happens on application startup in the - // module initialiser if source generator support is present - // at build time (note: not implemented as of 29/04/21) - - // Therefore, we can assume static registration did not go ahead - // and we should resort to the fallback reflection-based registration - // implemented below. We should therefore register ourselves - // and every type we inherit from that has also not been registered. - - FallbackRegistrationStrategy.RegisterSubclassRecursive(type); - - return TypeDictionary.GetGType(type); - } - - private static class FallbackRegistrationStrategy - { - private static string QualifyName(System.Type type) - => type.ToString() - .Replace(".", string.Empty) - .Replace("+", string.Empty) - .Replace("`", string.Empty) - .Replace("[", "_") - .Replace("]", string.Empty) - .Replace(" ", string.Empty) - .Replace(",", "_"); - - public static void RegisterSubclassRecursive(System.Type type) - { - System.Type baseType = type.BaseType!; - if (!TypeDictionary.ContainsSystemType(baseType)) - RegisterSubclassRecursive(baseType); - - // Do actual registration - Type gtype = TypeRegistrar.RegisterGType( - qualifiedName: QualifyName(type), - parentType: TypeDictionary.GetGType(baseType) - ); - - TypeDictionary.Add(type, gtype); - } - } -} diff --git a/src/Libs/GObject-2.0/Public/Object.Signals.cs b/src/Libs/GObject-2.0/Public/Object.Signals.cs index fe7bb11c4..7b90b9298 100644 --- a/src/Libs/GObject-2.0/Public/Object.Signals.cs +++ b/src/Libs/GObject-2.0/Public/Object.Signals.cs @@ -11,7 +11,7 @@ public partial class Object internal void SignalConnectClosure(SignalDefinition signalDefinition, Delegate callback, Closure closure, bool after, string? detail) { var detailQuark = GLib.Functions.QuarkFromString(detail); - var handlerId = Internal.Functions.SignalConnectClosureById(Handle, signalDefinition.Id, detailQuark, closure.Handle, after); + var handlerId = Internal.Functions.SignalConnectClosureById(Handle.DangerousGetHandle(), signalDefinition.Id, detailQuark, closure.Handle, after); if (handlerId.Value == 0) throw new Exception($"Could not connect to event {signalDefinition.ManagedName}"); @@ -24,7 +24,7 @@ internal void Disconnect(SignalDefinition signalDefinition, Delegate callback) if (!_signalStore.TryGetValue((signalDefinition, callback), out var tuple)) return; - Internal.Functions.SignalHandlerDisconnect(Handle, tuple.Item1); + Internal.Functions.SignalHandlerDisconnect(Handle.DangerousGetHandle(), tuple.Item1); tuple.Item2.Dispose(); _signalStore.Remove((signalDefinition, callback)); } @@ -33,7 +33,7 @@ private void DisposeClosures() { foreach (var item in _signalStore.Values) { - Internal.Functions.SignalHandlerDisconnect(Handle, item.Item1); + Internal.Functions.SignalHandlerDisconnect(Handle.DangerousGetHandle(), item.Item1); item.Item2.Dispose(); } diff --git a/src/Libs/GObject-2.0/Public/Object.cs b/src/Libs/GObject-2.0/Public/Object.cs index 5b4fd48ba..0a292e3fc 100644 --- a/src/Libs/GObject-2.0/Public/Object.cs +++ b/src/Libs/GObject-2.0/Public/Object.cs @@ -1,77 +1,24 @@ using System; -using System.ComponentModel; using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using GLib; using GObject.Internal; namespace GObject; -public partial class Object : IObject, IDisposable, IHandle +public partial class Object : IDisposable { - private readonly ObjectHandle _handle; + public ObjectHandle Handle { get; } - public IntPtr Handle => _handle.Handle; - - /// - /// Initializes a wrapper for an existing object - /// - /// - /// Defines if the handle is owned by us. If not owned by us it is refed to keep it around. - protected Object(IntPtr handle, bool ownedRef) - { - _handle = new ObjectHandle(handle, this, ownedRef); - Initialize(); - } - - /// - /// Constructs a new object - /// - /// True if the ownership of the resulting resulting handle will be transfered. Otherwise false. - /// - /// This constructor is protected to be sure that there is no caller (enduser) keeping a reference to - /// the construct parameters as the contained values are freed at the end of this constructor. - /// If certain constructors are needed they need to be implemented with concrete constructor arguments in - /// a higher layer. - protected Object(bool owned, ConstructArgument[] constructArguments) - { - Type gtype = GetGTypeOrRegister(GetType()); - - IntPtr handle = Internal.Object.NewWithProperties( - objectType: gtype, - nProperties: (uint) constructArguments.Length, - names: GetNames(constructArguments), - values: ValueArray2OwnedHandle.Create(constructArguments.Select(x => x.Value).ToArray()) - ); - - // We can't check if a reference is floating via "g_object_is_floating" here - // as the function could be "lying" depending on the intent of framework writers. - // E.g. A Gtk.Window created via "g_object_new_with_properties" returns an unowned - // reference which is not marked as floating as the gtk toolkit "owns" it. - // For this reason we just delegate the problem to the caller and require a - // definition wether the ownership of the new object will be transered to us or not. - _handle = new ObjectHandle(handle, this, owned); - - Initialize(); - } - - private string[] GetNames(ConstructArgument[] constructParameters) - => constructParameters.Select(x => x.Name).ToArray(); - - /// - /// Does common initialization tasks. - /// Wrapper and subclasses can override here to perform immediate initialization. - /// - protected virtual void Initialize() + public Object(ObjectHandle handle) { - Debug.WriteLine($"Handle {_handle.Handle}: Initialising object of type {GetType()}."); + Handle = handle; + Handle.Cache(this); + Handle.AddMemoryPressure(); } - public virtual void Dispose() + public void Dispose() { - Debug.WriteLine($"Handle {_handle.Handle}: Disposing object of type {GetType()}."); + Debug.WriteLine($"Handle {Handle.DangerousGetHandle()}: Disposing object of type {GetType()}."); DisposeClosures(); - _handle.Dispose(); + Handle.Dispose(); } } diff --git a/src/Libs/GObject-2.0/Public/Property.cs b/src/Libs/GObject-2.0/Public/Property.cs index 18e9a98d1..c048d3b47 100644 --- a/src/Libs/GObject-2.0/Public/Property.cs +++ b/src/Libs/GObject-2.0/Public/Property.cs @@ -56,7 +56,7 @@ public void Set(K obj, T value) if (obj is not Object o) throw new ArgumentException($"Can't set property {ManagedName} for object of type {typeof(K).Name} as it is not derived from {nameof(Object)}."); - var type = GetPropertyType(o.Handle); + var type = GetPropertyType(o.Handle.DangerousGetHandle()); using var gvalue = new Value(type); gvalue.Set(value); diff --git a/src/Libs/GObject-2.0/Public/Value.cs b/src/Libs/GObject-2.0/Public/Value.cs index 723a17345..c39e0ebb3 100644 --- a/src/Libs/GObject-2.0/Public/Value.cs +++ b/src/Libs/GObject-2.0/Public/Value.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using GLib.Internal; using GObject.Internal;