
Забегая вперёд стоит отметить, что сериализация является частью маршалинга.
Сериализация
Сериализация объекта – запись состояния объекта в байтовый поток таким образом, чтобы его можно было преобразовать обратно в копию исходного объекта.
Сериализация может применяется для любого объекта только в рамках одного процесса (одной программы).
Маршалинг
Абстрактно говоря, маршалинг – процесс передачи информации между управляемым и неуправляемым кодом.
Маршалинг объекта – запись состояние объекта и его кодовой базы таким образом, чтобы была была возможность получить копию объекта путём загрузки определений класса исходного объекта. Можно утверждать, что сериализация – часть маршалинга.
Кодовая база – информация о том, где можно найти реализация какого-либо объекта.
Процесс маршалинга возможно применить либо к сериализуемым объектам, либо к объектам, которые наследуются от класса MarshalByRefObject.
Маршалинг может применяется как в рамках одного процесса, так и в рамках нескольких процессов/потоков/машин, а также при использовании различных RPC механизмов и P/Invoke.
Резюмирую – маршалинг используется в следующих случаях:
Передача данных между приложениями или процессами
Передача данных между различными языками программирования
Использование библиотек, написанных на других языках программирования (например, использование C++ библиотек в C# проекте)
В данном случае рассмотрим примеры использования маршалинга в следующих случаях:
P/Invoke между C# и C++
Взаимодействие с удалёнными объектами
P/Invoke (Platform Invoke) между C# и C++
Работать с P/Invoke возможно как в .Net Framework, так и в .Net Core.
P/Invoke – механизм, позволяющий использовать функции из неуправляемого кода (например, из DLL библиотек), используя управляемый код.
Кроме того, P/Invoke нужен для использования функционала существующих фреймворков и инструментов, написанных на C++ вместо того, чтобы переписывать код с C++ на C#.
На эту тему уже написано множество вариативных статей, в которых рассматриваются примеры использования P/Invoke для маршалинга структур (одна из них).
Поэтому, в рамках данной статьи приведу пример P/Invoke для одного относительно известного open-source проекта – opencv.net. Этот проект предназначен для использование кода библиотеки OpenCV (которая написана на C++) в C# проектах, что точь в точь отражает рассматриваемые способы маршалинга.
Взаимодействие с удалёнными объектами
На данный момент взаимодействие с удалёнными объектами доступно только в .Net Framework.
Для создания удалённого объекта мы можем использовать технологию .Net Remoting, которая позволяет создавать объекты, работающие в разных процессах или на разных компьютерах.
Маршалинг в .Net Remoting происходит автоматически при вызове методов удалённого объекта через прокси-объект. Прокси-объект выполняет сериализацию параметров метода, отправляет их на сервер, получает результат выполнения метода и десериализует его – для этого используется форматтер по умолчанию – BinaryFormatter.
Для работы с удалёнными объектами используются следующие пространства имён:
System.Runtime.Remoting.*
System.StubHelpers
Для взаимодействия между приложениями по умолчанию существуют следующие варианты:
HttpChannel
TcpChannel
IpcChannel
Для взаимодействия с каналами нужно добавить:
Nuget-пакет Microsoft.NETFramework.ReferenceAssemblies
Ссылку на System.Runtime.Remoting.dll
MarshalByRefObject – базовый класс для объектов, которые нужно адресовать по ссылке. Использование этого класса означает то, что экземпляры классов, которые наследуются от него могут быть маршалированы через границы домена приложений.
В данном случае приведу простой пример работы с удалёнными объектами через TCP протокол.
Для Начала создадим удалённый объект:
public interface IRandomNumberGenerator { int GenerateRandomNumber(); } public class RandomNumberGenerator : MarshalByRefObject, IRandomNumberGenerator { private readonly Random _random = new Random(); public int GenerateRandomNumber() { return _random.Next(); } }
Создадим пример сервера:
Создаём и регистрируем TCP канал (или любой из вышеописанных каналов).
Устанавливаем настройки порта и форматтера.
Создаём экземпляр объекта, который наследуется от
MarshalByRefObject.Регистрируем удалённый объект на сервере.
public static void Main(string[] args) { var serverSinkProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = TypeFilterLevel.Full }; var clientSinkProvider = new BinaryClientFormatterSinkProvider(); var properties = new Hashtable { ["port"] = 12345 }; // Создание TCP канала var tcpChannel = new TcpChannel(properties, clientSinkProvider, serverSinkProvider); // Регистрация канала на сервере ChannelServices.RegisterChannel(tcpChannel, ensureSecurity: false); var randomNumberGenerator = new RandomNumberGenerator(); // Регистрация удалённого объекта на сервере RemotingServices.Marshal(randomNumberGenerator, URI: "RandomNumberGenerator.rem"); Console.WriteLine($"Channel Name: {tcpChannel.ChannelName}"); Console.WriteLine($"Channel Priority: {tcpChannel.ChannelPriority}"); var channelDataStore = (ChannelDataStore)tcpChannel.ChannelData; foreach (var uri in channelDataStore.ChannelUris) { Console.WriteLine(uri); } Console.WriteLine("Server started. Press any key to stop."); Console.ReadKey(); // Отключение удалённого объекта RemotingServices.Disconnect(randomNumberGenerator); // Остановка канала ChannelServices.UnregisterChannel(tcpChannel); }
Помимо метода RemotingServices.Marshal() удалённый объект может быть зарегистрирован с помощью метода RemotingConfiguration.RegisterWellKnownServiceType():
RemotingConfiguration.RegisterWellKnownServiceType( typeof(RandomNumberGenerator), objectUri: "RandomNumberGenerator.rem", WellKnownObjectMode.SingleCall);
Кроме того, зарегистрировать удалённый объект и определить необходимое поведение можно в конфигурационном файле приложения:
Файл конфигурации сервера
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" /> </startup> <runtime> <AllowDComReflection enabled="true" /> </runtime> <system.runtime.remoting> <application> <service> <wellknown mode="SingleCall" type="RandomNumberGenerator, RandomNumberGenerator" objectUri="RandomNumberGenerator.rem" /> </service> <channels> <channel ref="tcp" port="12345" /> </channels> </application> </system.runtime.remoting> </configuration>
Файл конфигурации клиента
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1"/> </startup> <runtime> <AllowDComReflection enabled="true" /> </runtime> <system.runtime.remoting> <application> <client> <wellknown type="RandomNumberGenerator, RandomNumberGenerator" url="tcp://localhost:12345/RandomNumberGenerator.rem" /> </client> </application> </system.runtime.remoting> </configuration>
Для загрузку конфигурации используется метод RemotingConfiguration.Configure().
Создадим пример клиента:
Для взаимодействия с удалённым объектом нужно создать прокси-объект, используя метод RemotingServices.Unmarshal(). Данный метод можно вызывать 2 способами:
Через RemotingServices –
RemotingServices.Connect()Через Активатор –
Activator.GetObject()
public static void Main(string[] args) { // Создание TCP канал var tcpChannel = new TcpChannel(); // Регистрируем канал на сервере ChannelServices.RegisterChannel(tcpChannel, ensureSecurity: false); RemotingConfiguration.CustomErrorsMode = CustomErrorsModes.On; var randomNumberGenerator = (IRandomNumberGenerator)RemotingServices.Connect( classToProxy: typeof(IRandomNumberGenerator), url: "tcp://localhost:12345/RandomNumberGenerator.rem"); var randomNumber = randomNumberGenerator.GenerateRandomNumber(); Console.WriteLine($"Random number = {randomNumber}"); Console.ReadKey(); }
Порядок выполнения приведённого примера кода:
Запустить приложение-сервер, чтобы создать и зарегистрировать удалённый объект.
Запустить приложение-клиент, чтобы создать прокси-объект и вызывать методы удалённого объекта через него.
После выполнения метода удалённого объекта, проси-объект будет автоматически удалён, а соединение между клиентом и сервером разорвано. Если попытаться вызвать метод удалённого объекта через этот же прокси-объект ещё раз – получим исключение.
Маршалинг в .Net Remoting происходит автоматически при вызове методов удалённого объекта через прокси-объект. Прокси-объект выполняет сериализацию параметров метода, отправляет их на сервер, получает результат выполнения метода и десериализует его. Для этого используется "форматтер" по умолчанию – BinaryFormatter.
