Skip to content

CppObject finalized before native method is called #95

@MillerJames

Description

@MillerJames

When Configuration.EnableReleaseOnFinalize is set to true, SharpDX.ComObject's finalizer releases its reference to the native object (and I imagine other SharpGen users might want to do something similar). Currently, it's possible for the finalizer to run and decrement the ref count to zero before the native method runs and acquires its own ref:

// Some managed method in my C# code
public void FillGeo(MyGeometryType geometry, D2D.Brush d2dbrush)
{
    D2D.Geometry d2dgeometry = geometry.CreateD2DGeometry();
    this.d2drendertarget.FillGeometry(d2dgeometry, d2dbrush);
}
// generated FillGeometry interop method in SharpDX (comments mine)
public unsafe void DrawGeometry(SharpDX.Direct2D1.Geometry geometry, SharpDX.Direct2D1.Brush brush,
    System.Single strokeWidth, SharpDX.Direct2D1.StrokeStyle strokeStyle)
{
    System.IntPtr geometry_ = System.IntPtr.Zero;
    System.IntPtr brush_ = System.IntPtr.Zero;
    System.IntPtr strokeStyle_ = System.IntPtr.Zero;
    geometry_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.Geometry>(geometry); 

    // At this point, “geometry” is eligible for finalization, its last use was in the above line. 
    // If the finalizer runs now, it will decrement the native Geometry object's ref count to zero
    brush_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.Brush>(brush);
    strokeStyle_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.StrokeStyle>(strokeStyle);

    // The native method called here will try to dereference "geometry_" but the native object it 
    // points at might have been destroyed (if the managed object's finalizer already ran)
    SharpDX.Direct2D1.LocalInterop.CalliStdCallvoid(this._nativePointer, (void *)geometry_, 
        (void *)brush_, strokeWidth, (void *)strokeStyle_, (*(void ***)this._nativePointer)[22]);
}

This case can be trivially resolved with a using block in my code, but it seems like the point of Configuration.EnableReleaseOnFinalizer is that explicitly disposing is not required. I'm working around this issue with using blocks and GC.KeepAlive for the cases I've found in my codebase, but resolving this more generally would be great.

Could SharpGen generate GC.KeepAlive calls to delay finalization for objects whose native pointer is passed into a native method call? i.e. the generated method would look something like:

public unsafe void DrawGeometry(SharpDX.Direct2D1.Geometry geometry, SharpDX.Direct2D1.Brush brush, System.Single strokeWidth, SharpDX.Direct2D1.StrokeStyle strokeStyle)
{
    System.IntPtr geometry_ = System.IntPtr.Zero;
    System.IntPtr brush_ = System.IntPtr.Zero;
    System.IntPtr strokeStyle_ = System.IntPtr.Zero;
    geometry_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.Geometry>(geometry);
    brush_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.Brush>(brush);
    strokeStyle_ = SharpDX.CppObject.ToCallbackPtr<SharpDX.Direct2D1.StrokeStyle>(strokeStyle);
    SharpDX.Direct2D1.LocalInterop.CalliStdCallvoid(this._nativePointer, (void *)geometry_, 
        (void *)brush_, strokeWidth, (void *)strokeStyle_, (*(void ***)this._nativePointer)[22]);

    GC.KeepAlive(this);
    GC.KeepAlive(geometry);
    GC.KeepAlive(brush);
    GC.KeepAlive(strokeStyle);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions