// 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."); } }