// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SharpGen.Runtime
{
///
/// The ShadowContainer is the main container used to keep references to all native COM/C++ callbacks.
/// It is stored in the property .
///
public sealed class ShadowContainer : DisposeBase
{
private static readonly Dictionary> TypeToShadowTypes = new();
private readonly IDictionary guidToShadow;
private readonly IntPtr guidPtr;
private readonly IntPtr[] guids;
#if DEBUG
private readonly string callbackTypeName;
#endif
public IntPtr[] Guids => guids;
public ShadowContainer(ICallbackable callbackable)
{
#if DEBUG
callbackTypeName = callbackable.GetType().Name;
#endif
guidToShadow = new Dictionary();
var guidList = BuildGuidList(callbackable, guidToShadow);
AllocateGuidsTable(guidList, out guids, out guidPtr);
#if DEBUG
TraceAllocation(RuntimeHelpers.GetHashCode(callbackable));
#endif
}
public ShadowContainer(IReadOnlyList guidList, IDictionary guidToShadow)
{
#if DEBUG
callbackTypeName = "UNSPECIFIED";
#endif
this.guidToShadow = guidToShadow;
AllocateGuidsTable(guidList, out guids, out guidPtr);
#if DEBUG
TraceAllocation(0);
#endif
}
#if DEBUG
private void TraceAllocation(int parentId)
{
if (!Configuration.EnableObjectLifetimeTracing)
return;
Debug.WriteLine($"{GetType().Name}<{callbackTypeName}>[{guidPtr.ToInt64():X}]::new({parentId:X})");
}
#endif
private static unsafe void AllocateGuidsTable(IReadOnlyList guidList,
out IntPtr[] guids, out IntPtr guidsPtr)
{
var guidCount = guidList.Count;
guids = new IntPtr[guidCount];
guidsPtr = Marshal.AllocHGlobal(Unsafe.SizeOf() * guidCount);
var pGuid = (Guid*) guidsPtr;
for (var i = 0; i < guidCount; i++)
{
pGuid[i] = guidList[i];
// Store the pointer
guids[i] = new IntPtr(pGuid + i);
}
}
public static IReadOnlyList BuildGuidList(ICallbackable callbackable, IDictionary guidToShadow)
{
List guidList = new();
// Associate all shadows with their interfaces.
foreach (var item in GetUninheritedShadowedInterfaces(callbackable.GetType()))
{
var shadowAttribute = ShadowAttribute.Get(item);
// Initialize the shadow with the callback
var shadow = (CppObjectShadow) Activator.CreateInstance(shadowAttribute.Type);
shadow.Initialize(callbackable);
guidToShadow.Add(GuidFromType(item), shadow);
if (ExcludeFromTypeListAttribute.Has(item))
guidList.Add(GuidFromType(item));
// Associate also inherited interface to this shadow
var inheritList = item.GetTypeInfo().ImplementedInterfaces;
foreach (var inheritInterface in inheritList)
{
// If there isn't a Shadow attribute then this isn't a native interface.
if (!ShadowAttribute.Has(inheritInterface))
continue;
var guid = GuidFromType(inheritInterface);
// If we have the same GUID as an already added interface,
// then there's already an accurate shadow for it, so we have nothing to do.
if (guidToShadow.ContainsKey(guid))
continue;
// Use same shadow as derived
guidToShadow.Add(guid, shadow);
if (ExcludeFromTypeListAttribute.Has(inheritInterface))
guidList.Add(guid);
}
}
return guidList;
}
///
/// Gets a list of interfaces implemented by that aren't inherited by any other shadowed interfaces.
///
/// The type for which to get the list.
/// The interface list.
private static List GetUninheritedShadowedInterfaces(Type type)
{
// Cache reflection on interface inheritance
lock (TypeToShadowTypes)
{
if (TypeToShadowTypes.TryGetValue(type, out var cachedInterfaces))
return cachedInterfaces;
var interfaces = type.GetTypeInfo().ImplementedInterfaces.ToList();
TypeToShadowTypes.Add(type, interfaces);
List interfacesToRemove = new();
// First pass to identify most detailed interfaces
foreach (var item in interfaces)
{
// Only process interfaces that are using shadow
if (!ShadowAttribute.Has(item))
{
interfacesToRemove.Add(item);
continue;
}
// Keep only final interfaces and not intermediate.
interfacesToRemove.AddRange(item.GetTypeInfo().ImplementedInterfaces);
}
foreach (var toRemove in interfacesToRemove)
interfaces.Remove(toRemove);
return interfaces;
}
}
public IntPtr Find(Type type) => Find(GuidFromType(type));
internal static Guid GuidFromType(Type type) => type.GetTypeInfo().GUID;
public IntPtr Find(Guid guidType)
{
var shadow = FindShadow(guidType);
return shadow?.NativePointer ?? IntPtr.Zero;
}
public CppObjectShadow FindShadow(Guid guidType)
{
guidToShadow.TryGetValue(guidType, out var shadow);
return shadow;
}
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
Debug.Assert(guidPtr != IntPtr.Zero);
#if DEBUG
if (Configuration.EnableObjectLifetimeTracing)
Debug.WriteLine(
$"{GetType().Name}<{callbackTypeName}>[{guidPtr.ToInt64():X}]::{nameof(Dispose)}"
);
#endif
foreach (var comObjectCallbackNative in guidToShadow.Values)
comObjectCallbackNative.Dispose();
guidToShadow.Clear();
Marshal.FreeHGlobal(guidPtr);
}
}
}