Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Забегая вперёд стоит отметить, что сериализация является частью маршалинга.
Сериализация
Сериализация объекта – запись состояния объекта в байтовый поток таким образом, чтобы его можно было преобразовать обратно в копию исходного объекта.
Сериализация может применяется для любого объекта только в рамках одного процесса (одной программы).
Маршалинг
Абстрактно говоря, маршалинг – процесс передачи информации между управляемым и неуправляемым кодом.
Маршалинг объекта – запись состояние объекта и его кодовой базы таким образом, чтобы была была возможность получить копию объекта путём загрузки определений класса исходного объекта. Можно утверждать, что сериализация – часть маршалинга.
Кодовая база – информация о том, где можно найти реализация какого-либо объекта.
Процесс маршалинга возможно применить либо к сериализуемым объектам, либо к объектам, которые наследуются от класса 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
.
Ссылка на Github проект.