// 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
{
///
/// Root class for all Cpp interop object.
///
public class CppObject : DisposeBase, ICallbackable
{
///
/// Logs a warning of a possible memory leak when is enabled.
/// Default uses .
///
public static Action LogMemoryLeakWarning = DefaultMemoryLeakWarningLogger;
private static void DefaultMemoryLeakWarningLogger(string warning)
{
Debug.WriteLine(warning);
}
///
/// The native pointer
///
private IntPtr _nativePointer;
#if !NETSTANDARD1_1
private static readonly ConditionalWeakTable TagTable = new();
#else
private object tag;
#endif
///
/// Gets or sets a custom user tag object to associate with this instance.
///
/// The tag object.
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
}
///
/// Default constructor.
///
/// Pointer to Cpp Object
public CppObject(IntPtr pointer)
{
NativePointer = pointer;
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine($"{GetType().Name}[{NativePointer.ToInt64():X}]::new()");
#endif
}
///
/// Initializes a new instance of the class.
///
protected CppObject()
{
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine($"{GetType().Name}[0]::new()");
#endif
}
///
/// Get a pointer to the underlying Cpp Object
///
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;
///
/// Method called when the is updated.
///
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);
}
///
/// Releases unmanaged and - optionally - managed resources
///
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void DisposeCore(IntPtr nativePointer, bool disposing)
{
}
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.FromPointer) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static T FromPointer(IntPtr cppObjectPtr) where T : CppObject =>
MarshallingHelpers.FromPointer(cppObjectPtr);
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.ToCallbackPtr) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IntPtr ToCallbackPtr(ICallbackable callback) where TCallback : ICallbackable =>
MarshallingHelpers.ToCallbackPtr(callback);
[Obsolete("Use " + nameof(MarshallingHelpers) + "." + nameof(MarshallingHelpers.ToCallbackPtr) + " instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IntPtr ToCallbackPtr(CppObject obj) where TCallback : ICallbackable =>
MarshallingHelpers.ToCallbackPtr(obj);
///
/// Implements but it cannot not be set.
/// This is only used to support for interop with unmanaged callback.
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ShadowContainer ICallbackable.Shadow => throw new InvalidOperationException("Invalid access to Callback. This is used internally.");
}
}