Browse Source

Value types may generate `Dispose` (#1787)

pull/1792/head
Stefan 2 years ago committed by GitHub
parent
commit
b14038ab07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 110
      src/Generator/Generators/CSharp/CSharpSources.cs
  2. 3
      tests/dotnet/CSharp/CSharp.Tests.cs

110
src/Generator/Generators/CSharp/CSharpSources.cs

@ -781,7 +781,7 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
} }
} }
if (@class.IsGenerated && isBindingGen && @class.IsRefType && !@class.IsOpaque) if (@class.IsGenerated && isBindingGen && NeedsDispose(@class) && !@class.IsOpaque)
{ {
bases.Add("IDisposable"); bases.Add("IDisposable");
} }
@ -790,6 +790,12 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
Write(" : {0}", string.Join(", ", bases)); Write(" : {0}", string.Join(", ", bases));
} }
private bool NeedsDispose(Class @class)
{
return @class.IsRefType || @class.IsValueType &&
(@class.GetConstCharFieldProperties().Any() || @class.HasNonTrivialDestructor);
}
private bool CanUseSequentialLayout(Class @class) private bool CanUseSequentialLayout(Class @class)
{ {
if (@class.IsUnion || @class.HasUnionFields) if (@class.IsUnion || @class.HasUnionFields)
@ -2223,25 +2229,24 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
GenerateMethod(ctor, @class); GenerateMethod(ctor, @class);
} }
if (@class.IsRefType) // We used to always call GenerateClassFinalizer here. However, the
{ // finalizer calls Dispose which is conditionally implemented below.
// We used to always call GenerateClassFinalizer here. However, the // Instead, generate the finalizer only if Dispose is also implemented.
// finalizer calls Dispose which is conditionally implemented below.
// Instead, generate the finalizer only if Dispose is also implemented.
// ensure any virtual dtor in the chain is called // ensure any virtual dtor in the chain is called
var dtor = @class.Destructors.FirstOrDefault(d => d.Access != AccessSpecifier.Private); var dtor = @class.Destructors.FirstOrDefault(d => d.Access != AccessSpecifier.Private);
if (ShouldGenerateClassNativeField(@class) || if (ShouldGenerateClassNativeField(@class) ||
(dtor != null && (dtor.IsVirtual || @class.HasNonTrivialDestructor)) || (dtor != null && (dtor.IsVirtual || @class.HasNonTrivialDestructor)) ||
// virtual destructors in abstract classes may lack a pointer in the v-table // virtual destructors in abstract classes may lack a pointer in the v-table
// so they have to be called by symbol; thus we need an explicit Dispose override // so they have to be called by symbol; thus we need an explicit Dispose override
@class.IsAbstract) @class.IsAbstract)
if (!@class.IsOpaque) if (!@class.IsOpaque)
{ {
if (@class.IsRefType)
GenerateClassFinalizer(@class); GenerateClassFinalizer(@class);
if (NeedsDispose(@class))
GenerateDisposeMethods(@class); GenerateDisposeMethods(@class);
} }
}
} }
private void GenerateClassFinalizer(Class @class) private void GenerateClassFinalizer(Class @class)
@ -2250,19 +2255,23 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
return; return;
using (PushWriteBlock(BlockKind.Finalizer, $"~{@class.Name}()", NewLineKind.BeforeNextBlock)) using (PushWriteBlock(BlockKind.Finalizer, $"~{@class.Name}()", NewLineKind.BeforeNextBlock))
WriteLine($"Dispose(false, callNativeDtor : {Helpers.OwnsNativeInstanceIdentifier} );"); WriteLine($"Dispose(false, callNativeDtor: {Helpers.OwnsNativeInstanceIdentifier});");
} }
private void GenerateDisposeMethods(Class @class) private void GenerateDisposeMethods(Class @class)
{ {
var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType; var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType;
var hasDtorParam = @class.IsRefType;
// Generate the IDispose Dispose() method. // Generate the IDispose Dispose() method.
if (!hasBaseClass) if (!hasBaseClass)
{ {
using (PushWriteBlock(BlockKind.Method, "public void Dispose()", NewLineKind.BeforeNextBlock)) using (PushWriteBlock(BlockKind.Method, "public void Dispose()", NewLineKind.BeforeNextBlock))
{ {
WriteLine($"Dispose(disposing: true, callNativeDtor : {Helpers.OwnsNativeInstanceIdentifier} );"); WriteLine(hasDtorParam
? $"Dispose(disposing: true, callNativeDtor: {Helpers.OwnsNativeInstanceIdentifier});"
: "Dispose(disposing: true);");
if (Options.GenerateFinalizerFor(@class)) if (Options.GenerateFinalizerFor(@class))
WriteLine("GC.SuppressFinalize(this);"); WriteLine("GC.SuppressFinalize(this);");
} }
@ -2276,7 +2285,10 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
// Generate Dispose(bool, bool) method // Generate Dispose(bool, bool) method
var ext = !@class.IsValueType ? (hasBaseClass ? "override " : "virtual ") : string.Empty; var ext = !@class.IsValueType ? (hasBaseClass ? "override " : "virtual ") : string.Empty;
using var _ = PushWriteBlock(BlockKind.Method, $"internal protected {ext}void Dispose(bool disposing, bool callNativeDtor )", NewLineKind.BeforeNextBlock); var protectionLevel = @class.IsValueType ? "private" : "internal protected";
using var _ = PushWriteBlock(BlockKind.Method,
$"{protectionLevel} {ext}void Dispose(bool disposing{(hasDtorParam ? ", bool callNativeDtor" : "")})",
NewLineKind.BeforeNextBlock);
if (@class.IsRefType) if (@class.IsRefType)
{ {
@ -2323,7 +2335,7 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
// instance from the NativeToManagedMap. Of course, this is somewhat half-hearted // instance from the NativeToManagedMap. Of course, this is somewhat half-hearted
// since we can't/don't do this when there's no virtual dtor available to detour. // since we can't/don't do this when there's no virtual dtor available to detour.
// Anyway, we must be able to call the native dtor in this case even if we don't // Anyway, we must be able to call the native dtor in this case even if we don't
/// own the underlying native instance. // own the underlying native instance.
// //
// 2. When we we pass a disposable object to a function "by value" then the callee // 2. When we we pass a disposable object to a function "by value" then the callee
// calls the dtor on the argument so our marshalling code must have a way from preventing // calls the dtor on the argument so our marshalling code must have a way from preventing
@ -2335,21 +2347,29 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
// } // }
// //
// IDisposable.Dispose() and Object.Finalize() set callNativeDtor = Helpers.OwnsNativeInstanceIdentifier // IDisposable.Dispose() and Object.Finalize() set callNativeDtor = Helpers.OwnsNativeInstanceIdentifier
WriteLine("if (callNativeDtor)"); if (hasDtorParam)
if (@class.IsDependent || dtor.IsVirtual) {
WriteOpenBraceAndIndent(); WriteLine("if (callNativeDtor)");
else if (@class.IsDependent || dtor.IsVirtual)
Indent(); WriteOpenBraceAndIndent();
else
Indent();
}
if (dtor.IsVirtual) if (dtor.IsVirtual)
this.GenerateMember(@class, c => GenerateDestructorCall( this.GenerateMember(@class, c => GenerateDestructorCall(
c is ClassTemplateSpecialization ? c is ClassTemplateSpecialization ?
c.Methods.First(m => m.InstantiatedFrom == dtor) : dtor)); c.Methods.First(m => m.InstantiatedFrom == dtor) : dtor));
else else
this.GenerateMember(@class, c => GenerateMethodBody(c, dtor)); this.GenerateMember(@class, c => GenerateMethodBody(c, dtor));
if (@class.IsDependent || dtor.IsVirtual)
UnindentAndWriteCloseBrace(); if (hasDtorParam)
else {
Unindent(); if (@class.IsDependent || dtor.IsVirtual)
UnindentAndWriteCloseBrace();
else
Unindent();
}
} }
} }
@ -2357,19 +2377,37 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
// referenced memory. Don't rely on testing if the field's IntPtr is IntPtr.Zero since // referenced memory. Don't rely on testing if the field's IntPtr is IntPtr.Zero since
// unmanaged memory isn't always initialized and/or a reference may be owned by the // unmanaged memory isn't always initialized and/or a reference may be owned by the
// native side. // native side.
//
string ptr;
if (@class.IsValueType)
{
ptr = $"{Helpers.InstanceIdentifier}Ptr";
WriteLine($"fixed ({Helpers.InternalStruct}* {ptr} = &{Helpers.InstanceIdentifier})");
WriteOpenBraceAndIndent();
}
else
{
ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})";
}
foreach (var prop in @class.GetConstCharFieldProperties()) foreach (var prop in @class.GetConstCharFieldProperties())
{ {
string name = prop.Field.OriginalName; string name = prop.Field.OriginalName;
var ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})->{name}";
WriteLine($"if (__{name}_OwnsNativeMemory)"); WriteLine($"if (__{name}_OwnsNativeMemory)");
WriteLineIndent($"Marshal.FreeHGlobal({ptr});"); WriteLineIndent($"Marshal.FreeHGlobal({ptr}->{name});");
} }
WriteLine("if ({0})", Helpers.OwnsNativeInstanceIdentifier); if (@class.IsValueType)
WriteLineIndent("Marshal.FreeHGlobal({0});", Helpers.InstanceIdentifier); {
UnindentAndWriteCloseBrace();
}
else
{
WriteLine("if ({0})", Helpers.OwnsNativeInstanceIdentifier);
WriteLineIndent("Marshal.FreeHGlobal({0});", Helpers.InstanceIdentifier);
WriteLine("{0} = IntPtr.Zero;", Helpers.InstanceIdentifier); WriteLine("{0} = IntPtr.Zero;", Helpers.InstanceIdentifier);
}
} }
private bool GenerateDestructorCall(Method dtor) private bool GenerateDestructorCall(Method dtor)

3
tests/dotnet/CSharp/CSharp.Tests.cs

@ -2034,6 +2034,7 @@ public unsafe class CSharpTests
test.CharPtrMember = "test2"; test.CharPtrMember = "test2";
Assert.AreEqual("test", test.StringMember); Assert.AreEqual("test", test.StringMember);
Assert.AreEqual("test2", test.CharPtrMember); Assert.AreEqual("test2", test.CharPtrMember);
test.Dispose();
} }
[Test] [Test]
@ -2047,6 +2048,7 @@ public unsafe class CSharpTests
test.CharPtrMember = "test2"; test.CharPtrMember = "test2";
Assert.AreEqual("test", test.StringMember); Assert.AreEqual("test", test.StringMember);
Assert.AreEqual("test2", test.CharPtrMember); Assert.AreEqual("test2", test.CharPtrMember);
test.Dispose();
} }
[Test] [Test]
@ -2059,5 +2061,6 @@ public unsafe class CSharpTests
test.CharPtrMember = "test2"; test.CharPtrMember = "test2";
Assert.AreEqual("test", test.StringMember); Assert.AreEqual("test", test.StringMember);
Assert.AreEqual("test2", test.CharPtrMember); Assert.AreEqual("test2", test.CharPtrMember);
test.Dispose();
} }
} }

Loading…
Cancel
Save