SharpGen.Runtime/CppObject.cs (138 lines of code) (raw):
// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using SharpGen.Runtime.Diagnostics;
namespace SharpGen.Runtime
{
/// <summary>
/// Root class for all Cpp interop object.
/// </summary>
public class CppObject : DisposeBase, ICallbackable
{
/// <summary>
/// Logs a warning of a possible memory leak when <see cref="Configuration.EnableObjectTracking" /> is enabled.
/// Default uses <see cref="System.Diagnostics.Debug"/>.
/// </summary>
public static Action<string> LogMemoryLeakWarning = DefaultMemoryLeakWarningLogger;
private static void DefaultMemoryLeakWarningLogger(string warning)
{
Debug.WriteLine(warning);
}
/// <summary>
/// The native pointer
/// </summary>
private IntPtr _nativePointer;
#if !NETSTANDARD1_1
private static readonly ConditionalWeakTable<CppObject, object> TagTable = new();
#else
private object tag;
#endif
/// <summary>
/// Gets or sets a custom user tag object to associate with this instance.
/// </summary>
/// <value>The tag object.</value>
public object Tag
{
#if !NETSTANDARD1_1
get => TagTable.TryGetValue(this, out var tag) ? tag : null;
set => TagTable.Add(this, value);
#else
get => tag;
set => tag = value;
#endif
}
/// <summary>
/// Default constructor.
/// </summary>
/// <param name = "pointer">Pointer to Cpp Object</param>
public CppObject(IntPtr pointer)
{
NativePointer = pointer;
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine($"{GetType().Name}[{NativePointer.ToInt64():X}]::new()");
#endif
}
/// <summary>
/// Initializes a new instance of the <see cref="CppObject"/> class.
/// </summary>
protected CppObject()
{
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine($"{GetType().Name}[0]::new()");
#endif
}
/// <summary>
/// Get a pointer to the underlying Cpp Object
/// </summary>
public IntPtr NativePointer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if !NETSTANDARD1_1
get => _nativePointer;
#else
get => Volatile.Read(ref _nativePointer);
#endif
set
{
var oldNativePointer = Interlocked.Exchange(ref _nativePointer, value);
if (oldNativePointer != value)
NativePointerUpdated(oldNativePointer);
}
}
public static explicit operator IntPtr(CppObject cppObject) => cppObject?.NativePointer ?? IntPtr.Zero;
/// <summary>
/// Method called when the <see cref="NativePointer"/> is updated.
/// </summary>
protected virtual void NativePointerUpdated(IntPtr oldNativePointer)
{
if (!Configuration.EnableObjectTracking)
return;
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine(
$"{GetType().Name}[{oldNativePointer.ToInt64():X}]::{nameof(NativePointerUpdated)} ({NativePointer.ToInt64():X})"
);
#endif
ObjectTracker.MigrateNativePointer(this, oldNativePointer, NativePointer);
}
protected unsafe void* this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (*(void***) _nativePointer)[index];
}
protected unsafe void* this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (*(void***) _nativePointer)[index];
}
protected unsafe void* this[nint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (*(void***) _nativePointer)[index];
}
protected unsafe void* this[nuint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (*(void***) _nativePointer)[index];
}
protected sealed override void Dispose(bool disposing)
{
var nativePointer = NativePointer;
if (nativePointer == IntPtr.Zero)
return;
var isObjectTrackingEnabled = Configuration.EnableObjectTracking;
// If object is disposed by the finalizer, emits a warning
if (!disposing && isObjectTrackingEnabled && Configuration.EnableTrackingReleaseOnFinalizer && !Configuration.EnableReleaseOnFinalizer)
{
var objectReference = ObjectTracker.Find(this, nativePointer);
LogMemoryLeakWarning?.Invoke(
$"Warning: Live CppObject released on finalizer [0x{nativePointer.ToInt64():X}], potential memory leak: {objectReference}"
);
}
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine(
$"{GetType().Name}[{nativePointer.ToInt64():X}]::{nameof(Dispose)}"
);
#endif
DisposeCore(nativePointer, disposing);
if (isObjectTrackingEnabled)
ObjectTracker.Untrack(this, nativePointer);
// Set pointer to null (using protected members in order to avoid callbacks).
Interlocked.Exchange(ref _nativePointer, IntPtr.Zero);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="nativePointer"><see cref="NativePointer"/></param>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void DisposeCore(IntPtr nativePointer, bool disposing)
{
}
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.FromPointer) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static T FromPointer<T>(IntPtr cppObjectPtr) where T : CppObject =>
MarshallingHelpers.FromPointer<T>(cppObjectPtr);
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.ToCallbackPtr) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IntPtr ToCallbackPtr<TCallback>(ICallbackable callback) where TCallback : ICallbackable =>
MarshallingHelpers.ToCallbackPtr<TCallback>(callback);
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.ToCallbackPtr) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IntPtr ToCallbackPtr<TCallback>(CppObject obj) where TCallback : ICallbackable =>
MarshallingHelpers.ToCallbackPtr<TCallback>(obj);
/// <summary>
/// Implements <see cref="ICallbackable"/> but it cannot not be set.
/// This is only used to support for interop with unmanaged callback.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ShadowContainer ICallbackable.Shadow => throw new InvalidOperationException("Invalid access to Callback. This is used internally.");
}
}