Skip to content

Instantly share code, notes, and snippets.

@Laeeth
Created May 5, 2018 12:34
Show Gist options
  • Save Laeeth/f7bab7017ccb634e333822ec42b73079 to your computer and use it in GitHub Desktop.
Save Laeeth/f7bab7017ccb634e333822ec42b73079 to your computer and use it in GitHub Desktop.
csharp marshalling code
using System;
using System.Dynamic;
using System.Runtime.InteropServices;
namespace Symmetry.Xenon.Model.Marshalling
{
/**
* Base for Marshallers which need a buffer and a size - basically for wrapping binary data.
*/
public abstract class CharArrayMarshaller : DualAllocationMarshaller
{
public override object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
var array = (SizedArray) Marshal.PtrToStructure(pNativeData, typeof(SizedArray));
if (array.Handle == IntPtr.Zero)
return null;
return UnmarshalArray(array);
}
public abstract override IntPtr MarshalManagedToNative(object ManagedObj);
protected override void FreeNativePointer(IntPtr pNativeData)
{
DeleteSizedCharArray(pNativeData);
}
protected override void FreeManagedPointer(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
protected abstract object UnmarshalArray(SizedArray array);
public override int GetNativeDataSize()
{
return IntPtr.Size;
}
[DllImport("Analytics.Model.dll", EntryPoint = "delete_sized_char_array",
CallingConvention = CallingConvention.Cdecl)]
private static extern void DeleteSizedCharArray(IntPtr pointer);
[StructLayout(LayoutKind.Sequential)]
protected struct SizedArray
{
public IntPtr Handle;
public int Size;
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace Symmetry.Xenon.Model.Marshalling
{
/**
* Base for marshallers that need to free pointers allocated in manage code differently
* to pointers managed in native code. This is achieved by keeping a threadlocal set of
* pointers allocated during a call to MarshalManagedToNative.
*/
public abstract class DualAllocationMarshaller : ICustomMarshaler
{
private readonly ThreadLocal<HashSet<IntPtr>> _managedPointers =
new ThreadLocal<HashSet<IntPtr>>(() => new HashSet<IntPtr>());
public abstract object MarshalNativeToManaged(IntPtr pNativeData);
public abstract IntPtr MarshalManagedToNative(object ManagedObj);
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return;
if (_managedPointers.Value.Remove(pNativeData))
{
// allocated in managed code
FreeManagedPointer(pNativeData);
}
else
{
// allocated in native code
FreeNativePointer(pNativeData);
}
}
public abstract void CleanUpManagedData(object ManagedObj);
public abstract int GetNativeDataSize();
protected abstract void FreeNativePointer(IntPtr pNativeData);
protected abstract void FreeManagedPointer(IntPtr pNativeData);
protected void RegisterManagedPointer(IntPtr pNativeData)
{
_managedPointers.Value.Add(pNativeData);
}
}
/**
* Base for marshallers that need to free pointers allocated in manage code differently
* to pointers managed in native code, while also keeping tracked of marshalled managed
* objects for additional cleanup. This is achieved by keeping a threadlocal dictionary of
* pointers allocated during a call to MarshalManagedToNative.
*/
public abstract class TrackingDualAllocationMarshaller<TObj>
{
private readonly ThreadLocal<IDictionary<IntPtr, TObj>> _managedPointers =
new ThreadLocal<IDictionary<IntPtr, TObj>>(() => new Dictionary<IntPtr, TObj>());
public abstract object MarshalNativeToManaged(IntPtr pNativeData);
public abstract IntPtr MarshalManagedToNative(object ManagedObj);
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return;
TObj obj;
if (_managedPointers.Value.TryGetValue(pNativeData, out obj))
{
_managedPointers.Value.Remove(pNativeData);
// allocated in managed code
FreeManagedPointer(pNativeData, obj);
}
else
{
// allocated in native code
FreeNativePointer(pNativeData);
}
}
public abstract void CleanUpManagedData(object ManagedObj);
public abstract int GetNativeDataSize();
protected abstract void FreeNativePointer(IntPtr pNativeData);
protected abstract void FreeManagedPointer(IntPtr pNativeData, TObj obj);
protected void RegisterManagedPointer(IntPtr pNativeData, TObj obj)
{
_managedPointers.Value.Add(pNativeData, obj);
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace Symmetry.Xenon.Model.Marshalling
{
/**
* Abstract base class for marshallers which need to hold a reference to a GCHandle for cleanup later.
* The marshalled data contains an additional pointer to the GCHandle at location -1.
* The GCHandle is freed during clearnup fo the managed pointer.
*/
public abstract class GCHandleMarshaller<TMarsh, TObj> : DualAllocationMarshaller
where TMarsh : GCHandleMarshaller<TMarsh, TObj>, new()
{
private static readonly ICustomMarshaler Instance = new TMarsh();
private readonly GCHandleType _handleType;
protected GCHandleMarshaller(GCHandleType handleType)
{
_handleType = handleType;
}
public static ICustomMarshaler GetInstance(string cookie)
{
return Instance;
}
public override IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj == null)
return IntPtr.Zero;
if (!(ManagedObj is TObj))
throw new ArgumentException("Cannot marshal " + ManagedObj.GetType().Name + "!");
TObj obj = (TObj) ManagedObj;
// Allocate enough space for the GCHandle pointer and the object.
IntPtr ptr = Marshal.AllocHGlobal(IntPtr.Size + AllocatedSize(obj));
// Allocate a GCHandle for the object.
GCHandle gcHandle = GCHandle.Alloc(obj, _handleType);
// Write the pointer value to the allocated memory location
Marshal.WriteIntPtr(ptr, 0, GCHandle.ToIntPtr(gcHandle));
// Advance past the stored GCHandle pointer, this is the pointer the client will see.
ptr += IntPtr.Size;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
CopyData(ptr, obj);
}
catch
{
// Make sure we clean up again if this fails.
FreeManagedPointer(ptr);
throw;
}
RegisterManagedPointer(ptr);
return ptr;
}
protected abstract int AllocatedSize(TObj obj);
protected abstract void CopyData(IntPtr dest, TObj obj);
protected override void FreeManagedPointer(IntPtr pNativeData)
{
GCHandle gcHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(pNativeData, -IntPtr.Size));
TObj obj = (TObj) gcHandle.Target;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
DoNativeCleanup(pNativeData, obj);
}
finally
{
gcHandle.Free();
Marshal.FreeHGlobal(pNativeData - IntPtr.Size);
}
}
protected abstract void DoNativeCleanup(IntPtr pNativeData, TObj obj);
public override int GetNativeDataSize()
{
return IntPtr.Size;
}
}
}
using Microsoft.Win32.SafeHandles;
using System;
namespace Symmetry.Xenon.Model.Handles
{
public abstract class Handle : SafeHandleZeroOrMinusOneIsInvalid
{
protected Handle()
: base(true)
{
}
protected Handle(IntPtr handle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
if (IsInvalid)
return false;
DeletePointer();
handle = IntPtr.Zero; // In case this is called multiple times
return true;
}
/*
* You can't just have a method in C++ which takes a void* and deletes it so
* this needs to be overridden with the appropraite native method to call.
* The native method just needs to call delete on the pointer.
*/
protected abstract void DeletePointer();
}
}
using System;
using System.Runtime.InteropServices;
using Symmetry.Xenon.Model.Marshalling;
namespace Symmetry.Xenon.Model.Handles
{
/**
* Marshaller for arrays of SafeHandles and their
derivatives.
* Supports only managed to native for now - may need to be wrapped in a sized array.
*/
public sealed class HandleArrayMarshaller : GCHandleMarshaller<HandleArrayMarshaller, SafeHandle[]>
{
public HandleArrayMarshaller()
: base(GCHandleType.Normal)
{
}
public override object MarshalNativeToManaged(IntPtr pNativeData)
{
throw new NotImplementedException();
}
protected override int AllocatedSize(SafeHandle[] obj)
{
return obj.Length * IntPtr.Size;
}
protected override void CopyData(IntPtr dest, SafeHandle[] obj)
{
bool success = false;
for (int i = 0; i < obj.Length; i++)
{
SafeHandle handle = obj[i];
if (handle != null && !handle.IsClosed && !handle.IsInvalid)
{
// Add a reference count so that the handle does not get GCed.
handle.DangerousAddRef(ref success);
// Write the ith handle's pointer value to the allocated memory in position i
Marshal.WriteIntPtr(dest, i*IntPtr.Size, handle.DangerousGetHandle());
}
}
}
public override void CleanUpManagedData(object ManagedObj)
{
throw new NotImplementedException();
}
protected override void FreeNativePointer(IntPtr pNativeData)
{
throw new NotImplementedException();
}
protected override void DoNativeCleanup(IntPtr pNativeData, SafeHandle[] obj)
{
for (int i = 0; i < obj.Length; i++)
{
SafeHandle handle = obj[i];
if (handle != null && Marshal.ReadIntPtr(pNativeData, i*IntPtr.Size) != IntPtr.Zero)
{
handle.DangerousRelease();
}
}
}
}
}
using System;
namespace Symmetry.Xenon.Model.Marshalling
{
public class MarshallingException : Exception
{
public MarshallingException(string message)
: base(message)
{
}
public MarshallingException(object value, Type targetType, string reason)
: this(string.Format("Error marshalling {0} to type {1}: {2}", value, targetType, reason))
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using pb = Symmetry.Xenon.Model.Protobuf;
namespace Symmetry.Xenon.Model.Marshalling
{
public static class Marshalling
{
private static readonly DateTime StartTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
public static pb.XeDate Marshal(this DateTime dateTime)
{
var year = dateTime.Year;
if (year < 1400 || year > 9999)
throw new MarshallingException(dateTime, typeof(pb.XeDate), "Year not in range 1400-9999.");
return new pb.XeDate
{
Millis = (long) (dateTime - StartTime).TotalMilliseconds
};
}
public static long ToTimestamp(this DateTime dateTime)
{
return (long) (dateTime - StartTime).TotalMilliseconds;
}
public static DateTime Unmarshal(this pb.XeDate date)
{
return StartTime.AddMilliseconds(date.Millis);
}
public static DateTime ToDate(this long date)
{
return StartTime.AddMilliseconds(date);
}
public static pb.Schedule Marshal(this IEnumerable<DateTime> dates)
{
var schedule = new pb.Schedule();
schedule.Dates.Add(dates.Select(Marshal));
return schedule;
}
public static pb.MonthSchedule Marshal(this IEnumerable<pb.YearMonth> months)
{
var schedule = new pb.MonthSchedule();
schedule.Months.Add(months);
return schedule;
}
}
}
using System;
using System.Runtime.InteropServices;
using Symmetry.Xenon.Model.Handles;
using static System.Runtime.InteropServices.CallingConvention;
namespace Symmetry.Xenon.Model.Marshalling
{
public class CurrencyAmountArrayMarshaller : ValueArrayMarshaller<CurrencyAmountArrayMarshaller, CurrencyAmountImpl>
{
protected override void FreeNativePointer(IntPtr pNativeData)
{
DeleteCurrencyAmountArray(pNativeData);
}
[DllImport("Analytics.Model.dll", EntryPoint = "delete_sized_currency_amount_array", CallingConvention = Cdecl)]
private static extern void DeleteCurrencyAmountArray(IntPtr pointer);
}
/**
* Base for marshalling arrays of blittable types
*/
public abstract class ValueArrayMarshaller : DualAllocationMarshaller
{
protected static readonly int ArraySize = Marshal.SizeOf(typeof(SizedArray));
protected override void FreeManagedPointer(IntPtr pNativeData)
{
var array = (SizedArray)Marshal.PtrToStructure(pNativeData, typeof(SizedArray));
Marshal.FreeHGlobal(array.Handle);
Marshal.FreeHGlobal(pNativeData);
}
public override void CleanUpManagedData(object ManagedObj)
{
// No Op
}
public override int GetNativeDataSize()
{
return ArraySize;
}
[StructLayout(LayoutKind.Sequential)]
protected struct SizedArray
{
public IntPtr Handle;
public int Size;
}
}
public abstract class ValueArrayMarshaller<TMarsh, TObj> : ValueArrayMarshaller
where TMarsh : ValueArrayMarshaller<TMarsh, TObj>, new()
{
private static readonly int ElementSize = Marshal.SizeOf(typeof(TObj));
private static readonly ICustomMarshaler Instance = new TMarsh();
public static ICustomMarshaler GetInstance(string cookie)
{
return Instance;
}
public override object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
var array = (SizedArray) Marshal.PtrToStructure(pNativeData, typeof(SizedArray));
if (array.Handle == IntPtr.Zero)
return null;
var result = new TObj[array.Size];
var ptr = array.Handle;
for (var i = 0; i < array.Size; ++i)
{
result[i] = (TObj) Marshal.PtrToStructure(ptr, typeof(TObj));
ptr += ElementSize;
}
return result;
}
public override IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj == null)
return IntPtr.Zero;
if (!(ManagedObj is TObj[]))
throw new ArgumentException("Cannot marshal " + ManagedObj.GetType().Name + "!");
var managedArray = (TObj[]) ManagedObj;
var handle = Marshal.AllocHGlobal(managedArray.Length * ElementSize);
var elemPtr = handle;
foreach (var item in managedArray)
{
Marshal.StructureToPtr(item, elemPtr, false);
elemPtr += ElementSize;
}
var array = new SizedArray
{
Handle = handle,
Size = managedArray.Length
};
var ptr = Marshal.AllocHGlobal(ArraySize);
Marshal.StructureToPtr(array, ptr, false);
return ptr;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment