Skip to content

Commit

Permalink
Support class based safe handles
Browse files Browse the repository at this point in the history
  • Loading branch information
badcel committed Dec 10, 2024
1 parent ead37a8 commit f003d0c
Show file tree
Hide file tree
Showing 79 changed files with 741 additions and 959 deletions.
3 changes: 1 addition & 2 deletions .github/actions/create/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ runs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
9.0.x
- name: Compile native library
run: dotnet fsi GenerateGirTestLib.fsx
Expand Down
1 change: 1 addition & 0 deletions src/Generation/Generator/Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static void Generate(IEnumerable<GirModel.Class> 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),
Expand Down
32 changes: 32 additions & 0 deletions src/Generation/Generator/Generator/Internal/ClassHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Generator.Model;

namespace Generator.Generator.Internal;

internal class ClassHandle : Generator<GirModel.Class>
{
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion src/Generation/Generator/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public static void Generate(IEnumerable<GirModel.Interface> interfaces, string p
var generators = new List<Generator<GirModel.Interface>>()
{
new Generator.Internal.InterfaceMethods(publisher),
new Generator.Public.InterfaceFramework(publisher),
new Generator.Public.InterfaceMethods(publisher),
new Generator.Public.InterfaceProperties(publisher),

Expand Down
21 changes: 21 additions & 0 deletions src/Generation/Generator/Model/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace {Namespace.GetInternalName(callback.Namespace)};
// AUTOGENERATED FILE - DO NOT MODIFY
// <summary>
/// <summary>
/// Call Handler for {callback.Name}. A call annotation indicates the closure should
/// be valid for the duration of the call. This handler does not implement any special
/// memory management.
Expand Down
66 changes: 66 additions & 0 deletions src/Generation/Generator/Renderer/Internal/Class/ClassHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
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}} : {{RenderParent(cls)}}
{
public {{handleName}}(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle){ }
public static {{RenderNewKeyword(cls)}}{{handleName}} For<T>(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);
}
}
""";
}

private static string RenderParent(GirModel.Class cls)
{
return cls.Parent is null
? throw new Exception("Class is missing parent")
: Class.GetFullyQualifiedInternalHandleName(cls.Parent);
}

private static string RenderNewKeyword(GirModel.Class cls)
{
//A class handle is not generated for GObject.Object. This means if
//cls.Parent.Parent is null that this class inherits GObject.Object.
//If it is not null this handle is a subclass handle.

return cls.Parent?.Parent is null
? string.Empty
: "new ";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)};
Expand All @@ -40,24 +42,24 @@ internal static void RegisterTypes()
.Join(Environment.NewLine)}
}}
private static void Register<T>(Func<nuint> getType, params OSPlatform[] supportedPlatforms) where T : class
private static void Register<T>(params OSPlatform[] supportedPlatforms) where T : InstanceFactory, GTypeProvider
{{
try
{{
try
{{
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);
}}
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)
Expand All @@ -76,6 +78,6 @@ private static string RenderPlatforms(GirModel.PlatformDependent? platformDepend
if (platformDependent.SupportsWindows)
statements.Add("OSPlatform.Windows");

return ", " + string.Join(", ", statements);
return string.Join(", ", statements);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ private static void Default(ParameterToManagedData parameterData)
var parameterName = Model.Parameter.GetName(parameterData.Parameter);
var callName = Model.Parameter.GetConvertedName(parameterData.Parameter);

var type = Model.ComplexType.GetFullyQualified(cls);

var wrapHandle = parameterData.Parameter.Nullable
? "GObject.Internal.ObjectWrapper.WrapNullableHandle"
: "GObject.Internal.ObjectWrapper.WrapHandle";

? $"({type}?) GObject.Internal.InstanceWrapper.WrapNullableHandle"
: $"({type}) GObject.Internal.InstanceWrapper.WrapHandle";
parameterData.SetSignatureName(() => parameterName);
parameterData.SetExpression(() => $"var {callName} = {wrapHandle}<{Model.ComplexType.GetFullyQualified(cls)}>({parameterName}, {Model.Transfer.IsOwnedRef(parameterData.Parameter.Transfer).ToString().ToLower()});");
parameterData.SetExpression(() => $"var {callName} = {wrapHandle}<{type}>({parameterName}, {Model.Transfer.IsOwnedRef(parameterData.Parameter.Transfer).ToString().ToLower()});");
parameterData.SetCallName(() => callName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void Initialize(ParameterToManagedData parameterData, IEnumerable<Paramet
var callName = Model.Parameter.GetConvertedName(parameterData.Parameter);

parameterData.SetSignatureName(() => signatureName);
parameterData.SetExpression(() => $"var {callName} = GObject.Internal.ObjectWrapper.WrapInterfaceHandle<{Model.Interface.GetFullyQualifiedImplementationName(iface)}>({signatureName}, {Model.Transfer.IsOwnedRef(parameterData.Parameter.Transfer).ToString().ToLower()});");
parameterData.SetExpression(() => $"var {callName} = ({Model.Type.GetPublicNameFullyQuallified(iface)}) GObject.Internal.InstanceWrapper.WrapHandle<{Model.Interface.GetFullyQualifiedImplementationName(iface)}>({signatureName}, {Model.Transfer.IsOwnedRef(parameterData.Parameter.Transfer).ToString().ToLower()});");
parameterData.SetCallName(() => callName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public string GetString(GirModel.ReturnType returnType, string fromVariableName)
throw new NotImplementedException($"{returnType.AnyType}: class return type which is no pointer can not be converted to native");

return returnType.Nullable
? fromVariableName + "?.Handle ?? IntPtr.Zero"
: fromVariableName + ".Handle";
? fromVariableName + "?.Handle.DangerousGetHandle() ?? IntPtr.Zero"
: fromVariableName + ".Handle.DangerousGetHandle()";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public bool Supports(GirModel.AnyType type)
public string GetString(GirModel.ReturnType returnType, string fromVariableName)
{
return returnType.Nullable
? fromVariableName + "?.Handle ?? IntPtr.Zero"
: fromVariableName + ".Handle";
? fromVariableName + "?.Handle.DangerousGetHandle() ?? IntPtr.Zero"
: fromVariableName + ".Handle.DangerousGetHandle()";
}
}
50 changes: 7 additions & 43 deletions src/Generation/Generator/Renderer/Public/Class/ClassFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public static string Render(GirModel.Class cls)

return $@"
using System;
using GObject;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
Expand All @@ -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)}
}}";
}

Expand All @@ -36,7 +36,7 @@ private static string RenderInheritance(GirModel.Class cls)
var interfaces = cls.Implements.Select(ComplexType.GetFullyQualified);

var elements = new List<string>(interfaces);

if (parentClass is not null)
elements.Insert(0, parentClass);

Expand All @@ -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<string>()
{
$@"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<ConstructArgument>()) {{}}");
}
else if (InheritsInitiallyUnowned(cls))
{
constructors.Add($"protected internal {cls.Name}(params ConstructArgument[] constructArguments) : base(constructArguments) {{}}");
constructors.Add($"public {cls.Name}() : this(Array.Empty<ConstructArgument>()) {{}}");
}
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<GObject.ConstructArgument>())) {{ }}"
: $" public {cls.Name}() : this({Class.GetFullyQualifiedInternalHandleName(cls)}.For<{cls.Name}>(true, Array.Empty<GObject.ConstructArgument>())) {{ }}";
}

private static bool IsNamedInitiallyUnowned(string name) => name == "InitiallyUnowned";
}
Loading

0 comments on commit f003d0c

Please sign in to comment.