using System; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace Python.Runtime { /// /// Abstract class defining boiler plate methods that /// Custom Marshalers will use. /// internal abstract class MarshalerBase : ICustomMarshaler { public object MarshalNativeToManaged(IntPtr pNativeData) { throw new NotImplementedException(); } public abstract IntPtr MarshalManagedToNative(object managedObj); public void CleanUpNativeData(IntPtr pNativeData) { Marshal.FreeHGlobal(pNativeData); } public void CleanUpManagedData(object managedObj) { // Let GC deal with it } public int GetNativeDataSize() { return IntPtr.Size; } } /// /// Custom Marshaler to deal with Managed String to Native /// conversion differences on UCS2/UCS4. /// internal class UcsMarshaler : MarshalerBase { private static readonly MarshalerBase Instance = new UcsMarshaler(); private static readonly Encoding PyEncoding = Runtime.PyEncoding; public override IntPtr MarshalManagedToNative(object managedObj) { var s = managedObj as string; if (s == null) { return IntPtr.Zero; } byte[] bStr = PyEncoding.GetBytes(s + "\0"); IntPtr mem = Marshal.AllocHGlobal(bStr.Length); try { Marshal.Copy(bStr, 0, mem, bStr.Length); } catch (Exception) { Marshal.FreeHGlobal(mem); throw; } return mem; } public static ICustomMarshaler GetInstance(string cookie) { return Instance; } public static string PtrToStringUni(IntPtr p) { if (p == IntPtr.Zero) { return null; } int size = GetUnicodeByteLength(p); var buffer = new byte[size]; Marshal.Copy(p, buffer, 0, size); return PyEncoding.GetString(buffer, 0, size); } public static int GetUnicodeByteLength(IntPtr p) { var len = 0; while (true) { int c = Runtime._UCS == 2 ? Marshal.ReadInt16(p, len * 2) : Marshal.ReadInt32(p, len * 4); if (c == 0) { return len * Runtime._UCS; } checked { ++len; } } } /// /// Utility function for Marshaling Unicode on PY3 and AnsiStr on PY2. /// Use on functions whose Input signatures changed between PY2/PY3. /// Ex. Py_SetPythonHome /// /// Managed String /// /// Ptr to Native String ANSI(PY2)/Unicode(PY3/UCS2)/UTF32(PY3/UCS4. /// /// /// You MUST deallocate the IntPtr of the Return when done with it. /// public static IntPtr Py3UnicodePy2StringtoPtr(string s) { return Instance.MarshalManagedToNative(s); } /// /// Utility function for Marshaling Unicode IntPtr on PY3 and /// AnsiStr IntPtr on PY2 to Managed Strings. Use on Python functions /// whose return type changed between PY2/PY3. /// Ex. Py_GetPythonHome /// /// Native Ansi/Unicode/UTF32 String /// /// Managed String /// public static string PtrToPy3UnicodePy2String(IntPtr p) { return PtrToStringUni(p); } } /// /// Custom Marshaler to deal with Managed String Arrays to Native /// conversion differences on UCS2/UCS4. /// internal class StrArrayMarshaler : MarshalerBase { private static readonly MarshalerBase Instance = new StrArrayMarshaler(); private static readonly Encoding PyEncoding = Runtime.PyEncoding; public override IntPtr MarshalManagedToNative(object managedObj) { var argv = managedObj as string[]; if (argv == null) { return IntPtr.Zero; } int totalStrLength = argv.Sum(arg => arg.Length + 1); int memSize = argv.Length * IntPtr.Size + totalStrLength * Runtime._UCS; IntPtr mem = Marshal.AllocHGlobal(memSize); try { // Preparing array of pointers to strings IntPtr curStrPtr = mem + argv.Length * IntPtr.Size; for (var i = 0; i < argv.Length; i++) { byte[] bStr = PyEncoding.GetBytes(argv[i] + "\0"); Marshal.Copy(bStr, 0, curStrPtr, bStr.Length); Marshal.WriteIntPtr(mem + i * IntPtr.Size, curStrPtr); curStrPtr += bStr.Length; } } catch (Exception) { Marshal.FreeHGlobal(mem); throw; } return mem; } public static ICustomMarshaler GetInstance(string cookie) { return Instance; } } /// /// Custom Marshaler to deal with Managed String to Native /// conversion on UTF-8. Use on functions that expect UTF-8 encoded /// strings like `PyUnicode_FromStringAndSize` /// /// /// If instead we used `MarshalAs(UnmanagedType.LPWStr)` the output to /// `foo` would be `f\x00o\x00o\x00`. /// internal class Utf8Marshaler : MarshalerBase { private static readonly MarshalerBase Instance = new Utf8Marshaler(); private static readonly Encoding PyEncoding = Encoding.UTF8; public override IntPtr MarshalManagedToNative(object managedObj) { var s = managedObj as string; if (s == null) { return IntPtr.Zero; } byte[] bStr = PyEncoding.GetBytes(s + "\0"); IntPtr mem = Marshal.AllocHGlobal(bStr.Length); try { Marshal.Copy(bStr, 0, mem, bStr.Length); } catch (Exception) { Marshal.FreeHGlobal(mem); throw; } return mem; } public static ICustomMarshaler GetInstance(string cookie) { return Instance; } } }