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

Support class based safe handles #1118

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
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
26 changes: 25 additions & 1 deletion src/Generation/Generator/Model/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@ 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 string GetFullyQualifiedInternalName(GirModel.Class @class)
=> Namespace.GetInternalName(@class.Namespace) + "." + @class.Name;

public static bool HidesConstructor(GirModel.Class? cls, GirModel.Constructor constructor)
{
if (cls is null)
Expand Down Expand Up @@ -84,4 +96,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
113 changes: 113 additions & 0 deletions src/Generation/Generator/Renderer/Internal/Class/ClassHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using Generator.Model;

namespace Generator.Renderer.Internal;

internal static class ClassHandle
{
public static string Render(GirModel.Class cls)
{
return cls.Final
? RenderFinalClassHandle(cls)
: RenderStandardClassHandle(cls);
}

private static string RenderFinalClassHandle(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 {{handleName}} Create(bool owned, GObject.ConstructArgument[] constructArguments)
{
// 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: {{Class.GetFullyQualifiedInternalName(cls)}}.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 RenderStandardClassHandle(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()";
}
}
Loading
Loading