Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid Reflection #1006

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions src/Libs/GObject-2.0/Internal/BoxedWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,7 @@ 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 });

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;
//TODO: REMOVE BOXED WRAPPER
return InstanceFactory.Create(handle, ownsHandle);
}
}
28 changes: 28 additions & 0 deletions src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;

namespace GObject.Internal;

internal class DemoTypeRegistration
{
internal static void RegisterTypes()
{
Register<GObject.Binding>(Binding.GetGType, OSPlatform.Linux, OSPlatform.OSX, OSPlatform.Windows);

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (Linux)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (Linux)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (MacOS)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (MacOS)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (Windows)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.

Check failure on line 13 in src/Libs/GObject-2.0/Internal/DemoTypeRegistration.cs

View workflow job for this annotation

GitHub Actions / Build (Windows)

The type 'GObject.Binding' cannot be used as type parameter 'T' in the generic type or method 'DemoTypeRegistration.Register<T>(Func<nuint>, params OSPlatform[])'. There is no implicit reference conversion from 'GObject.Binding' to 'GObject.Internal.Constructable'.
}

private static void Register<T>(Func<nuint> getType, params OSPlatform[] supportedPlatforms) where T : Constructable
{
try
{
if (supportedPlatforms.Any(RuntimeInformation.IsOSPlatform))
InstanceFactory.AddFactoryForType(getType(), T.Create);
}
catch (System.Exception e)
{
Debug.WriteLine($"Could not register type '{nameof(T)}': {e.Message}");
}
}
}
69 changes: 69 additions & 0 deletions src/Libs/GObject-2.0/Internal/InstanceFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace GObject.Internal;

public interface Constructable
{
public static abstract object Create(IntPtr handle, bool ownedRef);

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (Linux)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (Linux)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (MacOS)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (MacOS)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (Windows)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.

Check failure on line 10 in src/Libs/GObject-2.0/Internal/InstanceFactory.cs

View workflow job for this annotation

GitHub Actions / Build (Windows)

The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater.
}

/// <summary>
/// Creates new instances of classes and records
/// </summary>
internal class InstanceFactory
{
private static readonly Dictionary<Type, Func<IntPtr, bool, object>> Factories = new();

public static object Create(IntPtr handle, bool ownedRef)
{
var 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"
);

var factory = GetFactory(gtype);

return factory(handle, ownedRef);
}

public static void AddFactoryForType(Type type, Func<IntPtr, bool, object> factory)
{
Factories[type] = factory;
}

private static Func<IntPtr, bool, object> GetFactory(Type gtype)
{
if (Factories.TryGetValue(gtype, out var factory))
return factory;

do
{
var parentType = new Type(Functions.TypeParent(gtype));
if (parentType.Value is (nuint) BasicType.Invalid or (nuint) BasicType.None)
throw new Exception("Could not retrieve parent type - is the typeid valid?");

if (!Factories.TryGetValue(gtype, out var parentFactory))
continue;

//Store parent factory for later use
AddFactoryForType(gtype, parentFactory);

} while(true);
}

private static unsafe Type GetTypeFromInstance(IntPtr handle)
{
var gclass = Unsafe.AsRef<TypeInstanceData>((void*) handle).GClass;
var gtype = Unsafe.AsRef<TypeClassData>((void*) gclass).GType;

if (gtype == 0)
throw new Exception("Could not retrieve type from class struct - is the struct valid?");

return new Type(gtype);
}
}
38 changes: 3 additions & 35 deletions src/Libs/GObject-2.0/Internal/ObjectWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,22 @@ namespace GObject.Internal;

public static class ObjectWrapper
{
public static T? WrapNullableHandle<T>(IntPtr handle, bool ownedRef) where T : class, IHandle
public static T? WrapNullableHandle<T>(IntPtr handle, bool ownedRef) where T : GObject.Object, IHandle
{
return handle == IntPtr.Zero
? null
: WrapHandle<T>(handle, ownedRef);
}

public static T WrapHandle<T>(IntPtr handle, bool ownedRef) where T : class, IHandle
public static T WrapHandle<T>(IntPtr handle, bool ownedRef) where T : GObject.Object, 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 });
return (T) InstanceFactory.Create(handle, ownedRef);
}

public static T? WrapNullableInterfaceHandle<T>(IntPtr handle, bool ownedRef) where T : class, IHandle
Expand Down Expand Up @@ -78,17 +57,6 @@ public static T WrapInterfaceHandle<T>(IntPtr handle, bool ownedRef) where T : c
return (T) ctor.Invoke(new object[] { handle, ownedRef });
}

private static unsafe Type GetTypeFromInstance(IntPtr handle)
{
var gclass = Unsafe.AsRef<TypeInstanceData>((void*) handle).GClass;
var gtype = Unsafe.AsRef<TypeClassData>((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
Expand Down
34 changes: 0 additions & 34 deletions src/Libs/GObject-2.0/Internal/TypeDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public enum BasicType
public static class TypeDictionary
{
private static readonly Dictionary<System.Type, Type> _systemTypeDict = new();
private static readonly Dictionary<Type, System.Type> _reverseTypeDict = new();

/// <summary>
/// Add a new mapping of (System.Type, GObject.Type) to the type dictionary.
Expand All @@ -63,7 +62,6 @@ public static void Add(System.Type systemType, Type type)
);

_systemTypeDict[systemType] = type;
_reverseTypeDict[type] = systemType;
}

/// <summary>
Expand All @@ -87,39 +85,7 @@ internal static Type GetGType(System.Type type)
return _systemTypeDict[type];
}

/// <summary>
/// For a given gtype, retrieve the corresponding managed type.
/// </summary>
/// <param name="gtype">A type from the GType type system</param>
/// <returns>The equivalent managed type</returns>
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);
}
Loading