You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1453 lines
35 KiB

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Epic.OnlineServices
{
internal class AllocationException : Exception
{
public AllocationException(string message)
: base(message)
{
}
}
internal class ExternalAllocationException : AllocationException
{
public ExternalAllocationException(IntPtr address, Type type)
: base(string.Format("Attempting to allocate '{0}' over externally allocated memory at {1}", type, address.ToString("X")))
{
}
}
internal class CachedTypeAllocationException : AllocationException
{
public CachedTypeAllocationException(IntPtr address, Type foundType, Type expectedType)
: base(string.Format("Cached allocation is '{0}' but expected '{1}' at {2}", foundType, expectedType, address.ToString("X")))
{
}
}
internal class CachedArrayAllocationException : AllocationException
{
public CachedArrayAllocationException(IntPtr address, int foundLength, int expectedLength)
: base(string.Format("Cached array allocation has length {0} but expected {1} at {2}", foundLength, expectedLength, address.ToString("X")))
{
}
}
internal class DynamicBindingException : Exception
{
public DynamicBindingException(string bindingName)
: base(string.Format("Failed to hook dynamic binding for '{0}'", bindingName))
{
}
}
public static class Helper
{
internal class Allocation
{
public int Size { get; private set; }
public object CachedData { get; private set; }
public bool? IsCachedArrayElementAllocated { get; private set; }
public Allocation(int size)
{
Size = size;
}
public void SetCachedData(object data, bool? isCachedArrayElementAllocated = null)
{
CachedData = data;
IsCachedArrayElementAllocated = isCachedArrayElementAllocated;
}
}
private class DelegateHolder
{
public Delegate Public { get; private set; }
public Delegate Private { get; private set; }
public Delegate[] StructDelegates { get; private set; }
public ulong? NotificationId { get; set; }
public DelegateHolder(Delegate publicDelegate, Delegate privateDelegate, params Delegate[] structDelegates)
{
Public = publicDelegate;
Private = privateDelegate;
StructDelegates = structDelegates;
}
}
private static Dictionary<IntPtr, Allocation> s_Allocations = new Dictionary<IntPtr, Allocation>();
private static Dictionary<IntPtr, DelegateHolder> s_Callbacks = new Dictionary<IntPtr, DelegateHolder>();
private static Dictionary<string, DelegateHolder> s_StaticCallbacks = new Dictionary<string, DelegateHolder>();
/// <summary>
/// Gets the number of unmanaged allocations currently active within the wrapper. Use this to find leaks related to the usage of wrapper code.
/// </summary>
/// <returns>The number of unmanaged allocations currently active within the wrapper.</returns>
public static int GetAllocationCount()
{
return s_Allocations.Count;
}
// These functions are the front end when changing SDK values into wrapper values.
// They will either fetch or convert; whichever is most appropriate for the source and target types.
#region Marshal Getters
internal static bool TryMarshalGet<T>(T[] source, out uint target)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T target)
where T : Handle, new()
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet<TSource, TTarget>(TSource source, out TTarget target)
where TTarget : ISettable, new()
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet(int source, out bool target)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet(bool source, out int target)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet(long source, out DateTimeOffset? target)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T[] target, int arrayLength, bool isElementAllocated)
{
return TryFetch(source, out target, arrayLength, isElementAllocated);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T[] target, uint arrayLength, bool isElementAllocated)
{
return TryFetch(source, out target, (int)arrayLength, isElementAllocated);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T[] target, int arrayLength)
{
return TryMarshalGet(source, out target, arrayLength, !typeof(T).IsValueType);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T[] target, uint arrayLength)
{
return TryMarshalGet(source, out target, arrayLength, !typeof(T).IsValueType);
}
internal static bool TryMarshalGet<TSource, TTarget>(TSource[] source, out TTarget[] target)
where TSource : struct
where TTarget : class, ISettable, new()
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet<TSource, TTarget>(IntPtr source, out TTarget[] target, int arrayLength)
where TSource : struct
where TTarget : class, ISettable, new()
{
target = GetDefault<TTarget[]>();
TSource[] intermediateSource;
if (TryMarshalGet(source, out intermediateSource, arrayLength))
{
return TryMarshalGet(intermediateSource, out target);
}
return false;
}
internal static bool TryMarshalGet<TSource, TTarget>(IntPtr source, out TTarget[] target, uint arrayLength)
where TSource : struct
where TTarget : class, ISettable, new()
{
int arrayLengthInt = (int)arrayLength;
return TryMarshalGet<TSource, TTarget>(source, out target, arrayLengthInt);
}
internal static bool TryMarshalGet<T>(IntPtr source, out T? target)
where T : struct
{
return TryFetch(source, out target);
}
internal static bool TryMarshalGet(byte[] source, out string target)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalGet(IntPtr source, out object target)
{
target = null;
BoxedData boxedData;
if (TryFetch(source, out boxedData))
{
target = boxedData.Data;
return true;
}
return false;
}
internal static bool TryMarshalGet(IntPtr source, out string target)
{
return TryFetch(source, out target);
}
internal static bool TryMarshalGet<T, TEnum>(T source, out T target, TEnum currentEnum, TEnum comparisonEnum)
{
target = GetDefault<T>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
target = source;
return true;
}
return false;
}
internal static bool TryMarshalGet<TTarget, TEnum>(ISettable source, out TTarget target, TEnum currentEnum, TEnum comparisonEnum)
where TTarget : ISettable, new()
{
target = GetDefault<TTarget>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
return TryConvert(source, out target);
}
return false;
}
internal static bool TryMarshalGet<TEnum>(int source, out bool? target, TEnum currentEnum, TEnum comparisonEnum)
{
target = GetDefault<bool?>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
bool targetConvert;
if (TryConvert(source, out targetConvert))
{
target = targetConvert;
return true;
}
}
return false;
}
internal static bool TryMarshalGet<T, TEnum>(T source, out T? target, TEnum currentEnum, TEnum comparisonEnum)
where T : struct
{
target = GetDefault<T?>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
target = source;
return true;
}
return false;
}
internal static bool TryMarshalGet<T, TEnum>(IntPtr source, out T target, TEnum currentEnum, TEnum comparisonEnum)
where T : Handle, new()
{
target = GetDefault<T>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
return TryMarshalGet(source, out target);
}
return false;
}
internal static bool TryMarshalGet<TEnum>(IntPtr source, out IntPtr? target, TEnum currentEnum, TEnum comparisonEnum)
{
target = GetDefault<IntPtr?>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
return TryMarshalGet(source, out target);
}
return false;
}
internal static bool TryMarshalGet<TEnum>(IntPtr source, out string target, TEnum currentEnum, TEnum comparisonEnum)
{
target = GetDefault<string>();
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
return TryMarshalGet(source, out target);
}
return false;
}
internal static bool TryMarshalGet<TInternal, TPublic>(IntPtr source, out TPublic target)
where TInternal : struct
where TPublic : class, ISettable, new()
{
target = GetDefault<TPublic>();
TInternal? targetInternal;
if (TryMarshalGet(source, out targetInternal))
{
if (targetInternal.HasValue)
{
target = new TPublic();
target.Set(targetInternal);
return true;
}
}
return false;
}
internal static bool TryMarshalGet<TCallbackInfoInternal, TCallbackInfo>(IntPtr callbackInfoAddress, out TCallbackInfo callbackInfo, out IntPtr clientDataAddress)
where TCallbackInfoInternal : struct, ICallbackInfoInternal
where TCallbackInfo : class, ISettable, new()
{
callbackInfo = null;
clientDataAddress = IntPtr.Zero;
TCallbackInfoInternal callbackInfoInternal;
if (TryFetch(callbackInfoAddress, out callbackInfoInternal))
{
callbackInfo = new TCallbackInfo();
callbackInfo.Set(callbackInfoInternal);
clientDataAddress = callbackInfoInternal.ClientDataAddress;
return true;
}
return false;
}
#endregion
// These functions are the front end for changing wrapper values into SDK values.
// They will either allocate or convert; whichever is most appropriate for the source and target types.
#region Marshal Setters
internal static bool TryMarshalSet<T>(ref T target, T source)
{
target = source;
return true;
}
internal static bool TryMarshalSet<TTarget>(ref TTarget target, object source)
where TTarget : ISettable, new()
{
return TryConvert(source, out target);
}
internal static bool TryMarshalSet(ref IntPtr target, Handle source)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T? source)
where T : struct
{
return TryAllocate(ref target, source);
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T[] source, bool isElementAllocated)
{
return TryAllocate(ref target, source, isElementAllocated);
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T[] source)
{
return TryMarshalSet(ref target, source, !typeof(T).IsValueType);
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T[] source, out int arrayLength, bool isElementAllocated)
{
arrayLength = 0;
if (TryMarshalSet(ref target, source, isElementAllocated))
{
arrayLength = source.Length;
return true;
}
return false;
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T[] source, out uint arrayLength, bool isElementAllocated)
{
arrayLength = 0;
int arrayLengthInternal = 0;
if (TryMarshalSet(ref target, source, out arrayLengthInternal, isElementAllocated))
{
arrayLength = (uint)arrayLengthInternal;
return true;
}
return false;
}
internal static bool TryMarshalSet<T>(ref IntPtr target, T[] source, out uint arrayLength)
{
return TryMarshalSet(ref target, source, out arrayLength, !typeof(T).IsValueType);
}
internal static bool TryMarshalSet(ref long target, DateTimeOffset? source)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalSet(ref int target, bool source)
{
return TryConvert(source, out target);
}
internal static bool TryMarshalSet(ref byte[] target, string source, int length)
{
return TryConvert(source, out target, length);
}
internal static bool TryMarshalSet(ref IntPtr target, string source)
{
return TryAllocate(ref target, source);
}
internal static bool TryMarshalSet<T, TEnum>(ref T target, T source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
{
if (source != null)
{
TryMarshalDispose(ref disposable);
if (TryMarshalSet(ref target, source))
{
currentEnum = comparisonEnum;
return true;
}
}
return false;
}
internal static bool TryMarshalSet<TTarget, TEnum>(ref TTarget target, ISettable source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
where TTarget : ISettable, new()
{
if (source != null)
{
TryMarshalDispose(ref disposable);
if (TryConvert(source, out target))
{
currentEnum = comparisonEnum;
return true;
}
}
return false;
}
internal static bool TryMarshalSet<T, TEnum>(ref T target, T? source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
where T : struct
{
if (source != null)
{
TryMarshalDispose(ref disposable);
if (TryMarshalSet(ref target, source.Value))
{
currentEnum = comparisonEnum;
return true;
}
}
return true;
}
internal static bool TryMarshalSet<TEnum>(ref IntPtr target, Handle source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
{
if (source != null)
{
TryMarshalDispose(ref disposable);
if (TryMarshalSet(ref target, source))
{
currentEnum = comparisonEnum;
return true;
}
}
return true;
}
internal static bool TryMarshalSet<TEnum>(ref IntPtr target, string source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
{
if (source != null)
{
TryMarshalDispose(ref target);
target = IntPtr.Zero;
TryMarshalDispose(ref disposable);
if (TryMarshalSet(ref target, source))
{
currentEnum = comparisonEnum;
return true;
}
}
return true;
}
internal static bool TryMarshalSet<TEnum>(ref int target, bool? source, ref TEnum currentEnum, TEnum comparisonEnum, IDisposable disposable = null)
{
if (source != null)
{
TryMarshalDispose(ref disposable);
if (TryMarshalSet(ref target, source.Value))
{
currentEnum = comparisonEnum;
return true;
}
}
return true;
}
internal static bool TryMarshalSet<TInternal, TPublic>(ref IntPtr target, TPublic source)
where TInternal : struct, ISettable
where TPublic : class
{
if (source != null)
{
TInternal targetInternal = new TInternal();
targetInternal.Set(source);
if (TryAllocate(ref target, targetInternal))
{
return true;
}
}
return false;
}
internal static bool TryMarshalSet<TInternal, TPublic>(ref IntPtr target, TPublic[] source, out int arrayLength)
where TInternal : struct, ISettable
where TPublic : class
{
arrayLength = 0;
if (source != null)
{
TInternal[] targetInternal = new TInternal[source.Length];
for (int index = 0; index < source.Length; ++index)
{
targetInternal[index].Set(source[index]);
}
if (TryMarshalSet(ref target, targetInternal))
{
arrayLength = source.Length;
return true;
}
}
return false;
}
internal static bool TryMarshalSet<TInternal, TPublic>(ref IntPtr target, TPublic[] source, out uint arrayLength)
where TInternal : struct, ISettable
where TPublic : class
{
arrayLength = 0;
int arrayLengthInt;
if (TryMarshalSet<TInternal, TPublic>(ref target, source, out arrayLengthInt))
{
arrayLength = (uint)arrayLengthInt;
return true;
}
return false;
}
internal static bool TryMarshalSet<TInternal, TPublic>(ref IntPtr target, TPublic[] source, out int arrayLength, bool isElementAllocated)
where TInternal : struct, ISettable
where TPublic : class
{
arrayLength = 0;
if (source != null)
{
TInternal[] targetInternal = new TInternal[source.Length];
for (int index = 0; index < source.Length; ++index)
{
targetInternal[index].Set(source[index]);
}
if (TryMarshalSet(ref target, targetInternal, isElementAllocated))
{
arrayLength = source.Length;
return true;
}
}
return false;
}
internal static bool TryMarshalSet<TInternal, TPublic>(ref IntPtr target, TPublic[] source, out uint arrayLength, bool isElementAllocated)
where TInternal : struct, ISettable
where TPublic : class
{
arrayLength = 0;
int arrayLengthInt;
if (TryMarshalSet<TInternal, TPublic>(ref target, source, out arrayLengthInt, isElementAllocated))
{
arrayLength = (uint)arrayLengthInt;
return true;
}
return false;
}
internal static bool TryMarshalCopy(IntPtr target, byte[] source)
{
if (target != IntPtr.Zero && source != null)
{
Marshal.Copy(source, 0, target, source.Length);
return true;
}
return false;
}
internal static bool TryMarshalAllocate(ref IntPtr target, int size, out Allocation allocation)
{
TryMarshalDispose(ref target);
target = Marshal.AllocHGlobal(size);
Marshal.WriteByte(target, 0, 0);
allocation = new Allocation(size);
s_Allocations.Add(target, allocation);
return true;
}
internal static bool TryMarshalAllocate(ref IntPtr target, uint size, out Allocation allocation)
{
return TryMarshalAllocate(ref target, (int)size, out allocation);
}
internal static bool TryMarshalAllocate(ref IntPtr target, int size)
{
Allocation allocation;
return TryMarshalAllocate(ref target, size, out allocation);
}
internal static bool TryMarshalAllocate(ref IntPtr target, uint size)
{
Allocation allocation;
return TryMarshalAllocate(ref target, size, out allocation);
}
#endregion
// These functions are the front end for disposing of unmanaged memory that this wrapper has allocated.
#region Marshal Disposers
internal static bool TryMarshalDispose<TDisposable>(ref TDisposable disposable)
where TDisposable : IDisposable
{
if (disposable != null)
{
disposable.Dispose();
return true;
}
return false;
}
internal static bool TryMarshalDispose(ref IntPtr value)
{
return TryRelease(ref value);
}
internal static bool TryMarshalDispose<TEnum>(ref IntPtr member, TEnum currentEnum, TEnum comparisonEnum)
{
if ((int)(object)currentEnum == (int)(object)comparisonEnum)
{
return TryRelease(ref member);
}
return false;
}
#endregion
// These functions are exposed to the wrapper to generally streamline blocks of generated code.
#region Helpers
internal static T GetDefault<T>()
{
return default(T);
}
internal static void AddCallback(ref IntPtr clientDataAddress, object clientData, Delegate publicDelegate, Delegate privateDelegate, params Delegate[] structDelegates)
{
TryAllocateCacheOnly(ref clientDataAddress, new BoxedData(clientData));
s_Callbacks.Add(clientDataAddress, new DelegateHolder(publicDelegate, privateDelegate, structDelegates));
}
internal static void AddStaticCallback(string key, Delegate publicDelegate, Delegate privateDelegate)
{
s_StaticCallbacks[key] = new DelegateHolder(publicDelegate, privateDelegate);
}
internal static bool TryAssignNotificationIdToCallback(IntPtr clientDataAddress, ulong notificationId)
{
if (notificationId != 0)
{
DelegateHolder delegateHolder = null;
if (s_Callbacks.TryGetValue(clientDataAddress, out delegateHolder))
{
delegateHolder.NotificationId = notificationId;
return true;
}
}
// We can safely release if the notification id came back invalid
else
{
s_Callbacks.Remove(clientDataAddress);
TryRelease(ref clientDataAddress);
}
return false;
}
internal static bool TryRemoveCallbackByNotificationId(ulong notificationId)
{
var delegateHolderPairs = s_Callbacks.Where(pair => pair.Value.NotificationId.HasValue && pair.Value.NotificationId == notificationId);
if (delegateHolderPairs.Any())
{
IntPtr clientDataAddress = delegateHolderPairs.First().Key;
s_Callbacks.Remove(clientDataAddress);
TryRelease(ref clientDataAddress);
return true;
}
return false;
}
internal static bool TryGetAndRemoveCallback<TCallback, TCallbackInfoInternal, TCallbackInfo>(IntPtr callbackInfoAddress, out TCallback callback, out TCallbackInfo callbackInfo)
where TCallback : class
where TCallbackInfoInternal : struct, ICallbackInfoInternal
where TCallbackInfo : class, ICallbackInfo, ISettable, new()
{
callback = null;
callbackInfo = null;
IntPtr clientDataAddress = IntPtr.Zero;
if (TryMarshalGet<TCallbackInfoInternal, TCallbackInfo>(callbackInfoAddress, out callbackInfo, out clientDataAddress)
&& TryGetAndRemoveCallback(clientDataAddress, callbackInfo, out callback))
{
return true;
}
return false;
}
internal static bool TryGetStructCallback<TDelegate, TCallbackInfoInternal, TCallbackInfo>(IntPtr callbackInfoAddress, out TDelegate callback, out TCallbackInfo callbackInfo)
where TDelegate : class
where TCallbackInfoInternal : struct, ICallbackInfoInternal
where TCallbackInfo : class, ISettable, new()
{
callback = null;
callbackInfo = null;
IntPtr clientDataAddress = IntPtr.Zero;
if (TryMarshalGet<TCallbackInfoInternal, TCallbackInfo>(callbackInfoAddress, out callbackInfo, out clientDataAddress)
&& TryGetStructCallback(clientDataAddress, out callback))
{
return true;
}
return false;
}
#endregion
// These functions are used for allocating unmanaged memory.
// They should not be exposed outside of this helper.
#region Private Allocators
private static bool TryAllocate<T>(ref IntPtr target, T source)
{
TryRelease(ref target);
if (target != IntPtr.Zero)
{
throw new ExternalAllocationException(target, source.GetType());
}
if (source == null)
{
return false;
}
Allocation allocation;
if (!TryMarshalAllocate(ref target, Marshal.SizeOf(typeof(T)), out allocation))
{
return false;
}
allocation.SetCachedData(source);
Marshal.StructureToPtr(source, target, false);
return true;
}
private static bool TryAllocate<T>(ref IntPtr target, T? source)
where T : struct
{
TryRelease(ref target);
if (target != IntPtr.Zero)
{
throw new ExternalAllocationException(target, source.GetType());
}
if (source == null)
{
return false;
}
return TryAllocate(ref target, source.Value);
}
private static bool TryAllocate(ref IntPtr target, string source)
{
TryRelease(ref target);
if (target != IntPtr.Zero)
{
throw new ExternalAllocationException(target, source.GetType());
}
if (source == null)
{
return false;
}
byte[] bytes;
if (TryConvert(source, out bytes))
{
return TryAllocate(ref target, bytes, false);
}
return false;
}
private static bool TryAllocate<T>(ref IntPtr target, T[] source, bool isElementAllocated)
{
TryRelease(ref target);
if (target != IntPtr.Zero)
{
throw new ExternalAllocationException(target, source.GetType());
}
if (source == null)
{
return false;
}
var itemSize = 0;
if (isElementAllocated)
{
itemSize = Marshal.SizeOf(typeof(IntPtr));
}
else
{
itemSize = Marshal.SizeOf(typeof(T));
}
// Allocate the array
Allocation allocation;
if (!TryMarshalAllocate(ref target, source.Length * itemSize, out allocation))
{
return false;
}
allocation.SetCachedData(source, isElementAllocated);
for (int itemIndex = 0; itemIndex < source.Length; ++itemIndex)
{
var item = (T)source.GetValue(itemIndex);
if (isElementAllocated)
{
// Allocate the item
IntPtr newItemAddress = IntPtr.Zero;
if (typeof(T) == typeof(string))
{
TryAllocate(ref newItemAddress, (string)(object)item);
}
else if (typeof(T).BaseType == typeof(Handle))
{
TryConvert((Handle)(object)item, out newItemAddress);
}
else
{
TryAllocate(ref newItemAddress, item);
}
// Copy the item's address into the array
IntPtr itemAddress = new IntPtr(target.ToInt64() + itemIndex * itemSize);
Marshal.StructureToPtr(newItemAddress, itemAddress, false);
}
else
{
// Copy the data straight into memory
IntPtr itemAddress = new IntPtr(target.ToInt64() + itemIndex * itemSize);
Marshal.StructureToPtr(item, itemAddress, false);
}
}
return true;
}
private static bool TryAllocateCacheOnly<T>(ref IntPtr target, T source)
{
TryRelease(ref target);
if (target != IntPtr.Zero)
{
throw new ExternalAllocationException(target, source.GetType());
}
if (source == null)
{
return false;
}
// The source should always be fetched directly from our cache, so the allocation is arbitrary.
Allocation allocation;
if (!TryMarshalAllocate(ref target, 1, out allocation))
{
return false;
}
allocation.SetCachedData(source);
return true;
}
#endregion
// These functions are used for releasing unmanaged memory.
// They should not be exposed outside of this helper.
#region Private Releasers
private static bool TryRelease(ref IntPtr target)
{
if (target == IntPtr.Zero)
{
return false;
}
Allocation allocation = null;
if (!s_Allocations.TryGetValue(target, out allocation))
{
return false;
}
if (allocation.IsCachedArrayElementAllocated.HasValue)
{
var itemSize = 0;
if (allocation.IsCachedArrayElementAllocated.Value)
{
itemSize = Marshal.SizeOf(typeof(IntPtr));
}
else
{
itemSize = Marshal.SizeOf(allocation.CachedData.GetType().GetElementType());
}
var array = allocation.CachedData as Array;
for (int itemIndex = 0; itemIndex < array.Length; ++itemIndex)
{
if (allocation.IsCachedArrayElementAllocated.Value)
{
var itemAddress = new IntPtr(target.ToInt64() + itemIndex * itemSize);
itemAddress = Marshal.ReadIntPtr(itemAddress);
TryRelease(ref itemAddress);
}
else
{
var item = array.GetValue(itemIndex);
if (item is IDisposable)
{
var disposable = item as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
}
}
if (allocation.CachedData is IDisposable)
{
var disposable = allocation.CachedData as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
Marshal.FreeHGlobal(target);
s_Allocations.Remove(target);
target = IntPtr.Zero;
return true;
}
#endregion
// These functions are used for fetching unmanaged memory.
// They should not be exposed outside of this helper.
#region Private Fetchers
private static bool TryFetch<T>(IntPtr source, out T target)
{
target = GetDefault<T>();
if (source == IntPtr.Zero)
{
return false;
}
// If this is an allocation containing cached data, we should be able to fetch it from the cache
if (s_Allocations.ContainsKey(source))
{
Allocation allocation = s_Allocations[source];
if (allocation.CachedData != null)
{
if (allocation.CachedData.GetType() == typeof(T))
{
target = (T)allocation.CachedData;
return true;
}
else
{
throw new CachedTypeAllocationException(source, allocation.CachedData.GetType(), typeof(T));
}
}
}
target = (T)Marshal.PtrToStructure(source, typeof(T));
return true;
}
private static bool TryFetch<T>(IntPtr source, out T? target)
where T : struct
{
target = GetDefault<T?>();
if (source == IntPtr.Zero)
{
return false;
}
// If this is an allocation containing cached data, we should be able to fetch it from the cache
if (s_Allocations.ContainsKey(source))
{
Allocation allocation = s_Allocations[source];
if (allocation.CachedData != null)
{
if (allocation.CachedData.GetType() == typeof(T))
{
target = (T?)allocation.CachedData;
return true;
}
else
{
throw new CachedTypeAllocationException(source, allocation.CachedData.GetType(), typeof(T));
}
}
}
target = (T?)Marshal.PtrToStructure(source, typeof(T));
return true;
}
private static bool TryFetch<T>(IntPtr source, out T[] target, int arrayLength, bool isElementAllocated)
{
target = null;
if (source == IntPtr.Zero)
{
return false;
}
// If this is an allocation containing cached data, we should be able to fetch it from the cache
if (s_Allocations.ContainsKey(source))
{
Allocation allocation = s_Allocations[source];
if (allocation.CachedData != null)
{
if (allocation.CachedData.GetType() == typeof(T[]))
{
var cachedArray = (Array)allocation.CachedData;
if (cachedArray.Length == arrayLength)
{
target = cachedArray as T[];
return true;
}
else
{
throw new CachedArrayAllocationException(source, cachedArray.Length, arrayLength);
}
}
else
{
throw new CachedTypeAllocationException(source, allocation.CachedData.GetType(), typeof(T[]));
}
}
}
var itemSize = 0;
if (isElementAllocated)
{
itemSize = Marshal.SizeOf(typeof(IntPtr));
}
else
{
itemSize = Marshal.SizeOf(typeof(T));
}
List<T> items = new List<T>();
for (int itemIndex = 0; itemIndex < arrayLength; ++itemIndex)
{
IntPtr itemAddress = new IntPtr(source.ToInt64() + itemIndex * itemSize);
if (isElementAllocated)
{
itemAddress = Marshal.ReadIntPtr(itemAddress);
}
T item;
TryFetch(itemAddress, out item);
items.Add(item);
}
target = items.ToArray();
return true;
}
private static bool TryFetch(IntPtr source, out string target)
{
target = null;
if (source == IntPtr.Zero)
{
return false;
}
// Find the null terminator
int length = 0;
while (Marshal.ReadByte(source, length) != 0)
{
++length;
}
byte[] bytes = new byte[length];
Marshal.Copy(source, bytes, 0, length);
target = Encoding.UTF8.GetString(bytes);
return true;
}
#endregion
// These functions are used for converting managed memory.
// They should not be exposed outside of this helper.
#region Private Converters
private static bool TryConvert<THandle>(IntPtr source, out THandle target)
where THandle : Handle, new()
{
target = null;
if (source != IntPtr.Zero)
{
target = new THandle();
target.InnerHandle = source;
}
return true;
}
internal static bool TryConvert<TSource, TTarget>(TSource source, out TTarget target)
where TTarget : ISettable, new()
{
target = GetDefault<TTarget>();
if (source != null)
{
target = new TTarget();
target.Set(source);
}
return true;
}
private static bool TryConvert(Handle source, out IntPtr target)
{
target = IntPtr.Zero;
if (source != null)
{
target = source.InnerHandle;
}
return true;
}
private static bool TryConvert(byte[] source, out string target)
{
target = null;
if (source == null)
{
return false;
}
int length = 0;
foreach (byte currentByte in source)
{
if (currentByte == 0)
{
break;
}
++length;
}
target = Encoding.UTF8.GetString(source.Take(length).ToArray());
return true;
}
private static bool TryConvert(string source, out byte[] target, int length)
{
if (source == null)
{
source = "";
}
target = Encoding.UTF8.GetBytes(new string(source.Take(length).ToArray()).PadRight(length, '\0'));
return true;
}
private static bool TryConvert(string source, out byte[] target)
{
return TryConvert(source, out target, source.Length + 1);
}
private static bool TryConvert<T>(T[] source, out int target)
{
target = 0;
if (source != null)
{
target = source.Length;
}
return true;
}
private static bool TryConvert<T>(T[] source, out uint target)
{
target = 0;
int targetInt;
if (TryConvert(source, out targetInt))
{
target = (uint)targetInt;
return true;
}
return false;
}
internal static bool TryConvert<TSource, TTarget>(TSource[] source, out TTarget[] target)
where TTarget : ISettable, new()
{
target = GetDefault<TTarget[]>();
if (source != null)
{
target = new TTarget[source.Length];
for (int index = 0; index < source.Length; ++index)
{
target[index] = new TTarget();
target[index].Set(source[index]);
}
}
return true;
}
private static bool TryConvert(int source, out bool target)
{
target = source != 0;
return true;
}
private static bool TryConvert(bool source, out int target)
{
target = source ? 1 : 0;
return true;
}
private static bool TryConvert(DateTimeOffset? source, out long target)
{
target = -1;
if (source.HasValue)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long unixTimestampTicks = (source.Value.UtcDateTime - unixStart).Ticks;
long unixTimestampSeconds = unixTimestampTicks / TimeSpan.TicksPerSecond;
target = unixTimestampSeconds;
}
return true;
}
private static bool TryConvert(long source, out DateTimeOffset? target)
{
target = null;
if (source >= 0)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
long unixTimeStampTicks = source * TimeSpan.TicksPerSecond;
target = new DateTimeOffset(unixStart.Ticks + unixTimeStampTicks, TimeSpan.Zero);
}
return true;
}
#endregion
// These functions exist to further streamline blocks of generated code.
#region Private Helpers
private static bool CanRemoveCallback<TCallbackInfo>(IntPtr clientDataAddress, TCallbackInfo callbackInfo)
where TCallbackInfo : ICallbackInfo
{
DelegateHolder delegateHolder = null;
if (s_Callbacks.TryGetValue(clientDataAddress, out delegateHolder))
{
if (delegateHolder.NotificationId.HasValue)
{
return false;
}
}
if (callbackInfo.GetResultCode().HasValue)
{
return Common.IsOperationComplete(callbackInfo.GetResultCode().Value);
}
return true;
}
private static bool TryGetAndRemoveCallback<TCallback, TCallbackInfo>(IntPtr clientDataAddress, TCallbackInfo callbackInfo, out TCallback callback)
where TCallback : class
where TCallbackInfo : ICallbackInfo
{
callback = null;
if (clientDataAddress != IntPtr.Zero && s_Callbacks.ContainsKey(clientDataAddress))
{
callback = s_Callbacks[clientDataAddress].Public as TCallback;
if (callback != null)
{
if (CanRemoveCallback(clientDataAddress, callbackInfo))
{
s_Callbacks.Remove(clientDataAddress);
TryRelease(ref clientDataAddress);
}
return true;
}
}
return false;
}
internal static bool TryGetStaticCallback<TCallback>(string key, out TCallback callback)
where TCallback : class
{
callback = null;
if (s_StaticCallbacks.ContainsKey(key))
{
callback = s_StaticCallbacks[key].Public as TCallback;
if (callback != null)
{
return true;
}
}
return false;
}
private static bool TryGetStructCallback<TCallback>(IntPtr clientDataAddress, out TCallback structCallback)
where TCallback : class
{
structCallback = null;
if (clientDataAddress != IntPtr.Zero && s_Callbacks.ContainsKey(clientDataAddress))
{
structCallback = s_Callbacks[clientDataAddress].StructDelegates.FirstOrDefault(delegat => delegat.GetType() == typeof(TCallback)) as TCallback;
if (structCallback != null)
{
return true;
}
}
return false;
}
#endregion
}
}