Cannot deserialize ProtoBuf message in child process, works fine in parent - c#

I'm trying to send a protobuf message to a child process. Deserialization works fine in the parent, like this:
var deserialized = Serializer.Deserialize(typeof(ChildProcessTestMessage), new MemoryStream(new byte[] { 8, 1 }));
[ProtoContract]
public class ChildProcessTestMessage
{
[ProtoMember(1)]
public int Int { get; set; }
}
I have hardcoded the bytes 0x0801 in this sample code and in the child process in order to make sure that the bytes are correct. I obtained these bytes by serializing a test instance.
Deserialization code in the child is different in one important way: The parent communicates the type to use for deserialization over a pipe. The type can be loaded successfully in the child. I then call protobuf-net like this:
var (assemblyName, typeName, methodName, argument) = protocol.ReadExecuteMethod();
Assembly assembly;
try
{
var name = new AssemblyName(assemblyName);
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), name.Name + ".dll"));
}
catch (Exception ex)
{
//...
}
var type = assembly.GetType(typeName);
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
var parameterType = method.GetParameters().Single().ParameterType;
var message = Serializer.Deserialize(new MemoryStream(new byte[] { 8, 1 }), parameterType);
The child reports the following error:
Failed to invoke method 'TestMethod': ProtoBuf.ProtoException: Invalid
wire-type (Varint); this usually means you have over-written a file
without truncating or setting the length; see
Using Protobuf-net, I suddenly got an exception about an unknown wire-type at
ProtoBuf.ProtoReader.State.ThrowProtoException(String message) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 764
at ProtoBuf.ProtoReader.State.ThrowWireTypeException() in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 758
at
ProtoBuf.Internal.PrimaryTypeProvider.ProtoBuf.Serializers.ISerializer<System.Type>.Read(State&
state, Type value) in
//src/protobuf-net.Core/Internal/PrimaryTypeProvider.Primitives.cs:line
292 at
ProtoBuf.ProtoReader.State.g__ReadFieldOne|102_0[T](State&
state, SerializerFeatures features, T value, ISerializer1 serializer) in /_/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1075 at ProtoBuf.ProtoReader.State.ReadAsRoot[T](T value, ISerializer1
serializer) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1059
at ProtoBuf.ProtoReader.State.DeserializeRoot[T](T value,
ISerializer`1 serializer) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1036
at ProtoBuf.Serializer.Deserialize[T](Stream source, T value, Object
userState, Int64 length) in
//src/protobuf-net/Serializer.Deserialize.cs:line 43 at
ChildProcess.Program.Main(String[] args) in ...\Program.cs:line 324
I have added debug output to make sure that parameterType == typeof(ChildProcessTestMessage).
I'm currently at a loss at why it's not working and how to debug this. There must be a difference in the child process somehow but I have no lead what it could be.
One idea was that maybe assembly loading is different. Could this manifest as this kind of failure?
This is on .NET Core 5.
I'd appreciate your help.
Update: I have now hardcoded a copy of that proto contract into the child. Everything coming over the wire is now ignored. The deserialization call looks like in the parent. Same error as before...
The child process exe is a project in this solution. I'm invoking it from a unit test project, and it lives in the same directory as the unit test DLL and current directory. Could this mess up the child? There are lots of files there which the child maybe didn't expect.

OK, this was a pretty nasty but interesting bug. I kept bisecting by trying to remove differences between the parent and the child. So I kept hardcoding more deserializing code pasting it as the first line in Main. That worked, yet at a later point in the program the (seemingly!) same code didn't.
When I brought the working and the non-working code closer and closer, until the lines were adjacent, I finally spotted the issue:
When you say Serializer.Deserialize(type, stream) it picks a different overload than when you say Serializer.Deserialize(stream, type). Both will compile, but the latter will use type as the "prototype" object. This will cause the library to try to deserialize stream as a System.Type instance. This is a system type that cannot be deserialized. But protobuf-net attempted to do so anyway finding that the serialized bytes and the type structure are incompatible.
So in the end, all of this was caused by switched argument order. It had nothing to do with being in a child process.

Related

IndividualAssemblyLoadContext XmlSerializers.dll memory leak

I have a problem with a memory leak in .NET Core 3.1 API. The application is hosted in azure app service.
It is clearly visible on a graph that under constant load the memory is very slowly growing. it will only go down after app restart.
I created two memory dumps. One with high memory and one after restart and it's clearly visible that the reason is the app trying to load XmlSerialization.dll multiple times.
Now we have multiple other APIs that are using almost identical code when it comes to serialization and I'm not exactly sure why the problem occurs only in this one. Potentially because maybe this one has a much higher traffic when using the APIs.
I've read some articles about XmlSerializer class having memory issues but those were listed for some of the constructors we are not using. The only instance of using XmlSerializer directly in code was using an XmlSerializer(Type) constructor.
private static async Task<T> ParseResponseContentAsync<T>(HttpResponseMessage response, Accept accept)
{
try
{
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
{
using (StreamReader reader = new StreamReader(contentStream, Encoding.UTF8))
{
switch (accept)
{
case Accept.Xml:
XmlSerializer serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(reader);
case Accept.Json:
string stringContent = await reader.ReadToEndAsync();
return JsonConvert.DeserializeObject<T>(stringContent);
default:
throw new CustomHttpResponseException(HttpStatusCode.NotImplemented, $"Unsupported Accept type '{accept}'");
}
}
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Response content could not be deserialized as {accept} to {typeof(T)}", ex);
}
}
But I'm pretty sure this method is not used in this API anyway .
So another potential problematic place could be somewhere in the Controller serialization of responses.
Startup.cs registration:
services
.AddControllers(options =>
{
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(
new XmlWriterSettings
{
OmitXmlDeclaration = false
}));
options.Filters.Add<CustomHttpResponseExceptionFilter>();
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(
new StringEnumConverter(typeof(CamelCaseNamingStrategy))))
.AddXmlSerializerFormatters();
Example of an endpoint:
[Produces(MimeType.ApplicationXml, MimeType.TextXml, MimeType.ApplicationJson, MimeType.TextJson)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpGet("EndpointName")]
[Authorize]
public async Task<ActionResult<ResponseDto>> Get([FromModel] InputModel inputModel)
{
//some code
return responseDto;
}
Dto returned from the API:
[XmlRoot(ElementName = "SomeName")]
public class ResponseDto
{
[XmlElement(ElementName = "Result")]
public Result Result { get; set; }
[XmlAttribute(AttributeName = "Status")]
public string Status { get; set; }
[XmlAttribute(AttributeName = "DoneSoFar")]
public int DoneSoFar { get; set; }
[XmlAttribute(AttributeName = "OfTotal")]
public int OfTotal { get; set; }
}
Now I haven't been able to find any documented cases of .AddXmlSerialization causing these kinds of issues and I'm not sure what the solution or a workaround should be. Any help would be greatly appreciated.
EDIT:
I've run some additional tests as #dbc suggested.
Now it seems that we are not even hitting this line new XmlSerializer(typeof(T) in our scenarios since nothing was logged after logger code was added. We do however use default xml serialization for some of our API endpoints. Now one thing I noticed that might be causing this behavior is that the paths in memory dumps logs don't match the files that actually exist in the root folder.
The paths which are visible in memory dumps are *.Progress.Lib.XmlSerializers.dll or *.Domain.Lib.XmlSerializers.dll
Now I wonder if this isn't the issue documented here - link since I can't see those files in wwwroot directory.
If it is I'm not sure if the solution would be to somehow reference the .dlls directly ?
Edit2:
Adding a screen of how memory looks like after deploying cached serializer suggested by #dbc. There is no constant growth but it seems after few hours memory rises and doesn't go down. It is possible that the main problem is resolved but since it takes a lot of time to notice big differences we will monitor this for now. There is nothing showing in large object heap or any big number of memory is not allocated in managed memory. This API however when first deployed runs around 250 mB and after one day now at 850 mB. When we turn off the load test tool the memory didn't really go down too much.
Edit3:
So we looked closer at some historical data and it seems that the last screen is a normal behavior. It never grows beyond a certain point. Not sure why that happens but this is acceptable.
The assemblies that the new XmlSerializer(typeof(T)) constructor are trying to load are Microsoft XML Serializer Generator assemblies a.k.a Sgen.exe assemblies that might have or might not been created at the time the app was built.
But what are Sgen assemblies? In brief, XmlSerializer works by generating code to serialize and deserialize the type passed into the constructor, then compiling that generated code into a DLL and loading it into the application domain to do the actual serialization. This run-time DLL generation can be time-consuming, but as long as you use the XmlSerializer(Type) or XmlSerializer(Type, String) constructors it will only be done once per type T, with the resulting assembly being cached internally in a static dictionary by XmlSerializer.
As you might imagine this can cause the first call to new XmlSerializer(typeof(T)) to be slow, so (in .NET 2 I believe, this is all very old code) Microsoft introduced a tool to generate those run-time serialization DLLs at application build time: SGen.exe. This tool doesn't work for all types (e.g. generics) and was, if I recall correctly, finicky to use, but when it did work it did speed up serializer construction. Once loaded successfully the Sgen assembly is cached in the same cache used for generated assemblies.
And it seems like you have stumbled across a bug in .NET Core 3.1, 5, and 6 related to this:
The base class method OutputFormatter.CanWriteResult(OutputFormatterCanWriteContext context) of XmlSerializerOutputFormatter tests whether a type can be serialized by calling XmlSerializerOutputFormatter.CanWriteType(Type type). This in turn tests to see whether a type is serializable by XmlSerializer by attempting to construct a serializer for the type and returning false if construction failed because any exception was thrown. The serializer is cached if construction was successful, but nothing is cached if construction failed.
the new XmlSerializer(Type) constructor tries to load an Sgen assembly unless an assembly has already been cached for the type by a previous successful call to the constructor.
But if a type is not serializable by XmlSerializer, the constructor will throw an exception and nothing will be cached. Thus successive attempts to construct a serializer for the same non-serializable type will result in multiple calls to load Sgen assemblies.
As you yourself found, .NET Core itself permanently leaks a small amount of IndividualAssemblyLoadContext memory every time assembly load fails: Failed Assembly.Load and Assembly.LoadFile leaks memory #58093.
Putting all this together, enabling XML serialization when some of your DTOs are not serializable (because e.g. they don't have parameterless constructors) can result in ever-growing IndividualAssemblyLoadContext memory use.
So, what are your options for a workaround?
Firstly, issue #58093 was apparently fixed in .NET 7 with pull #68502 so if you upgrade to this version the problem may resolve itself.
Secondly, you could subclass XmlSerializerOutputFormatter to cache returned XmlSerializer instances even when null. This will prevent multiple attempts to create serializers for non-seializable types.
First, subclass XmlSerializerOutputFormatter and override XmlSerializerOutputFormatter.CreateSerializer(Type) as follows:
public class CachedXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
{
// Cache and reuse the serializers returned by base.CreateSerializer(t). When null is returned for a non-serializable type,
// a null serializer will be cached and returned.
static readonly ConcurrentDictionary<Type, XmlSerializer> Serializers = new ConcurrentDictionary<Type, XmlSerializer>();
public CachedXmlSerializerOutputFormatter() : base() { }
public CachedXmlSerializerOutputFormatter(ILoggerFactory loggerFactory) : base(loggerFactory) { }
public CachedXmlSerializerOutputFormatter(XmlWriterSettings writerSettings) : base(writerSettings) { }
public CachedXmlSerializerOutputFormatter(XmlWriterSettings writerSettings, ILoggerFactory loggerFactory) : base(writerSettings, loggerFactory) { }
protected override XmlSerializer CreateSerializer(Type type) { return Serializers.GetOrAdd(type, (t) => base.CreateSerializer(t)); }
}
Then replace use of XmlSerializerOutputFormatter with your subclassed version as follows:
services
.AddControllers(options =>
{
options.OutputFormatters.Add(new CachedXmlSerializerOutputFormatter (
new XmlWriterSettings
{
OmitXmlDeclaration = false
}));
options.Filters.Add<CustomHttpResponseExceptionFilter>();
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(
new StringEnumConverter(typeof(CamelCaseNamingStrategy))))
.AddXmlSerializerFormatters();
This should in theory eliminate the repeated failing calls to load Sgen assemblies.
Notes:
If you have enabled XML model binding and some of your input types are not XML-serializable, you may need to similarly subclass XmlSerializerInputFormatter. Its CreateSerializer(Type type)) also fails to cache failed attempts to construct a serializer.
Demo fiddles:
Demo fiddle showing that that multiple calls to XmlSerializerOutputFormatter.CanWriteType() for a non-serializable DTO result in multiple assembly load failures here: demo #1.
Demo fiddle showing that CachedXmlSerializerOutputFormatter fixes this problem here: demo #2.
Demo that multiple calls to XmlSerializerOutputFormatter.CanWriteType() for a serializable DTO do not result in multiple assembly load failures, and hence don't cause growing IndividualAssemblyLoadContext memory use, here: demo #3.
This might not be feasible, but could you offload the XML generation onto Azure API Management?
https://learn.microsoft.com/en-us/azure/api-management/api-management-transformation-policies#ConvertJSONtoXML

Protbuf-net Compiling to DLL string[] causing corrupt dll

Here is my code:
using ProtoBuf;
[ProtoContract]
[ProtoInclude(500, typeof(SampleClassDrv))]
public class SampleClass
{
[ProtoMember(1)] public int theInt;
[ProtoMember(2)] public string[] items;
public SampleClass(){}
public SampleClass(int c) {this.theInt = c;}
}
[ProtoContract]
public class SampleClassDrv : SampleClass
{
[ProtoMember(1)] public int theOtherInt;
public SampleClassDrv(){}
public SampleClassDrv(int b):base(1){this.theOtherInt=b;}
}
To compile my DLL I run the following code:
RuntimeTypeModel rModel = TypeModel.Create();
rModel.AllowParseableTypes = true;
rModel.AutoAddMissingTypes = true;
rModel.Add(typeof(SampleClass), true);
rModel.Add(typeof(SampleClassDrv), true);
rModel.Compile("MySerializer", "MySerializer.dll");
Finally I should be able to initialize by RuntimeTypeModel from the dll like so:
MySerializer serializer = new MySerializer();
serializer.Serialize(stream, object);
But Unity throws the following exception
Internal compiler error. See the console log for more information.
[...]
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
Interestingly enough if I go back and remove the line
[ProtoMember(2)] public string[] items;
It works as expected...
It is also worth noting the RunTimeModel works as expected if used after adding the classes instead of attempting to use dll.
My environment:
Unity3D 4.3.4
Protobuf-net r668
using protbuf-net.dll in Full/unity
I would greatly appreciate if someone could point out the error in my ways.
EDIT
From the suggestion by Flamy I changed form string[] to List
[ProtoMember(2)] public List<string> items;
Sadly the error still persists.
Another Note
Also I decided to use a dll decompiler to see what is going on. I was not able to decompile the dll until the "string[] items" variable was removed.
SOLVED
I think it is related to some issue with compiling the DLL with Unity3D.
When I created the project in Visual Studios with the code I showed above everything seems to be working as expected. Which is a relief as this seems like it would be a huge issue if protobuf could not serialize string[].
I followed the article provided by BCCode to setup the visual studio project and compile the DLLs.
Now all I need to do is create the dll with my large scale project! Fingers Crossed
Thanks everyone for their help!
Are your references correct?
%project dir%\Library\ScriptAssemblies\Assembly-CSharp.dll
Also take a look here:
http://purdyjotut.blogspot.com/2013/10/using-protobuf-in-unity3d.html?m=1
Serilizing array of strings using Binary serilization or a serilization method that uses binary format (protobuff in this case) will always result in so many issues. The reason is String by itself is an array of chars,which doesnt have a defined size, meaning it dont know where one string ends and the next starts... Usually we serialize string array one by one instead of the whole array. so I advice you to to use a property which does that (hopefully protobuff accepts attributes on properties!) or create a list of strings instead (though i haven't tested yet it should work!)

How to Deserialize and Serialize Build Process Parameters in TFS

I am trying to create a new build definition using TFS 2013 API. The process template that I have to refer contains several custom activities and parameters. While creating the Build Definition, some of the Property value needs to be updated dynamically. So I tried to Deserialize the process parameters using below code:
IDictionary<string, object> processParams = WorkflowHelpers.DeserializeProcessParameters(defaultTemplate.Parameters);
This code always throwing below exception:
An unhandled exception of type 'System.Xaml.XamlObjectWriterException' occurred in System.Xaml.dll
Additional information: No matching constructor found on type 'System.Activities.Activity'. You can use the Arguments or FactoryMethod directives to construct this type.
This is really frustrating and I can't get rid of this error yet.
I have also tried to deserialize the process parameters using below code:
using (StringReader stringReader = new StringReader(parameterValues))
{
object obj = XamlServices.Load(
ActivityXamlServices.CreateReader(
new XamlXmlReader((TextReader)stringReader, new XamlXmlReaderSettings { LocalAssembly = System.Reflection.Assembly.GetExecutingAssembly() }
)));
}
This works but when I serialize it again using XamlServices.Save(XamlWriter writer, object instance) method, the parameters got changed which is not compatible with build workflow.
So, how can I update the build process parameters here ? Can the WorkflowHelpers class can be used in other ways ? Or is there any other way to update the process parameters so that it gets reflected in the build definition. Any help is highly appreciated.
This is working fine now.
Below is new code:
IDictionary<string, object> processParams = WorkflowHelpers.DeserializeProcessParameters(defaultTemplate.ProcessParameters);
Instead of defaultTemplate.Parameters, I need to pass defaultTemplate.ProcessParameters

Executable fails with weird exception

I am using ILMerge and Quartz.NET in a C# .NET 4.0 Windows Service application. The app runs fine without using ILMerge, but now that we're nearing shipping release, I wanted to combine all DLLs into a single executable.
Problem is, that ILMerge seems to work fine, but when I run the combined executable, it throws this exception:
Unhandled Exception: Quartz.SchedulerException: ThreadPool type 'Quartz.Simpl.SimpleThreadPool' could not be instantiated. ---> System.InvalidCastException: Unable to cast object of type 'Quartz.Simpl.SimpleThreadPool' to type 'Quartz.Spi.IThreadPool'.
at Quartz.Util.ObjectUtils.InstantiateType[T](Type type) in :line 0
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
--- End of inner exception stack trace ---
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
at Quartz.Impl.StdSchedulerFactory.GetScheduler() in :line 0
Does anyone have any idea why this is? I have been wasting over 4 hours already and I can't figure it out. If I don't combine with ILMerge, then everything runs fine (with the Quartz.dll and Common.Logging.dll in the same directory).
I'm sure someone must have tried packaging Quartz.net up like this before, any ideas?
Disclaimer: I don't know Quartz.NET at all, although I spent some time struggling with ILMerge. When I finally understood its limitations... I stopped using it.
ILMerge'd application tends to have problems with everything which contains the word "reflection".
I can guess (I've never used Quartz.NET) that some classes are resolved using reflection and driven by configuration files.
Class is not only identified by its name (with namespace) but also by assembly it is coming from (unfortunatelly it doesn't get displayed in exception message).
So, let's assume you had (before ILMerging) two assemblies A (for you Application) and Q (for Quartz.NET).
Assembly 'A' was referencing assembly 'Q' and was using a class 'Q:QClass' which was implementing 'Q:QIntf'.
After merging, those classes became 'A:QClass' and 'A:QIntf' (they were moved from assembly Q to A) and all the references in code has been replaced to use those (completely) new classes/interfaces, so "A:QClass" is implementing "A:QIntf" now.
But, it did not change any config files/embedded strings which may still reference "Q:QClass".
So when application is reading those not-updated config files it still loads "Q:QClass" (why it CAN find it is a different question, maybe you left assembly 'Q' in current folder or maybe it is in GAC - see 1).
Anyway, "Q:QClass" DOES NOT implement "A:QIntf", it still implements "Q:QIntf" even if they are binary identical - so you can't cast 'Q:QClass' to 'A:QIntf'.
The not-ideal-but-working solution is to "embed" assemblies instead of "merging" them. I wrote a open-source tool which does it (embedding instead of merging) but it is not related to this question. So if you decide to embed just ask me.
You can test it by removing (hiding, whatever works for you) every single instance of Q.dll on your PC. If I'm right, the exception should say now 'FileNotFound'.
You could try creating your own ISchedulerFactory and avoid using reflection to load all of your types.
The StdSchedulerFactory uses this code to creat a threadpool. It's where your error is happening and would be the place to start looking at making changes:
Type tpType = loadHelper.LoadType(cfg.GetStringProperty(PropertyThreadPoolType)) ?? typeof(SimpleThreadPool);
try
{
tp = ObjectUtils.InstantiateType<IThreadPool>(tpType);
}
catch (Exception e)
{
initException = new SchedulerException("ThreadPool type '{0}' could not be instantiated.".FormatInvariant(tpType), e);
throw initException;
}
The ObjectUtils.InstantiateType method that is called is this one, and the last line is the one throwing your exception:
public static T InstantiateType<T>(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type", "Cannot instantiate null");
}
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
if (ci == null)
{
throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
}
return (T) ci.Invoke(new object[0]);
}
Right after this section in the factory, datasources are loaded using the same pattern and then the jobs themselves are also loaded dynamically which means you'd also have to write your own JobFactory. Since Quartz.Net loads a bunch of bits and pieces dynamically at runtime going down this road means you might end up rewriting a fair amount of things.

Clipboard behaves differently in .NET 3.5 and 4, but why?

We recently upgraded a very large project from .NET framework 3.5 to 4, and initially everything seemed to work the same. But now bugs have started to appear on copy paste operations.
I have managed to make a small reproducible app, which shows the different behavior in .NET 3.5 and 4.
I have also found a workaround (manually serialize the data to the clipboard), but I'm left with a need to know "why" there is a difference in behavior.
This is the small test app I made:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
namespace ClipboardTest
{
public class Program
{
[Serializable]
public class Element
{
public Element(string name)
{
this.name = name;
}
public string name;
}
public static List<Element> TestSerializer(List<Element> obj)
{
var memoryStream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, obj);
return (List<Element>)formatter.Deserialize(new MemoryStream(memoryStream.GetBuffer()));
}
public static List<Element> TestClipboard(List<Element> obj)
{
Clipboard.SetDataObject(obj);
return (List<Element>)Clipboard.GetDataObject().GetData(typeof(List<Element>));
}
public static void DumpObject(string testName, List<Element> obj)
{
if (obj == null)
{
Console.WriteLine("{0} : List is null", testName);
return;
}
foreach (var prop in obj)
{
Console.WriteLine("{0} : {1}", testName, prop.name);
}
}
[STAThread]
static void Main()
{
var copyData = new List<Element> { new Element("all good") };
DumpObject("Serializer", TestSerializer(copyData));
DumpObject("Clipboard", TestClipboard(copyData));
}
}
}
.NET 3.5 output:
Serializer : all good
Clipboard : all good
.NET 4 output:
Serializer : all good
Clipboard : List is null
I have looked at the .NET source for the Clipboard & DataObject class, but I couldn't see what serializer was used. The MSDN documentation says that the type must be serializable, which in this case both the List<> and Element classes are. Copying an Element object works just fine, but as soon as I copy a list of elements, it breaks.
To test, I have created 2 C# "Console Application" projects in Visual Studio 2010 SP1. The first project I have left with the default "Target framework" setting of ".NET Framework 4 Client Profile". The second project I have modified to use ".NET Framework 3.5 Client Profile".
Additional information about my Forms DLL version:
Original filename: System.Windows.Forms.dll
File version/Prouct version : 4.0.30319.235
Language: English (United States)
Date modified: 16-02-2012 22:50
I repro. You can get more insight into the bug with Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. That will stop the program when an internal exception is thrown by the clipboard code in the framework. The IDataObject.GetDataHere() implementation method fails with a COM exception, "Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))".
There is something wrong with the format. That becomes clear when you set a breakpoint after the Clipboard.SetDataObject(obj) statement. And put Clipboard.GetDataObject().GetFormats() in a debugger watch expression. I see:
"System.Collections.Generic.List`1[[ClipboardTest.Program+Element, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, Public"
Note how the string is truncated, the PublicKeyToken part got mangled. You can arbitrarily alter this truncated string by changing the namespace name and the project name. Make them short enough and the program won't fail.
Clearly this is the cause of the problem. The string length is clipped to 127 characters, any type whose full name is longer than that is going to cause this failure. With a high likelihood that this will be a generic type since they have very long names.
Please report this bug at connect.microsoft.com. Your code demonstrates the bug very well, just posting a link to it in your bug report will be sufficient. I don't have a very good workaround, ensuring the name is short enough is not very practical. But you can with code like this:
// Put it on the clipboard, use a wrapper type with a short name
var envelope = new List<object>();
envelope.AddRange(obj);
Clipboard.SetDataObject(envelope);
// Retrieve from clipboard, unwrap back to original type
envelope = (List<object>)Clipboard.GetDataObject().GetData(typeof(List<object>));
var retval = new List<Element>();
retval.AddRange(envelope.Cast<Element>());
return retval;
UPDATE: this bug is reported fixed in VS2013.

Categories