BinaryFormatter - можно ли десериализовать известный класс без сборки?
В настоящее время я пытаюсь взаимодействовать с программой, которая отправляет данные по сети после первого форматирования с помощью BinaryFormatter в C#. Это глупая идея, и я ненавижу ее, но я должен взаимодействовать с ней.
Я знаю, как выглядит тип, я знаю, что это точное расположение. Но я не могу добавить ссылку на эту конкретную сборку в моей программе по разным причинам.
Учитывая, насколько тесно связан BinaryFormatter с конкретным типом / версией, я не могу найти способ заставить его десериализоваться, несмотря на знание структуры данных.
В настоящее время я смотрю либо на создание поддельной сборки со всеми правильными атрибутами, и на попытку связать ее (кажется, действительно грязно), или на попытку вручную пройти через двоичный поток и извлечь значения, которые я ищу (я глядя на документацию MS по этому вопросу, и это ясно, как грязь, как для макета).
Любые другие великие идеи, или кто-то имел успехи с этим в прошлом? Кажется, что я знаю всю информацию, которая мне нужна, и BinaryFormatter просто заведомо хрупок по этому поводу.
редактировать:
Чтобы ответить на вопрос ниже (что является хорошим моментом, кстати), есть несколько причин.
Чистота проекта. Добавление 5-мегабайтной ссылки на файл.exe, который является внешним для одной функции, немного нецелесообразно.
У единиц оборудования, с которыми я взаимодействую, есть различные версии, развернутые по всему миру. Внутренняя структура данных для элемента, который мне нужен, одинакова во всех из них, но версии сборки различаются, что приводит к поломке в BinaryFormatter. Я мог бы преобразовать двоичный поток в строку, найти номер версии и загрузить нужную версию, но теперь у меня есть дюжина.exe-файлов, ожидающих загрузки нужной версии. Тогда эта схема не очень пригодна для будущего (ну, эта схема не совсем пригодна для будущего, но я бы по крайней мере хотел бы абстрагироваться от некоторой хрупкости BinaryFormatter, чтобы облегчить мою работу). Просто написание этого ответа заставило меня задуматься об использовании emit или аналогичных программ для создания собственной сборки на лету... но, чувак, должен быть более простой способ, верно? Я буквально ищу пару bools в структуре данных среднего размера.
Переменные объекта предоставляются через свойства get / set, которые имеют некоторую логику, и пытаются выполнять вызовы функций и обновлять другие объекты, которые могут отсутствовать на моей стороне (иначе, get получает мне нужное значение, но также вызывает уведомления, которые распространяются через связанную зависимость, и я могу получить исключения, всплывающие в моем приложении. Поговорим о запахе кода!). Это превращается в кроличью нору зависимости / реализации.
edit2: Производитель работает со мной, чтобы сделать их систему лучше, но когда мы рекламируем "Работает с X", мы хотели бы, чтобы он просто работал с X, а не требовал конкретных версий. Особенно с некоторыми из наших клиентских систем, которые строго контролируются версиями, и простое обновление приложения-нарушителя становится серьезной задачей.
2 ответа
Как вы предполагали в комментариях к вашему вопросу, SerializationSurrogate и SerializationBinder могут помочь вам в этом. Учитывая, что вас интересуют только несколько свойств, вы можете десериализовать в прокси-класс, который вы бы заполнили, перечисляя SerializationInfo, передаваемый в ваш SerializationSurrogate. К сожалению, это только поможет вам в этом.
Проблема в том, что доступ к свойствам не вызовет никаких побочных эффектов в сериализованном объекте, потому что вы получаете доступ к данным только с помощью SerializationSurrogate - ни один из двоичного кода не выполняется. Быстрый и грязный тестовый код ниже иллюстрирует проблему:
namespace BinaryProxy
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
class TestClass
{
public bool mvalue;
public TestClass(bool value)
{
BoolValue = value;
}
public bool BoolValue
{
get
{
// won't happen
SideEffect = DateTime.Now.ToString();
return mvalue;
}
set
{
mvalue = value;
}
}
public string SideEffect { get; set; }
}
class ProxyTestClass
{
private Dictionary<string, object> data = new Dictionary<string, object>();
public Object GetData(string name)
{
if(data.ContainsKey(name))
{
return data[name];
}
return null;
}
public void SetData(string name, object value)
{
data[name] = value;
}
public IEnumerable<KeyValuePair<string, object>> Dump()
{
return data;
}
}
class SurrogateTestClassConstructor : ISerializationSurrogate
{
private ProxyTestClass mProxy;
/// <summary>
/// Populates the provided <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the object.
/// </summary>
/// <param name="obj">The object to serialize. </param>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Populates the object using the information in the <see cref="T:System.Runtime.Serialization.SerializationInfo"/>.
/// </summary>
/// <returns>
/// The populated deserialized object.
/// </returns>
/// <param name="obj">The object to populate. </param>
/// <param name="info">The information to populate the object. </param>
/// <param name="context">The source from which the object is deserialized. </param>
/// <param name="selector">The surrogate selector where the search for a compatible surrogate begins. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
if (mProxy == null) mProxy = new ProxyTestClass();
var en = info.GetEnumerator();
while (en.MoveNext())
{
mProxy.SetData(en.Current.Name, en.Current.Value);
}
return mProxy;
}
}
sealed class DeserializeBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return typeof(ProxyTestClass);
}
}
static class Program
{
static void Main()
{
var tc = new TestClass(true);
byte[] serialized;
using (var fs = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(fs, tc);
serialized = fs.ToArray();
var surrSel = new SurrogateSelector();
surrSel.AddSurrogate(typeof(ProxyTestClass),
new StreamingContext(StreamingContextStates.All), new SurrogateTestClassConstructor());
using (var fs2 = new MemoryStream(serialized))
{
var formatter2 = new BinaryFormatter();
formatter2.Binder = new DeserializeBinder();
formatter2.SurrogateSelector = surrSel;
var deser = formatter2.Deserialize(fs2) as ProxyTestClass;
foreach (var c in deser.Dump())
{
Console.WriteLine("{0} = {1}", c.Key, c.Value);
}
}
}
}
}
}
В приведенном выше примере TestClass
' SideEffect
вспомогательное поле останется нулевым.
Если вам не нужны побочные эффекты и вы просто хотите получить доступ к значению некоторых полей, такой подход несколько жизнеспособен. В противном случае я не могу придумать другого жизнеспособного решения, кроме как следовать предложению Джона Скита.
Вы можете добавить дополнительный слой (или отдельный сервис), который может добавить ссылку на эту конкретную сборку? Если это так, вы можете заставить этот небольшой слой десериализовать двоичный формат и переформатировать его во что-то более переносимое (JSON, буферы протокола, XML, что угодно). Думайте об этом как об изоляции от безумия BinaryFormatter
:)