Старый добрый маршалинг. Обзор

от автора

Забегая вперёд стоит отметить, что сериализация является частью маршалинга.

Сериализация

Сериализация объекта – запись состояния объекта в байтовый поток таким образом, чтобы его можно было преобразовать обратно в копию исходного объекта.

Сериализация может применяется для любого объекта только в рамках одного процесса (одной программы).

Маршалинг

Абстрактно говоря, маршалинг – процесс передачи информации между управляемым и неуправляемым кодом.

Маршалинг объекта – запись состояние объекта и его кодовой базы таким образом, чтобы была была возможность получить копию объекта путём загрузки определений класса исходного объекта. Можно утверждать, что сериализация – часть маршалинга.

Кодовая база – информация о том, где можно найти реализация какого-либо объекта.

Процесс маршалинга возможно применить либо к сериализуемым объектам, либо к объектам, которые наследуются от класса MarshalByRefObject.

Маршалинг может применяется как в рамках одного процесса, так и в рамках нескольких процессов/потоков/машин, а также при использовании различных RPC механизмов и P/Invoke.

Резюмирую – маршалинг используется в следующих случаях:

  1. Передача данных между приложениями или процессами

  2. Передача данных между различными языками программирования

  3. Использование библиотек, написанных на других языках программирования (например, использование C++ библиотек в C# проекте)

В данном случае рассмотрим примеры использования маршалинга в следующих случаях:

  1. P/Invoke между C# и C++

  2. Взаимодействие с удалёнными объектами

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.

Для работы с удалёнными объектами используются следующие пространства имён:

  1. System.Runtime.Remoting.*

  2. System.StubHelpers

Для взаимодействия между приложениями по умолчанию существуют следующие варианты:

  1. HttpChannel

  2. TcpChannel

  3. IpcChannel

Для взаимодействия с каналами нужно добавить:

  1. Nuget-пакет Microsoft.NETFramework.ReferenceAssemblies

  2. Ссылку на 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(); } }

Создадим пример сервера:

  1. Создаём и регистрируем TCP канал (или любой из вышеописанных каналов).

  2. Устанавливаем настройки порта и форматтера.

  3. Создаём экземпляр объекта, который наследуется от MarshalByRefObject.

  4. Регистрируем удалённый объект на сервере.

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 способами:

  1. Через RemotingServices – RemotingServices.Connect()

  2. Через Активатор – 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(); }

Порядок выполнения приведённого примера кода:

  1. Запустить приложение-сервер, чтобы создать и зарегистрировать удалённый объект.

  2. Запустить приложение-клиент, чтобы создать прокси-объект и вызывать методы удалённого объекта через него.

После выполнения метода удалённого объекта, проси-объект будет автоматически удалён, а соединение между клиентом и сервером разорвано. Если попытаться вызвать метод удалённого объекта через этот же прокси-объект ещё раз – получим исключение.

Маршалинг в .Net Remoting происходит автоматически при вызове методов удалённого объекта через прокси-объект. Прокси-объект выполняет сериализацию параметров метода, отправляет их на сервер, получает результат выполнения метода и десериализует его. Для этого используется «форматтер» по умолчанию – BinaryFormatter.

Ссылка на Github проект.


ссылка на оригинал статьи https://habr.com/ru/articles/733766/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *