Crashes with RhinoCommon GeometryBase serialisation

Hi @stevebaer and @dalelear,

I’m getting exceptions, and in some cases crashes trying to (de)serialise GeometryBase objects in RhinoCommon.

public static byte[] CommonObjectToByteArray(Runtime.CommonObject data)
{
    if (data == null)
        throw new ArgumentNullException("data");

    FileIO.SerializationOptions options = new FileIO.SerializationOptions();
    options.RhinoVersion = 4;
    options.WriteUserData = true;

    StreamingContext context = new StreamingContext(StreamingContextStates.All, options);
    MemoryStream stream = new MemoryStream();

    Formatters.Binary.BinaryFormatter formatter = new Formatters.Binary.BinaryFormatter(null, context);

    formatter.Serialize(stream, data); // <-- Exception
    stream.Close();

    return stream.GetBuffer();
}

The exception is as follows:

{System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Runtime.InteropServices.Marshal.CopyToManaged(IntPtr source, Object destination, Int32 startIndex, Int32 length)
   at Rhino.Runtime.CommonObject.SerializeWriteON_Object(IntPtr pConstOnObject, SerializationInfo info, StreamingContext context)
   at Rhino.Runtime.CommonObject.GetObjectData(SerializationInfo info, StreamingContext context)
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph)
   at Grasshopper.Kernel.GH_Convert.CommonObjectToByteArray(Int32 rhinoVersion, CommonObject data) in C:\dev\grasshopper\1.0\root\src\GH_Convert.vb:line 792}

I’ve started calling this method in a loop and incrementing the options.RhinoVersion each time (first 4, then 5, then 6). I found that on Rhino 5, serialisation of some curves I’ve got only works if the version is set to 6. I do not know how it’s even legal to use 6 while running on Rhino5, but there you go.

However now the problem becomes deserialisation. If I use the same trick trying to deserialise the byte into a GeometryBase instance, there’s an exception that doesn’t cross the unmanaged/managed boundary and it crashes Rhino. I’ve tried stepping into the code, but I’m not getting anywhere sensible.

I’ve attached a Rhino5 3DM file which contains a single curve that will only serialize using RhinoVersion=6 on Rhino5. Trying to deserialize the resulting byte using RhinoVersion=4 will crash.

CurveWhichDoesNotSerialize.3dm (167.2 KB)

Can you add this to youtrack and assign it to me? I won’t be able to look at this for at least a few days and I don’t want to forget about it.

Done.

1 Like

David, I’m pretty sure this is in the .NET wrapping code because I don’t see anything in the exception information indicating the actual file writing/reading failed.

Setting version to 6 in Rhino 5 is not a good thing. It should not work and I imagine an error code is being ignored someplace.

Steve, there are several version numbers you can set. Please double check that the “4”, “5”, “6” corresponding to Rhino 4, 5, 6 are correctly setting the low level opennurbs 3dm archive version to 4, 50 or 60. The other version number is a large number that identifies the specific version of opennurbs.

David, if you want to get a few more details, run in mixed mode, put a breakpoint in ON_BinaryArchive::WriteObject() and then walk out to see what event triggers the .NET exception.

David,
Dale and I just committed some fixes for this yesterday. Could you please test
http://mcneel.myjetbrains.com/youtrack/issue/RH-28446
since I marked this as stop-ship for SR10

Thanks

1 Like

@stevebaer,

tested in Rhino6 and closed the YouTrack entry. It works now for me, thanks for fixing it.

I did run into another issue with the same curve, I use the following code at the moment to convert OpenNurbs objects to byte-arrays:

  Dim options As New FileIO.SerializationOptions()
  options.RhinoVersion = 5
  options.WriteUserData = True

  Dim context As New StreamingContext(StreamingContextStates.All, options)
  Dim stream As New MemoryStream()

  Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(Nothing, context)

  formatter.Serialize(stream, data)
  stream.Close()
  Return stream.GetBuffer()

Which works (as mentioned above) but the stream I get looks like this:

where every byte after roughly 8000 is zero. What would be the correct way of creating byte arrays from CommonObjects using the lowest possible RhinoVersion that still works? GH files written by Grasshoppers running on Rhino6 would ideally still be readable on Rhino5, so I’d prefer to use Rhino5 compatible serialization.