Windows drag drop between different applications - c#

I have two WinForms applications and want to be able to drag objects from one to the other.
My data object code is very simple:
// the data object
[ComVisible(true)]
[Serializable]
public class MyData : ISerializable {
public int Value1 { get; set; }
public int Value2 { get; set; }
public MyData() { }
public MyData(int value1, int value2) {
Value1 = value1;
Value2 = value2;
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(nameof(Value1), Value1);
info.AddValue(nameof(Value2), Value2);
}
}
The object is part of a dll, which is referenced in both of my WinForms applications.
I'm initializing the drag drop using:
// inside some control
MyData toBeTransmitted = new MyData(0, 0);
IDataObject dataObject = new DataObject(DataFormats.Serializable, toBeTransmitted);
this.DoDragDrop(dataObject, DragDropEffects.All);
and handling it using:
// inside some drag over handler
IDataObject dataObject = dragEvent.DataObject;
if (dataObject.GetDataPresent(DataFormats.Serializable)) {
object obj = e.DataObject.GetData(DataFormats.Serializable);
}
All this works fine, as long as I'm dragging and dropping data inside a single application.
But as soon as I drag data over from one process to the other retrieving the dragged data returns an object of type System.__ComObject instead of MyData.
How can I retrieve the actual data contained inside the IDataObject?
(note: I also tried using a custom format instead of DataFormats.Serializable, no luck there.)

The problem:
When DataFormats.Serializable is specified, the DataObject class uses a BinaryFormatter to serialize a class object that implement ISerializable (BTW, you should add a public MyData(SerializationInfo info, StreamingContext context) constructor).
The BinaryFormatter object is created using the standard form with a default Binder, which implies that the serialized object contains strict Assembly information.
As a consequence, you can Drag/Drop your object(s) to/from Processes that represent instances of the same Assembly without problem, but if the Assemblies are different or their version doesn't match, the BinaryFormatter fails deserialization and you get an unwrapped IComDataObject as result.
You could marshal this COM object yourself, which means you have to build a compatible FORMATETC struct object (System.Runtime.InteropServices.ComTypes.FORMATETC), get the STGMEDIUM from IDataObject.GetData([FORMATETC], out [STGMEDIUM]), get the IStream object using Marshal.GetObjectForIUnknown(), passing the [STGMEDIUM].unionmember pointer.
Then create a BinaryFormatter specifying a less restrictive / custom Binder and deserialize the Stream ignoring or replacing the Assembly name.
Before you ask, you cannot set the [SerializationInfo].AssemblyName (even though it's not read-only) directly in your ISerializable class, won't work.
A possible solution:
A simple approach is to replace the BinaryFormatter with a different serializer and create the IDataObject setting a custom format (or a predefined DataFormat compatible with the generated data).
An example using XmlSerializer as serializer and a MemoryStream:
The class object can be simplified, removing the ISerializable implementation:
[Serializable]
public class MyData {
public int Value1 { get; set; }
public int Value2 { get; set; }
public MyData() { }
public MyData(int value1, int value2)
{
Value1 = value1;
Value2 = value2;
}
public override string ToString()
{
return $"Value1: {Value1}, Value2: {Value2}";
}
}
Two static methods used to generate an IDataObject on the Source side and to extract its content on the Target side. XmlSerializer is used to serialize / deserialize the class object(s).
private static IDataObject SetObjectData<T>(object value, string format) where T : class
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms)) {
var serializer = new XmlSerializer(typeof(T), "");
serializer.Serialize(sw, value);
sw.Flush();
var data = new DataObject(format, ms.ToArray());
// Failsafe custom data type - could be a GUID, anything else, or removed entirely
data.SetData("MyApp_DataObjectType", format);
return data;
};
}
private static T GetObjectData<T>(IDataObject data, string format) where T : class
{
// Throws if the byte[] cast fails
using (var ms = new MemoryStream(data.GetData(format) as byte[])) {
var serializer = new XmlSerializer(typeof(T));
var obj = serializer.Deserialize(ms);
return (T)obj;
}
}
In the example, I'm using a Dictionary<string, Action> to call methods nased on the Type contained in the IDataObject received from the DragDrop operation.
This because I suppose you could transfer different Types. Of course you can use anything else.
You could also use a common Interface and just use this as <T>. It would simplify a lot the whole implementation (and future expansion, if generic enough methods and properties can be defined).
Dictionary<string, Action<IDataObject>> dataActions = new Dictionary<string, Action<IDataObject>>() {
["MyData"] = (data) => {
// The Action delegate deserialzies the IDataObject...
var myData = GetObjectData<MyData>(data, "MyData");
// ...and calls a method passing the class object
MessageBox.Show(myData.ToString());
},
["MyOtherData"] = (data) => {
var otherData = GetObjectData<MyOtherData>(data, "MyOtherData");
MessageBox.Show(otherData.ToString());
}
};
▶ On the Source side (Drag/Drop initiator):
Point mouseDownPos = Point.Empty;
private void SomeSourceControl_MouseDown(object sender, MouseEventArgs e)
{
mouseDownPos = e.Location;
}
private void SomeSourceControl_MouseMove(object sender, MouseEventArgs e)
{
MyData toBeTransmitted = new MyData(100, 100);
if (e.Button == MouseButtons.Left &&
((Math.Abs(e.X - mouseDownPos.X) > SystemInformation.DragSize.Width) ||
(Math.Abs(e.Y - mouseDownPos.Y) > SystemInformation.DragSize.Height))) {
var data = SetObjectData<MyData>(toBeTransmitted, "MyData");
DoDragDrop(data, DragDropEffects.All);
}
}
▶ On the Target side (Drag/Drop target)
private void SomeTargetControl_DragEnter(object sender, DragEventArgs e)
{
var formats = e.Data.GetFormats();
// Verify that a Data Type is defined in the Dictionary
if (formats.Any(f => dataActions.ContainsKey(f))) {
e.Effect = DragDropEffects.All;
}
}
private void SomeTargetControl_DragDrop(object sender, DragEventArgs e)
{
// Double check: the fail-safe Data Type is present
string dataType = (string)e.Data.GetData("MyApp_DataObjectType");
// If the Data Type is in the Dictionary, deserialize and call the Action
if (dataActions.ContainsKey(dataType)) {
dataActions[dataType](e.Data);
}
}

Related

How can I properly store an enum value with XmlSerializer (.NET) when the enum value is stored as object

I want to save an object containing a value that can basically be any type. I am using XmlSerializer to do that and it works fine with one exception: If the value is an enum, the serializer stores the value as integer. If I load it back and use that value to read from a dictionary I get a KeyNotFoundException.
Is there any elegant way to save the enum as enum or to avoid the KeyNotFoundException and still use XmlSerializer? (Casting back to enum is not a good option here, the container and dictionary must support all types)
Here is a simplified piece of code that demonstrates the problem:
public enum SomeEnum
{
SomeValue,
AnotherValue
}
// Adding [XmlInclude(typeof(SomeEnum))] is no proper solution as Key can be any type
public class GenericContainer
{
public object Key { get; set; }
}
private Dictionary<object, object> SomeDictionary = new Dictionary<object, object>();
public void DoSomething()
{
SomeDictionary[SomeEnum.AnotherValue] = 123;
var value = SomeDictionary[SomeEnum.AnotherValue];
Save(new GenericContainer { Key = SomeEnum.AnotherValue}, "someFile.xml");
var genericContainer = (GenericContainer)Load("someFile.xml", typeof(GenericContainer));
// Throws KeyNotFoundException
value = SomeDictionary[genericContainer.Key];
}
public void Save(object data, string filePath)
{
var serializer = new XmlSerializer(data.GetType());
using (var stream = File.Create(filePath))
{
serializer.Serialize(stream, data);
}
}
public object Load(string filePath, Type type)
{
var serializer = new XmlSerializer(type);
using (var stream = File.OpenRead(filePath))
{
return serializer.Deserialize(stream);
}
}
You can put attributes to your enum
public enum Simple
{
[XmlEnum(Name="First")]
one,
[XmlEnum(Name="Second")]
two,
[XmlEnum(Name="Third")]
three,
}
Original:
How do you use XMLSerialize for Enum typed properties in c#?

BinaryFormatter determine type of object in OnDeserializingMethod

I have a very complex csla object. This object has been serialized to Database in binary. One of the child objects changes and I cannot deserialize the object anymore. (When I try to deserialize it, the properties contain junk or are not initialized because the deserialization did'nt work in the new class.) I'll post a simplified example here.
[serializable]
public class Head : BusinessBase<Head>
{
private static PropertyInfo<int> _eyesCountProperty = RegisterProperty<int>(new PropertyInfo<int>("EyesCount"));
public int EyesCount
{
get { return GetProperty(_eyesCountProperty ); }
set { SetProperty(_eyesCountProperty , value); }
}
}
[serializable]
public class Person : BusinessBase<Person>
{
private static PropertyInfo<string> _firstNameProperty = RegisterProperty<string>(new PropertyInfo<string>("FirstName"));
public string FirstName
{
get { return GetProperty(_firstNameProperty ); }
set { SetProperty(_firstNameProperty , value); }
}
}
public Head MyHead { get; set; }
}
So let's say I have an instance of "Person" class and I serialize it to the database.
Here's our serializing and deserializing methods.
public static byte[] ConvertToByteArray(object theObject)
{
byte[] result = null;
BinaryFormatter bf = new BinaryFormatter();
using(MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, theObject);
result = ms.ToArray();
}
return result;
}
public static object ConvertFromByteArray(byte[] serializedObject)
{
object result = null;
using(MemoryStream ms = new MemoryStream(serializedObject))
{
BinaryFormatter bf = new BinaryFormatter();
result = bf.Deserialize(ms);
}
return result;
}
Now let's say that the Head class changes and I now have the noseCount and mouthCount in it. So I try to rename it as "HeadV1" and create a "HeadV2" class with the new properties in it. (I would need to do a "PersonV1" class with the "HeadV1" property and a "PersonV2" class with the "HeadV2".)
In the PersonV2 class, I would like to have an "OnDeserializing" method that would let me know if the item I'm trying to deserialize is of type "PersonV1" or "PersonV2" in order to deserialize it the right way.
[OnDeserializing()]
internal void OnDeserializingMethod(StreamingContext context)
{
// Determine if data is of type "PersonV1" or "PersonV2"
}
But I'm stuck, I don't know how to do it and I can't seem to find how to do it. Is there any way to do so? I don't seem to have access to the data inside the "OnDeserializing" method?

How do I use my own debugger visualiser to edit variables runtime?

I'm writing my own debugger visualiser. All works great to show up to visualiser with the data.
Now I add the code for more clearness:
public class MyVisualiserObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
string data= target as string;
var writer = new StreamWriter(outgoingData);
writer.Write(data);
writer.Flush();
}
}
public class MyVirtualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var streamReader = new StreamReader(objectProvider.GetData());
string data = streamReader.ReadToEnd();
using (var form = new MyVirtualizerForm(data))
{
windowService.ShowDialog(form);
}
}
}
The string here is passed to the visualizer and show my own form. It works.
But now I want to pass back the modified data from the form to the variable.
How do I do that?
Edit:
I found out that I need to override the TransferData method in VisualizerObjectSource. But in the MSDN is no detail information about how I implement this correctly.
Can someone help me please?
Edit 2:
I looked with IL-Spy what TransferData method does. It throws an exception.
So I override the method. But it is still not working. In the incomingData is the modified string from the Form. But I do not get back this value into the variable :(
public class StringVisualiserObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var data = target as string;
var writer = new StreamWriter(outgoingData);
writer.Write(data);
writer.Flush();
}
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
incomingData.CopyTo(outgoingData);
}
}
You just need to add a public property to your form that contains the data you wish to "pass back". For example, if your form contains a textbox called MyDataTextBox, you need to create a public property on your form like:
public MyVirtualizerForm : System.Windows.Form
{
public string MyData
{
get{ return MyDataTextBox.Text;}
}
}
You can then get access to the text of the textbox when the form is closed by doing the following:
Edited - Passing the data back to the variable
This assumes that the data you are getting back from the form is a string.
using (var form = new MyVirtualizerForm(data))
{
windowService.ShowDialog(form);
var formData = form.MyData;
using (MemoryStream returnStream =
new MemoryStream(ASCIIEncoding.Default.GetBytes(formData)))
{
objectProvider.TransferData(returnStream);
}
}
Is this what you want to achieve? Also, you may find the following link useful as reference: http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.debuggervisualizers.ivisualizerobjectprovider.transferdata.aspx

How to pass custom data to deserialization function

I'm doing some C# IO work and I want to import/export data from a few classes.
After looking for a while, it seems serialization is pretty close to what I want.
However, there is a problem. I have a XML file that describes members of a certain class(which I will refer as ValueWithId), which are aggregated in a class which I will call CollectionOfValuesWithId for the purposes of this question.
ValueWithId is a class that contains a string member called ShortName, which is unique. There is only one ShortName per ValueWithId and all ValueWithId have a non-null ShortName. CollectionOfValuesWithId contains a function to find the ValueWithId with a given ShortName.
When serializing, I do NOT want to store ValueWithId nor CollectionOfValuesWithId in the output file. Instead, I just want to store the ShortName in the file.
So far, so good. I just need to use SerializationInfo.AddValue("ValueWithId", MyValueWIthId.ShortName).
The problem comes with deserialization. Some googling suggests that to read data from a file one would do this:
public SomeClassThatUsesValueWithId(SerializationInfo info, StreamingContext ctxt)
{
string name = (string)info.GetValue("ValueWithId", typeof(string));
}
However, the string is not enough to recover the ValueWithId instance. I also need the CollectionOfValuesWithId. I want something like this:
public SomeClassThatUsesValueWithId(SerializationInfo info,
StreamingContext ctxt, CollectionOfValuesWithId extraParameter)
In other words, I need to pass extra data to the deserialization constructor. Does anyone know any way to do this or any alternatives?
I figured it out. The important class to do this is StreamingContext.
This class conveniently has a property named Context(which can be set in the constructor parameter additional.
From MSDN:
additional
Type: System.Object
Any additional information to be associated with the
StreamingContext. This information is available to any object that
implements ISerializable or any serialization surrogate. Most users do
not need to set this parameter.
So here is some sample code regarding how to do this(tested on Mono, but I think it should work on Windows):
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class ValueWithId
{
public string ShortName;
public string ActualValue;
public ValueWithId(string shortName, string actualValue)
{
ShortName = shortName;
ActualValue = actualValue;
}
public override string ToString()
{
return ShortName + "->" + ActualValue;
}
}
public class CollectionOfValuesWithId
{
private IList<ValueWithId> Values = new List<ValueWithId>();
public void AddValue(ValueWithId val)
{
Values.Add(val);
}
public ValueWithId GetValueFromId(string id)
{
foreach (var value in Values)
if (value.ShortName == id)
return value;
return null;
}
}
[Serializable]
public class SomeClassThatUsesValueWithId : ISerializable
{
public ValueWithId Val;
public SomeClassThatUsesValueWithId(ValueWithId val)
{
Val = val;
}
public SomeClassThatUsesValueWithId(SerializationInfo info, StreamingContext ctxt)
{
string valId = (string)info.GetString("Val");
CollectionOfValuesWithId col = ctxt.Context as CollectionOfValuesWithId;
if (col != null)
Val = col.GetValueFromId(valId);
}
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
//Store Val.ShortName instead of Val because we don't want to store the entire object
info.AddValue("Val", Val.ShortName);
}
public override string ToString()
{
return "Content="+Val;
}
}
class MainClass
{
public static void Main(string[] args)
{
CollectionOfValuesWithId col = new CollectionOfValuesWithId();
col.AddValue(new ValueWithId("foo", "bar"));
SomeClassThatUsesValueWithId sc = new SomeClassThatUsesValueWithId(col.GetValueFromId("foo"));
BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File, col));
using (var stream = new FileStream("foo", FileMode.Create))
{
bf.Serialize(stream, sc);
}
col.GetValueFromId("foo").ActualValue = "new value";
using (var stream2 = new FileStream("foo", FileMode.Open))
{
Console.WriteLine(bf.Deserialize(stream2));
}
}
}
The output I get is:
Content=foo->new value
Which is exactly what I wanted.

How can I send multiple types of objects across Protobuf?

I'm implementing a client-server application, and am looking into various ways to serialize and transmit data. I began working with Xml Serializers, which worked rather well, but generate data slowly, and make large objects, especially when they need to be sent over the net. So I started looking into Protobuf, and protobuf-net.
My problem lies in the fact that protobuf doesn't sent type information with it. With Xml Serializers, I was able to build a wrapper which would send and receive any various (serializable) object over the same stream, since object serialized into Xml contain the type name of the object.
ObjectSocket socket = new ObjectSocket();
socket.AddTypeHandler(typeof(string)); // Tells the socket the types
socket.AddTypeHandler(typeof(int)); // of objects we will want
socket.AddTypeHandler(typeof(bool)); // to send and receive.
socket.AddTypeHandler(typeof(Person)); // When it gets data, it looks for
socket.AddTypeHandler(typeof(Address)); // these types in the Xml, then uses
// the appropriate serializer.
socket.Connect(_host, _port);
socket.Send(new Person() { ... });
socket.Send(new Address() { ... });
...
Object o = socket.Read();
Type oType = o.GetType();
if (oType == typeof(Person))
HandlePerson(o as Person);
else if (oType == typeof(Address))
HandleAddress(o as Address);
...
I've considered a few solutions to this, including creating a master "state" type class, which is the only type of object sent over my socket. This moves away from the functionality I've worked out with Xml Serializers, though, so I'd like to avoid that direction.
The second option would be to wrap protobuf objects in some type of wrapper, which defines the type of object. (This wrapper would also include information such as packet ID, and destination.) It seems silly to use protobuf-net to serialize an object, then stick that stream between Xml tags, but I've considered it. Is there an easy way to get this functionality out of protobuf or protobuf-net?
I've come up with a third solution, and posted it below, but if you have a better one, please post it too!
Information on field bounds bug (using System.String):
Hashing:
protected static int ComputeTypeField(Type type) // System.String
{
byte[] data = ASCIIEncoding.ASCII.GetBytes(type.FullName);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
return Math.Abs(BitConverter.ToInt32(md5.ComputeHash(data), 0));
}
Serialization:
using (MemoryStream stream = new MemoryStream())
{
Serializer.NonGeneric.SerializeWithLengthPrefix
(stream, o, PrefixStyle.Base128, field); // field = 600542181
byte[] data = stream.ToArray();
_pipe.Write(data, 0, data.Length);
}
Deserializaion:
using (MemoryStream stream = new MemoryStream(_buffer.Peek()))
{
lock (_mapLock)
{
success = Serializer.NonGeneric.TryDeserializeWithLengthPrefix
(stream, PrefixStyle.Base128, field => _mappings[field], out o);
}
if (success)
_buffer.Clear((int)stream.Position);
else
{
int len;
if (Serializer.TryReadLengthPrefix(stream, PrefixStyle.Base128, out len))
_buffer.Clear(len);
}
}
field => _mappings[field] throws a KeyNotFoundException while looking for 63671269.
If I replace ToInt32 with ToInt16 in the hash function, the field value is set to 29723 and it works. It also works if I explicitly define System.String's field to 1. Explicitly defining the field to 600542181 has the same effect as using the hash function to define it. The value of the string being serialized does not change the outcome.
This functionality is actually built in, albeit not obviously.
In this scenario, it is anticipated that you would designate a unique number per message type. The overload you are using passes them all in as "field 1", but there is an overload that lets you include this extra header information (it is still the job of the calling code to decide how to map numbers to types, though). You can then specify different types as different fields is the stream (note: this only works with the base-128 prefix style).
I'll need to double check, but the intention is that something like the following should work:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ProtoBuf;
static class Program
{
static void Main()
{
using (MemoryStream ms = new MemoryStream())
{
WriteNext(ms, 123);
WriteNext(ms, new Person { Name = "Fred" });
WriteNext(ms, "abc");
ms.Position = 0;
while (ReadNext(ms)) { }
}
}
// *** you need some mechanism to map types to fields
static readonly IDictionary<int, Type> typeLookup = new Dictionary<int, Type>
{
{1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)}
};
static void WriteNext(Stream stream, object obj) {
Type type = obj.GetType();
int field = typeLookup.Single(pair => pair.Value == type).Key;
Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field);
}
static bool ReadNext(Stream stream)
{
object obj;
if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj))
{
Console.WriteLine(obj);
return true;
}
return false;
}
}
[ProtoContract] class Person {
[ProtoMember(1)]public string Name { get; set; }
public override string ToString() { return "Person: " + Name; }
}
Note that this doesn't currently work in the v2 build (since the "WithLengthPrefix" code is incomplete), but I'll go and test it on v1. If it works, I'll all the above scenario to the test suite to ensure it does work in v2.
Edit:
yes, it does work fine on "v1", with output:
123
Person: Fred
abc
I've come up with another solution, but I decided to put it as an answer, instead of in the question, because that makes more sense to me. It's pretty ugly, in my opinion, and I've been warned against using reflection, so please comment on it or provide better answers if you have them. Thanks!
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
Id = 12345,
Name = "Fred",
Address = new Address
{
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
object value;
using (Stream stream = new MemoryStream())
{
Send<Person>(stream, person);
stream.Position = 0;
value = Read(stream);
person = value as Person;
}
}
static void Send<T>(Stream stream, T value)
{
Header header = new Header()
{
Guid = Guid.NewGuid(),
Type = typeof(T)
};
Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
}
static object Read(Stream stream)
{
Header header;
header = Serializer.DeserializeWithLengthPrefix<Header>
(stream, PrefixStyle.Base128);
MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix",
new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type);
Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} );
return value;
}
}
[ProtoContract]
class Header
{
public Header() { }
[ProtoMember(1, IsRequired = true)]
public Guid Guid { get; set; }
[ProtoIgnore]
public Type Type { get; set; }
[ProtoMember(2, IsRequired = true)]
public string TypeName
{
get { return this.Type.FullName; }
set { this.Type = Type.GetType(value); }
}
}
[ProtoContract]
class Person {
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public Address Address { get; set; }
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 { get; set; }
[ProtoMember(2)]
public string Line2 { get; set; }
}

Categories