Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Refactor Managed-Native conversion - ICustomMarshaler #407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions src/runtime/CustomMarshaler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Python.Runtime
{
/// <summary>
/// Abstract class defining boiler plate methods that
/// Custom Marshalers will use.
/// </summary>
public 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;
}
}


/// <summary>
/// Custom Marshaler to deal with Managed String to Native
/// conversion differences on UCS2/UCS4.
/// </summary>
public class StrMarshaler : MarshalerBase
{
private static readonly MarshalerBase Instance = new StrMarshaler();
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)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no exception throwing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what you mean? like re-throwing the Exception? That part was meant to de-allocate the memory in case something goes wrong, otherwise keep it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't failing on Marshal.Copy need to be thrown as Exception? If not, how is this handled later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll throw the exception again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the throw in the latest commit

Marshal.FreeHGlobal(mem);
throw;
}

return mem;
}

public static ICustomMarshaler GetInstance(string cookie)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of string cookie?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its part of the implementation of the interface. On the link below scroll down to Implementing the GetInstance Method. For our use case we don't need the cookie though but left it since its part of the implementation.

https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.icustommarshaler(v=vs.110).aspx#Remarks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our old monosupport actually was an ICustomMarshaler too but it depended on mono libraries to work. It also had the same signature, except it was called s

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i see on Mono: // The Utf32Marshaler was written Jonathan Pryor and has been placed in the PUBLIC DOMAIN.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vmuriart so is custom marshalling dependent on COM interop now?

This static method is called by the common language runtime's COM interop layer to instantiate an instance of the custom marshaler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I think that line is in reference that the COM Interop layer can call the instance. Its probably related to the whole cookie thing in which the instance returned depends on which cookie is passed.

Copy link
Contributor

@den-run-ai den-run-ai Feb 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is previous Mono implemented Marshalled string not a null-terminating like you added here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it didn't do the marshal at all, it passed it mono to do it. Mono itself then did it here

{
return Instance;
}
}


/// <summary>
/// Custom Marshaler to deal with Managed String Arrays to Native
/// conversion differences on UCS2/UCS4.
/// </summary>
public 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;
}
}


/// <summary>
/// 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`
/// </summary>
/// <remarks>
/// If instead we used `MarshalAs(UnmanagedType.LPWStr)` the output to
/// `foo` would be `f\x00o\x00o\x00`.
/// </remarks>
public 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;
}
}
}
1 change: 1 addition & 0 deletions src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<Compile Include="constructorbinder.cs" />
<Compile Include="constructorbinding.cs" />
<Compile Include="converter.cs" />
<Compile Include="CustomMarshaler.cs" />
<Compile Include="debughelper.cs" />
<Compile Include="delegatemanager.cs" />
<Compile Include="delegateobject.cs" />
Expand Down
19 changes: 4 additions & 15 deletions src/runtime/converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -599,21 +599,10 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
{
if (Runtime.PyUnicode_GetSize(value) == 1)
{
op = Runtime.PyUnicode_AS_UNICODE(value);
if (Runtime.UCS == 2) // Don't trust linter, statement not always true.
{
// 2011-01-02: Marshal as character array because the cast
// result = (char)Marshal.ReadInt16(op); throws an OverflowException
// on negative numbers with Check Overflow option set on the project
Char[] buff = new Char[1];
Marshal.Copy(op, buff, 0, 1);
result = buff[0];
}
else // UCS4
{
// XXX this is probably NOT correct?
result = (char)Marshal.ReadInt32(op);
}
op = Runtime.PyUnicode_AsUnicode(value);
Char[] buff = new Char[1];
Marshal.Copy(op, buff, 0, 1);
result = buff[0];
return true;
}
goto type_error;
Expand Down
22 changes: 22 additions & 0 deletions src/runtime/debughelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,27 @@ internal static void debug(string msg)
Console.WriteLine("thread {0} : {1}", tid, caller);
Console.WriteLine(" {0}", msg);
}

/// <summary>
/// Helper function to inspect/compare managed to native conversions.
/// Especially useful when debugging CustomMarshaler.
/// </summary>
/// <param name="bytes"></param>
[Conditional("DEBUG")]
public static void PrintHexBytes(byte[] bytes)
{
if ((bytes == null) || (bytes.Length == 0))
{
Console.WriteLine("<none>");
}
else
{
foreach (byte t in bytes)
{
Console.Write("{0:X2} ", t);
}
Console.WriteLine();
}
}
}
}
Loading