Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров15K

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

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

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

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

Маршалинг

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

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

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

Процесс маршалинга возможно применить либо к сериализуемым объектам, либо к объектам, которые наследуются от класса 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 проект.

Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+7
Комментарии9

Публикации

Ближайшие события