diff --git a/src/Libs/GLib-2.0/GObject/Public/Type.cs b/src/Libs/GLib-2.0/GObject/Public/Type.cs
index 3ff15da8a..a86d04b1b 100644
--- a/src/Libs/GLib-2.0/GObject/Public/Type.cs
+++ b/src/Libs/GLib-2.0/GObject/Public/Type.cs
@@ -3,7 +3,7 @@
namespace GObject;
[StructLayout(LayoutKind.Explicit)]
-public struct Type
+public record struct Type
{
//This is a manual implementation of GObject.Type inside GLib project inside the GObject namespace.
@@ -42,7 +42,8 @@ public struct Type
//Offsets see: https://gitlab.gnome.org/GNOME/glib/blob/master/gobject/gtype.h
- [FieldOffset(0)] private readonly nuint _value;
+ [FieldOffset(0)]
+ private readonly nuint _value;
public Type(nuint value)
{
diff --git a/src/Libs/GObject-2.0/Public/Object2.cs b/src/Libs/GObject-2.0/Public/Object2.cs
new file mode 100644
index 000000000..80f9aa887
--- /dev/null
+++ b/src/Libs/GObject-2.0/Public/Object2.cs
@@ -0,0 +1,377 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using GLib;
+using GObject.Internal;
+
+namespace GObject
+{
+ public class Object2 : IDisposable
+ {
+ private readonly Object2Handle _handle;
+
+ public Object2(Object2Handle handle)
+ {
+ _handle = handle;
+ _handle.Cache(this);
+ _handle.AddMemoryPressure();
+ }
+
+ public IntPtr GetHandle() => _handle.DangerousGetHandle();
+
+ public void Dispose()
+ {
+ _handle.Dispose();
+ }
+ }
+}
+
+
+namespace GObject.Internal
+{
+ public delegate Object2 InstanceFactoryForType(IntPtr handle, bool ownsHandle);
+
+ public interface InterfaceFactory
+ {
+ static abstract Object2 Create(IntPtr handle, bool ownsHandle);
+ }
+
+ public interface ClassFactory
+ {
+ static abstract Object2 Create(IntPtr handle, bool ownsHandle);
+ }
+///
+/// Registers a custom subclass with the GObject type system.
+///
+public static class SubclassRegistrar2
+{
+ public static Type Register()
+ where TSubclass : ClassFactory
+ where TParent : GTypeProvider
+ {
+ var newType = RegisterNewGType();
+ InstanceFactory.Register(newType, TSubclass.Create);
+
+ return newType;
+ }
+
+ private static Type RegisterNewGType()
+ where TSubclass : ClassFactory
+ 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!");
+ }
+ */
+}
+
+public static class InstanceFactory
+{
+ private static readonly Dictionary TypeFactories = new();
+
+ internal static Object2 Create(IntPtr handle, bool ownsHandle)
+ {
+ var type = GetType(handle);
+ var instanceFactory = GetInstanceFactory(type);
+ return instanceFactory(handle, ownsHandle);
+ }
+
+ public static void Register(Type type, InstanceFactoryForType handleWrapper)
+ {
+ TypeFactories.Add(type, handleWrapper);
+ }
+
+ private static InstanceFactoryForType GetInstanceFactory(Type gtype)
+ {
+ if (TypeFactories.TryGetValue(gtype, out InstanceFactoryForType? 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 (!TypeFactories.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);
+ }
+}
+
+ internal class ToggleRef2 : IDisposable
+ {
+ private readonly IntPtr _handle;
+ private readonly ToggleNotify _callback;
+
+ private object _reference;
+
+ public Object2? Object
+ {
+ get
+ {
+ if(_reference is WeakReference weakRef)
+ return (Object2?) weakRef.Target;
+
+ return (Object2) _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 ToggleRef2(Object2 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);
+ }
+ }
+
+ internal static class InstanceCache2
+ {
+ private static readonly Dictionary Cache = new();
+
+ public static bool TryGetObject(IntPtr handle, [NotNullWhen(true)] out Object2? obj)
+ {
+ if (Cache.TryGetValue(handle, out ToggleRef2? toggleRef))
+ {
+ if (toggleRef.Object is not null)
+ {
+ obj = toggleRef.Object;
+ return true;
+ }
+ }
+
+ obj = null;
+ return false;
+ }
+
+ public static void Add(IntPtr handle, Object2 obj)
+ {
+ lock (Cache)
+ {
+ Cache[handle] = new ToggleRef2(obj);
+ }
+
+ Debug.WriteLine($"Handle {handle}: Added object of type '{obj.GetType()}' to {nameof(InstanceCache2)}");
+ }
+
+ 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(InstanceCache2)}.");
+ }
+ }
+
+ public static class InterfaceWrapper
+ {
+ public static T? WrapNullableHandle(IntPtr handle, bool ownedRef) where T : Object2, InterfaceFactory
+ {
+ return handle == IntPtr.Zero
+ ? null
+ : WrapHandle(handle, ownedRef);
+ }
+
+ public static T WrapHandle(IntPtr handle, bool ownedRef) where T : Object2, InterfaceFactory
+ {
+ if (handle == IntPtr.Zero)
+ throw new NullReferenceException($"Failed to wrap handle as type <{typeof(T).FullName}>. Null handle passed to WrapHandle.");
+
+ if (InstanceCache2.TryGetObject(handle, out var obj))
+ return (T) 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.
+
+ return (T) T.Create(handle, ownedRef);
+ }
+ }
+
+ public static class InstanceWrapper
+ {
+ public static Object2? WrapNullableHandle(IntPtr handle, bool ownedRef)
+ {
+ return handle == IntPtr.Zero
+ ? null
+ : WrapHandle(handle, ownedRef);
+ }
+
+ public static Object2 WrapHandle(IntPtr handle, bool ownedRef)
+ {
+ if (handle == IntPtr.Zero)
+ throw new NullReferenceException("Failed to wrap handle: Null handle passed to WrapHandle.");
+
+ if (InstanceCache2.TryGetObject(handle, out var obj))
+ return obj;
+
+ return InstanceFactory.Create(handle, ownedRef);
+ }
+ }
+
+ public class Object2Handle : SafeHandle
+ {
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ public Object2Handle(IntPtr handle, bool ownsHandle) : base(IntPtr.Zero, true)
+ {
+ SetHandle(handle);
+ OwnReference(ownsHandle);
+ }
+
+ 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.");
+ }
+ }
+
+ internal void Cache(Object2 obj)
+ {
+ InstanceCache2.Add(handle, obj);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ RemoveMemoryPressure();
+ InstanceCache2.Remove(handle);
+ return true;
+ }
+
+ protected internal virtual void AddMemoryPressure() { }
+ protected virtual void RemoveMemoryPressure() { }
+ }
+}
+
+
+
diff --git a/src/Libs/GdkPixbuf-2.0/Public/Pixbuf2.cs b/src/Libs/GdkPixbuf-2.0/Public/Pixbuf2.cs
new file mode 100644
index 000000000..77f2bc288
--- /dev/null
+++ b/src/Libs/GdkPixbuf-2.0/Public/Pixbuf2.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using GdkPixbuf.Internal;
+using GObject;
+using GObject.Internal;
+using Type = GObject.Type;
+using TypePlugin = GObject.TypePlugin;
+
+namespace GdkPixbuf
+{
+ public interface TestInterface
+ {
+ void Bla();
+ }
+
+ public class TestInterfaceHelper : Object2, TestInterface, InterfaceFactory, GTypeProvider
+ {
+ public TestInterfaceHelper(Object2Handle handle) : base(handle)
+ {
+ }
+
+ public void Bla()
+ {
+ }
+
+ static Object2 InterfaceFactory.Create(IntPtr handle, bool ownsHandle)
+ {
+ return new TestInterfaceHelper(new Object2Handle(handle, ownsHandle));
+ }
+
+ static Type GTypeProvider.GetGType()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class MyPixbuf : Pixbuf2, ClassFactory, GTypeProvider
+ {
+ private static readonly Type GType;
+ static MyPixbuf()
+ {
+ GType = SubclassRegistrar2.Register();
+ }
+
+ public MyPixbuf() : base(Pixbuf2Handle.For(true, []))
+ {
+ }
+
+ private MyPixbuf(IntPtr handle, bool ownsHandle) : base(new Pixbuf2Handle(handle, ownsHandle)) { }
+
+ static Type GTypeProvider.GetGType() => GType;
+
+ static Object2 ClassFactory.Create(IntPtr handle, bool ownsHandle)
+ {
+ return new MyPixbuf(handle, ownsHandle);
+ }
+ }
+ public class Pixbuf2 : GObject.Object2, ClassFactory, GTypeProvider
+ {
+ protected internal Pixbuf2(Pixbuf2Handle handle) : base(handle) { }
+
+ public static Pixbuf2 New(Colorspace colorspace, bool hasAlpha, int bitsPerSample, int width, int height)
+ {
+ var handle = Internal.Pixbuf.New(colorspace, hasAlpha, bitsPerSample, width, height);
+ return (Pixbuf2) Create(handle, true);
+ }
+
+ private static Object2 Create(IntPtr handle, bool ownsHandle)
+ {
+ var safeHandle = new Pixbuf2Handle(handle, ownsHandle);
+ return new Pixbuf2(safeHandle);
+ }
+
+ static Type GTypeProvider.GetGType()
+ {
+ var resultGetGType = GdkPixbuf.Internal.Pixbuf.GetGType();
+ return resultGetGType;
+ }
+
+ static Object2 ClassFactory.Create(IntPtr handle, bool ownsHandle)
+ {
+ return Create(handle, ownsHandle);
+ }
+
+
+ [Version("2.12")]
+ public Pixbuf2? ApplyEmbeddedOrientation()
+ {
+ var resultApplyEmbeddedOrientation = GdkPixbuf.Internal.Pixbuf.ApplyEmbeddedOrientation(GetHandle());
+ //return InterfaceWrapper.WrapNullableHandle(resultApplyEmbeddedOrientation, true);
+ return (Pixbuf2?) InstanceWrapper.WrapNullableHandle(resultApplyEmbeddedOrientation, true);
+ }
+ }
+}
+
+namespace GdkPixbuf.Internal
+{
+ internal static class TypeRegistration2
+ {
+ public static void RegisterTypes()
+ {
+ Register(OSPlatform.Linux, OSPlatform.OSX, OSPlatform.Windows);
+ }
+
+ private static void Register(params OSPlatform[] supportedPlatforms) where T : ClassFactory, GTypeProvider
+ {
+ try
+ {
+ if(supportedPlatforms.Any(RuntimeInformation.IsOSPlatform))
+ GObject.Internal.InstanceFactory.Register(new Type(T.GetGType()), T.Create);
+ }
+ catch(System.Exception e)
+ {
+ Debug.WriteLine($"Could not register type: {e.Message}");
+ }
+ }
+ }
+
+ public class Pixbuf2Handle : GObject.Internal.Object2Handle
+ {
+ private long _size;
+
+ public Pixbuf2Handle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
+ {
+ }
+
+ public static Pixbuf2Handle For(bool owned, ConstructArgument[] constructArguments) where T : Pixbuf2, 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: ValueArray2OwnedHandle.Create(constructArguments.Select(x => x.Value).ToArray())
+ );
+
+ return new Pixbuf2Handle(ptr, owned);
+ }
+
+ protected override void AddMemoryPressure()
+ {
+ _size = (long) Internal.Pixbuf.GetByteLength(handle);
+ GC.AddMemoryPressure(_size);
+ }
+
+ protected override void RemoveMemoryPressure()
+ {
+ GC.RemoveMemoryPressure(_size);
+ }
+ }
+}
+
+
+
diff --git a/src/Properties/GirCore.Libraries.props b/src/Properties/GirCore.Libraries.props
index 3daf1db32..c5c55490d 100644
--- a/src/Properties/GirCore.Libraries.props
+++ b/src/Properties/GirCore.Libraries.props
@@ -1,7 +1,7 @@
- net6.0;net7.0;net8.0
+ net8.0
enable
true
true
diff --git a/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/Program.cs b/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/Program.cs
index 4343a3c5c..f4d33831e 100644
--- a/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/Program.cs
+++ b/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/Program.cs
@@ -2,6 +2,10 @@
using System.IO;
using System.Threading.Tasks;
using GdkPixbuf;
+using GdkPixbuf.Internal;
+using GObject.Internal;
+using Pixbuf = GdkPixbuf.Pixbuf;
+using PixbufLoader = GdkPixbuf.PixbufLoader;
namespace TestMemoryLeaks;
@@ -17,6 +21,12 @@ public static void Main(string[] args)
{
GdkPixbuf.Module.Initialize();
+ //TypeRegistration2.RegisterTypes();
+ var pb = Pixbuf2.New(Colorspace.Rgb, false, 8, 10, 10);
+ var bla = new MyPixbuf();
+
+ var ii = InstanceWrapper.WrapHandle(bla.GetHandle(), false);
+
var cycles = 10000;
var fileName = "test.bmp";
diff --git a/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/TestMemoryLeaks.csproj b/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/TestMemoryLeaks.csproj
index b6d5df728..4246e8406 100644
--- a/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/TestMemoryLeaks.csproj
+++ b/src/Samples/GdkPixbuf-2.0/TestMemoryLeaks/TestMemoryLeaks.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
enable