Как известно, в .Net Core, на данный момент, нет AppDomain, а WCF только SOAP клиент .Net Core, WCF и ODATA клиенты.

Конечно, задачу можно решить и через Web Api с WebSockets для вызова событий. Но, я просто предлагаю альтернативное решение на маршалинге по TCP/IP и создание объектов, и вызов методов на стороне сервера с помощью Reflection.

Вот как выглядит удаленный вызов методов и свойств. Пример взят отсюда Основы перегрузки операторов:

   // Выведем сообщение в консоли сервера
   string typeStr = typeof(Console).AssemblyQualifiedName;
   var _Console = wrap.GetType(typeStr);// Получим тип на сервере по имени
   // "Hello from Client" будет выведено в консоле сервера
    _Console.WriteLine("Hello from Client");

   // получим тип по имени класса TestDllForCoreClr.MyArr
   // Из сборки TestDll.dll
   var MyArr = wrap.GetType("TestDllForCoreClr.MyArr", "TestDll");

   // Создадим объекты на стороне сервера
   // и получим ссылки на них
   var Point1 = MyArr._new(1, 12, -4); // new MyArr(1, 12, -4);
   var Point2 = MyArr._new(0, -3, 18); // new MyArr(0, -3, 18);

   // Все операции с объектами PointX происходят на стороне сервера
   Console.WriteLine("Координаты первой точки: "+Point1.x+" "+Point1.y+" "+Point1.z);
   Console.WriteLine("Координаты второй точки: "+Point2.x+" "+Point2.y + " "+ Point2.z);

   var Point3 = Point1 + Point2;
   Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z);
   Point3 = Point1 - Point2;
   Console.WriteLine("Point1 - Point2 = "+ Point3.x + " " + Point3.y + " " + Point3.z);
   Point3 = -Point1;
   Console.WriteLine("-Point1 = " + Point3.x + " " + Point3.y + " " + Point3.z);
   Point2++;
   Console.WriteLine("Point2++ = "+ Point2.x + " " + Point2.y + " " + Point2.z);
   Point2--;
   Console.WriteLine("Point2-- = " + Point2.x + " " + Point2.y + " " + Point2.z);

Непонятны только методы wrap.GetType и MyArr._new и _Console не родной. Все остальное один в один работа с объектами на C#.

На самом деле, Point1 и Point2 и Point3 это наследники DynamicObject с переопределенными методами TryXXX, а внутри них происходит упаковка типа метода, имя метода и параметров в Stream и передача его на Сервер по протоколу Tcp/IP, где он распаковывается и вызывается метод, который ищется по типу, названию метода и параметрам. После получения результата, те же процедуры но, только с сервера на клиента.

Само решение очень близко с COM out process взаимодействием на IDispatch. Помню с удовольствием разбирался с внутренностями TSocketConnection.

Но, в отличие от Idispatch, используется перегрузка методов и операторов, вызов Generic методов с выводом типов или с заданием Generic аргументов. Поддержка методов расширений для классов, находящихся в одной сборке и для Linq методов.

Также поддержка асинхронных методов и подписка на события, ref и out параметры, доступ по индексу [], поддержка итераторов в foreach.

В отличии от Web Api, не нужно писать специально серверный код Controller, Hub ы.
Это близко к AppDomain c Remouting но, в отличие от Remoting, каждый класс является аналогом MarshalByRefObject. То есть, мы можем создать любой объект на стороне сервера и вернуть ссылку на него (некоторые языки из чисел поддерживают только double).

При вызове методов, напрямую сериализуются параметры только следующих типов: числа, строки, дата, Guid и byte[]. Для остальных типов нужно их создать на стороне сервера, а в параметрах методов уже передаются ссылки на них.

Так примеры можно посмотреть на TypeScript, который по синтаксису близок к C#
CEF, ES6, Angular 2, TypeScript использование классов .Net Core. Создание кроссплатформенного GUI для .Net с помощью CEF

CEF, Angular 2 использование событий классов .Net Core

Вызов метода на стороне сервера можно посмотреть здесь Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux.

В этой статье я сосредоточусь на особенностях использования DinamicObject, маршалинга для вызова объектных и статических методов удаленных объектов.

Первое, с чего начнем — это с загрузки нужной сборки и получения типа. В первом примере мы получали тип по полному имени типа, по имени типа и имени сборки.

// Получим ссылку на сборку
//вызывается метод на сервере
//public static Assembly GetAssembly(string FileName, bool IsGlabalAssembly = false)
//Если IsGlabalAssembly == true? то ищется сборка в каталоге typeof(string).GetTypeInfo().Assembly.Location
//Иначе в каталоге приложения Server
var assembly = wrap.GetAssembly("TestDll");
// Получим из неё нужный тип
var @TestClass = assembly.GetType("TestDllForCoreClr.TestClass");

// Можно получить тип , зная имя класса и имя сборки. Удобно, когда нужен только один тип
//Метод на сервере 
//public static Type GetType(string type, string FileName = "", bool IsGlabalAssembly = false)
//var @TestClass = wrap.GetType("TestDllForCoreClr.TestClass", "TestDll");

Теперь, имея ссылку на тип, можно создать объект, вызвав мeтод _new или вызвать метод врапера New.

var TO = @TestClass._new("Property from Constructor");

или

wrap.New(@TestClass,"Property from Constructor");

Можно конструировать Generic типы:


  var Dictionary2 = wrap.GetType("System.Collections.Generic.Dictionary`2", "System.Collections");
  var DictionaryIntString = wrap.GetGenericType(Dictionary2, "System.Int32", "System.String");
  var dict = wrap.New(DictionaryIS);

Во wrap.New и wrap.GetGenericType можно передавать ссылки на типы или их строковое представление. Для строковых главное, что бы сборки были загружены.

Следующий вариант — это скопировать объект на сервер. Это важно потому, что скорость обмена по Tcp/IP составляет порядка 15 000 вызовов в секунду, при постоянном соединениии и всего 2000 при соединении на каждый запрос TCP/IP скорость обмена.


 var ClientDict = new Dictionary<int, string>()
        {
            [1] = "Один",
            [2] = "Два",
            [3] = "Три"
        };

// Скопируем объект с помощью Json сериализации .
//Более подробно чуть ниже.
 var dict = connector.CoryTo(ClientDict);

теперь dict это ссылка на словарь на стороне сервера, и можем передавать в параметрах.

 
 // Вызовем дженерик метод с автовыводом типа
 //public V GenericMethod<K, V>(Dictionary<K, V> param1, K param2, V param3)
 resGM = TO.GenericMethod(dict, 99, "Hello");
 Console.WriteLine("Вызов дженерик метода с выводом типа " + resGM);

Мы можем использовать индексы для доступа и установки значения


 Console.WriteLine("dict[2] " + dict[2]);
 dict[2] = "Два";
  Console.WriteLine("dict[2] " + dict[2]);
      

Можем использовать итератор


foreach (string value in dict.Values)
    Console.WriteLine("Dict Values " + value);

Теперь я обращу ваше внимание на отличие синтаксиса. Прежде всего, это вызов Generic методов с заданием Generic аргуметов, ref и out параметров, асинхронный вызов.


// Будем вызывать следующий метод
// public V GenericMethodWithRefParam<К,V >(К param, V param2, ref string param3)

// Не получилось у меня использовать ref параметр. Ошибка, платформа не поддерживает.
// Создадим объект класса RefParam, у которого есть поле Value куда и будет записываться результат
var OutParam = new ClientRPC.RefParam("TroLoLo");
resGM = TO.GenericMethodWithRefParam(5, "GenericMethodWithRefParam", OutParam);
Console.WriteLine($@"Вызов дженерик метода с автовыводом типов Ref {resGM}  {OutParam.Value}");

// Массив параметров для получения нужного метода
var GenericArgs = new object[] { "System.String", "System.String" };
// Массив может быть из строк и ссылок на типы например:

// var @Int32 = wrap.GetType("System.Int32");
//var GenericArgs = new object[] {@Int32, "System.String" };
  
// Первым параметром для вызова дженерик метода без вывода типа по параметрам
// должен быть массив представления типов
// Это аналог вызова
//   resGM = TO.GenericMethodWithRefParam<String,String>(null, "GenericMethodWithRefParam", ref OutParam) 
 resGM = TO.GenericMethodWithRefParam(GenericArgs, null, "GenericMethodWithRefParam", OutParam);
        Console.WriteLine($@"Вызов дженерик метода с дженерик аргументами Ref {resGM}  {OutParam.Value}");

// Test return null
resGM = TO.GenericMethodWithRefParam(GenericArgs, null, null, OutParam);
Console.WriteLine($@"Вызов дженерик метода с дженерик аргументами Ref {resGM}  {OutParam}");

Класс RefParam нужен для записи изменённого параметра в поле Value.

public class RefParam
    {
       public dynamic Value;
       public RefParam(object Value)
        {
            this.Value = Value;
        }
       public RefParam()
        {
            this.Value = null;
        }

        public override string ToString()
        {
            return Value?.ToString();
        }
    }

Для вызова асинхронного метода:


//         public async Task<V> GenericMethodAsync<K, V>(K param, string param4 = "Test")
var GenericArgs = new object[] { "System.Int32", "System.String" };
object resTask = await TO.async.GenericMethodAsync(GenericArgs , 44);

Нужно перед именем асинхронного метода добавить слово async

Если у вас есть Task, то можно дождаться выполнения, вызвав:

int res =await wrap.async.ReturnParam(task);

Еще одно отличие от реального кода заключается в том, что мы не можем напрямую использовать перегрузку ==

 
 if (myObject1 == myObject2)
                Console.WriteLine("Объекты равны перегрузка оператора ==");
          

Вместо него мы должны явно вызвать

           
 if (myObject1.Equals(myObject2))
                Console.WriteLine("Объекты равны Equals");

или, если есть перегрузка, оператора ==

 
if (MyArr.op_Equality(myObject1,myObject2))
                Console.WriteLine("Объекты равны op_Equality");

Есть поддержка объектов, поддерживающих System.Dynamic.IDynamicMetaObjectProvider. Это ExpandoObject, DinamicObject, JObject итд.

Возьмем для тестов следующий объект:


     public object GetExpandoObject()
        {

            dynamic res = new ExpandoObject();
            res.Name = "Test ExpandoObject";
            res.Number = 456;
            res.toString = (Func<string>)(() => res.Name);
            res.Sum = (Func<int, int, int>)((x, y) => x + y);

            return res;
        }

Теперь можно его использовать:

 var EO = TO.GetExpandoObject();
        Console.WriteLine("Свойство ExpandoObject Имя " + EO.Name);
        Console.WriteLine("Свойство ExpandoObject Число " + EO.Number);
        
       // Получим делегат
        var Delegate = EO.toString;
        Console.WriteLine("Вызов делегата toString " + Delegate()); // Вызовем как делегат
         
      // Для ExpandoObject можно вызвать как метод
        Console.WriteLine("Вызов Метода toString " + EO.toString()); 

        var DelegateSum = EO.Sum;
        Console.WriteLine("Вызов делегата Sum " + DelegateSum(3,4)); // Вызовем как делегат
        // Для ExpandoObject можно вызвать как метод
        Console.WriteLine("Вызов Метода Sum " + EO.Sum(3,4));          // Для ExpandoObject
        }
       

Как видно из примера, поддерживаются не только методы и свойства, но и делегаты. Часто нужно приводить объекты к интерфейсам. Для этого есть ключевое слово _as.

 string[] sa = new string[] { "Нулевой", "Первый", "Второй", "Третий", "Четвертый" };
// Скопируем массив на сервер
            var ServerSa = Connector.CoryTo(sa);
// Получим интерфейс IEnumerable по имени
            var en = ServerSa._as("IEnumerable");
            var Enumerator = en.GetEnumerator();

            while(Enumerator.MoveNext())
                Console.WriteLine(Enumerator.Current);

// Получим ссылки на типы
            var @IEnumerable = wrap.GetType("System.Collections.IEnumerable");
            var @IEnumerator = wrap.GetType("System.Collections.IEnumerator");

  // Для приведения к типу, используем ссылки на типы
            en = ServerSa._as(@IEnumerable);
            Enumerator = en.GetEnumerator();
            // На всякий случай приведем к Интерфейсу IEnumerator
            Enumerator = Enumerator._as(@IEnumerator);

            while (Enumerator.MoveNext())
                Console.WriteLine(Enumerator.Current);

Теперь перейдем к полуавтоматической сериализации.

var dict = connector.CoryTo(ClientDict);

Внутри connector.CoryTo происходит Json сериализация.

 public dynamic CoryTo(object obj)
        {
           // Получим строковое представление типа
          // Нужен для десериализации на сервере
            string type = obj.GetType().AssemblyQualifiedName;
            var str = JsonConvert.SerializeObject(obj);
            return CoryTo(type, str);

        }

Необходимо, что бы сборка сериализуемого типа была загружена на сервере. Пояснение чуть ниже.

Также на клиенте может не быть сборки с сериализуемым типом. Поэтому для сериализации мы можем использовать JObject
Анонимных типов.

JsonObject

Мы можем указать тип, ввиде строки или ссылки на тип и объект, который нужно сериализовать.

 public dynamic CoryTo(object type, object obj)
        {
            var str = JsonConvert.SerializeObject(obj);
            return CoryTo(type, str);

        }

И в итоге, отослать на сервер:

 // type может быть ссылкой на Type AutoWrapClient на стороне сервера
        // Или строковым представлением типа
        public dynamic CoryTo(object type, string objToStr)
        {
            object result;
            var res = AutoWrapClient.TryInvokeMember(0, "JsonToObject", new object[] { type, objToStr }, out result, this);

            if (!res)
                throw new Exception(LastError);

            return result;

        }

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

 static void TestSerializeObject(ClientRPC.TCPClientConnector connector)
    {
        // Создадим объект на стороне клиента
        var obj = new TestDllForCoreClr.TestClass("Объект на стороне Клиента");

        dynamic test = null;
        try
        {  // Скопируем объект на сервер
            test = connector.CoryTo(obj);

        }
        // Сборка не загружена
        //Поэтому явно загрузим сборку на сервере и повторим операцию CoryTo
        catch (Exception)
        {
            Console.WriteLine("Ошибка " + connector.LastError);
            var assembly = wrap.GetAssembly("TestDll");
            test = connector.CoryTo(obj);
        }
        Console.WriteLine(test.ObjectProperty);
    }

Также сборки, не находящиеся в каталоге Core CLR или не являющиеся NuGet пакетами, нужно вручную загружать:

Код загрузки сборки
 static Assembly LoadAssembly(string fileName)
    {
        var Dir = AppContext.BaseDirectory;
        string path = Path.Combine(Dir, fileName);
        Assembly assembly = null;
        if (File.Exists(path))
        {

            try
            {
                var asm = System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(path);
                assembly = Assembly.Load(asm);
            }
            catch (Exception)
            {
                assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
            }

        }
        else
            throw new Exception("Не найдена сборка " + path);

        return assembly;

    }


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

var objFromServ = connector.CoryFrom<Dictionary<int, string>>(dict);
        Console.WriteLine("dict[2] " + objFromServ[2]);

Можно использовать JObject, если такого типа нет на клиенте, используя:

 connector.CoryFrom<dynamic>(

Ну и под конец. перейдем к подключению к серверу.

        
     if (LoadLocalServer)
        {
           // Запустим процесс dotnet.exe c Server.dll,передав известный путь.
            connector = ClientRPC.TCPClientConnector.LoadAndConnectToLocalServer(GetParentDir(dir, 4) + $@"\Server\Server\bin\Release\netcoreapp1.1\Server.dll");
                 }
        else
        {
           // Подключимся к запущенному серверу по известному порту и адресу
            //третий параметр отвечает за признак постоянного соединения с сервером
            //Используется пул из 5 соединений
            connector = new ClientRPC.TCPClientConnector("127.0.0.1", port, false);

       // Запустим Tcp/IP сервер на стороне клиента для асинхронных методов и получения событий.
            port = ClientRPC.TCPClientConnector.GetAvailablePort(6892);
            connector.Open(port, 2);
        }

Внутри LoadAndConnectToLocalServer мы запускаем процесс dotnet.exe с адресом файла Server.dll:

Код загрузки процесса сервера
public static TCPClientConnector LoadAndConnectToLocalServer(string FileName)
        {
            int port = 1025;

            port = GetAvailablePort(port);
            ProcessStartInfo startInfo = new ProcessStartInfo("dotnet.exe");
            startInfo.Arguments = @""""+ FileName+ $@""" { port}";
            Console.WriteLine(startInfo.Arguments);
            var server = Process.Start(startInfo);
            Console.WriteLine(server.Id);

            var connector = new TCPClientConnector("127.0.0.1", port);

            port++;
            port = GetAvailablePort(port);
            connector.Open(port, 2);

            return connector;

        }


Теперь мы можем получить proxy.

 wrap = ClientRPC.AutoWrapClient.GetProxy(connector);

И с помощью него получать типы, вызывать статические методы, создавать объекты, вызывать методы объектов и тд.

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

    // Вызовем финализаторы всех AutoWrapClient ссылок на серверные объекты
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Press any key");
        Console.ReadKey();

        // Удаления из хранилища на стороне сервера происходит пачками, по 50 элементов
        // Отправим оставшиеся
        connector.ClearDeletedObject();

        // Отключимся от сервера, закроем все соединения, Tcp/Ip сервер на клиенте
        connector.Close();

        // Если мы запустили процесс сервера,
        // То выгрузим его
        if (LoadLocalServer) connector.CloseServer();
        Console.WriteLine("Press any key");
        Console.ReadKey();

Что касается событий, то можно посмотреть статью CEF, Angular 2 использование событий классов .Net Core.

Там описан процесс работы с событиями .Net объектов. Единственное, что код модуля для клиента можно получить:


 var @DescribeEventMethods = wrap.GetType("NetObjectToNative.DescribeEventMethods", "Server");
        string CodeModule = @DescribeEventMethods.GetCodeModuleForEvents(@EventTest);


Обращу внимание, что при подписке на событие с двумя и больше параметрами. создается
анонимный класс с полями соответствующими именами и типам параметров. Так для события:

 public event Action<string, int> EventWithTwoParameter;

Будет создана обертка:

Target.EventWithTwoParameter += (arg1,arg2) =>
 {
 if (EventWithTwoParameter!=null)
 {
 var EventWithTwoParameterObject = new {arg1=arg1,arg2=arg2};
 EventWithTwoParameter(EventWithTwoParameterObject);
 }
 };

CodeModule будет содержать следующий код:

//  параметр value:Анонимный Тип
    // Свойства параметра
    // arg1:System.String
    // arg2:System.Int32

   static public void EventWithTwoParameter(dynamic value)
    {
        Console.WriteLine("EventWithTwoParameter " + wrap.toString(value));
       //  Можно обратиться к параметрам.
        Console.WriteLine($"EventWithTwoParameter arg1:{value.arg1} arg2:{value.arg2}");
        value(ClientRPC.AutoWrapClient.FlagDeleteObject);
    }

Про использование динамической компиляции можно почитать здесь .Net Core, 1C, динамическая компиляция, Scripting API.

Что касается безопасности Analog System.Security.Permissions in .NET Core, то советуют запускать процесс под определенным аккаунтом пользователя с определенными правами.

Следует выразить сожаление, что в C# для динамиков нет псевдоинтерфейсов, аналога аннотации типа в TypeScript d.ts, для статической проверки кода и IntelliSense.

Но можно писать обычный код, переделывая его на удаленный. с минимальными телодвижениями.

Исходники лежат здесь RPCProjects.

Перед запуском примеров скомпилируйте проекты и скопируйте из папки TestDll\bin\Release\netcoreapp1.1\ библиотеку TestDll.dll в каталоги Server\bin\Release\netcoreapp1.1\ и Client\bin\Release\netcoreapp1.1\.

Если статья вызовет интерес, то в следующей статье распишу механизмы обмена и вызова методов на сервере.

P.S. Активно избавляюсь от руслиша в коде но, его еще достаточно много. Если проект будет интересен, то окончательно вычищу от русского кода.
Поделиться с друзьями
-->

Комментарии (10)


  1. kekekeks
    06.03.2017 11:25
    +2

    С подобными решениями на самом деле две проблемы:


    • из-за того что мы работает с экземплярами объектов на сервере, протокол автоматически становится stateful, из-за чего потом лезет куча головной боли, возникающие при обрыве соединения (а он происходит и часто)
    • Возможность дёргать произвольные свойства/методы произвольных типов вообще говоря является немаленькой такой дырой в безопасности, позволяющей проводить remote code execution атаки. Запуск под "ограниченным пользователем" тут не особо поможет, т. к.у вашего процесса уже есть права на работу с сетью, а если это не хелловорлд, то ещё и доступ к БД.


    1. Serginio1
      06.03.2017 11:40
      -1

      Ну для локал сервера разрыва соединения не замечал. Кроме всего есть режим с подключением отключением.
      Есть System.IO.Pipes.dll в \.nuget\packages\System.IO.Pipes\4.3.0\ref\netstandard1.3\, но напрямую подключить не удалось.

      Ну во первых это нужно для AppDomain, а для безопасности можно код выполнять в терминальной сессии, под определенными правами пользователя.


    1. Serginio1
      07.03.2017 12:53

      Кстати при работе в Web Api ты так же хранишь состояние сессии.
      Если мы говорим об песочнице аналоге AppDomain, то на данный момент я не нашел, но с выходом NetStandard 2 и .net Core 2 то там уже вроде будут NamedPipes. Кроме того, можно отрыть только определенные порты.
      Зачем песочнице доступ к БД? Суть AppDomain это возможность выгружать сборки, работа с нестабильным оборудованием с использованием натива. Если это не чужой код, то какие могут быть атаки. А проблема плагинов она будет и в моем решении и в обычном решении. Просто в моем решении можно прикрыть кучу дыр, за счет прав пользователя.


    1. Serginio1
      07.03.2017 14:19

      Кстати, что касается WFF, то можно ограничить число экспортируемых типов, необходимых для функционирования удаленного приложения. Все передаваемые типы легко контролируются.


    1. Serginio1
      07.03.2017 17:50

      Есть еще момент. Клиент на начальном этапе получает ссылку на тип со статическими методами. В данном случае GetType, GetAssembly. Никто не мешает поместить туда другой тип с другим набором методов. При этом несложно сделать универсальную сериализацию параметров на основе CopyTo добавив в перечисление EnumVar новое значение и при мериализации указвать это значение, затем строковое представление типа и сериализованное в Json строку значение. А эвены можно передавать на клиента отдельно.
      То есть из существуещей разработки можно сделать различные варианты. Было бы желание.


  1. Splo1ter
    07.03.2017 10:57

    Как то все сложновато)
    Подумайте над API со стороны разработчика, только начинающего изучать ваш код — что бы можно было собрать рабочий пример буквально в 2 строчки на клиенте и сервере.


    1. Serginio1
      07.03.2017 11:06

      Помоему все просто. Получаете тип создаете объекты. Единственно, что нет автоматической сериализации для не строк чисел, GUID и byte[]. По сравнению с WCF и Remoting все значительно проще.

      Что в начальном примере непонятно?


    1. Serginio1
      07.03.2017 11:48

      Здесь еще один момент, по уму это все должна сделать MS. Ибо я никто и звать меня никак. А вещь нужная, при этом нет встроенной технологии типа Remoting. По сути введение псевдоинтерфейсов (аналог анотации типов в TypeScript) решают проблему типизации. Внутри то так или иначе тоже самое.

      Плюс работа с Reflection. Если в большом .Net есть метод типа InvokeMember, то для .Net Core я сам реализовать поиск нужного метода, плюс поиск дженерик метода с выводом типа, поиск методов расширений итд. Это тоже было бы неплохо иметь в стандартных библиотеках как методы расширений по аналогии с GetTypeInfo.

      У меня это все реализовано.


      1. DarkFIxED
        07.03.2017 19:29

        Использовать рефлексию на стороне сервера? Это же удар по производительности, вдовесок к тем минусам, которые описаны выше. Что-то подсказывает мне, что вы не с той стороны какую-то проблему решаете.


        1. Serginio1
          07.03.2017 19:54

          Угу. На самом деле удар по производительности это маршалинг. Для примера Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux

          Скорость вызова из натива метода принимающего int и возвращающего int составляет порядка 500 000 вызовов в секунду. Для JavaScript- Native-Managed CEF, ES6, Angular 2, TypeScript использование классов .Net Core. Создание кроссплатформенного GUI для .Net с помощью CEF
          Составляет порядка 60 000 вызовов ( но например итератор поряка 170 000).
          То для Tcp/Ip это порядка 14 000. Так, что затраты на Reflection минимальны, по сравнению с маршалингом.