Это продолжение статьи CEF, ES6, Angular 2, TypeScript использование классов .Net Core для расширения возможностей.

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

Хочу немного остановиться на CEF.

Это кроссплатформенный браузер (с ядром используемым Google Chrome), с неограаниченными расширениями за счет использования натива на С++, позволяющее писать полноценное крооссплатформенное декстопное приложение с UI.

Кроме того Chrome 57 принесёт поддержку языков C и C++ для веб-сайтов

Сегодня я покажу как использовать события объектов .Net Core классов в Angular 2.
Многие прочитав мою первую статью приводили довод, что вместо использования классов .Net можно использовать HTTP сервисы.

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

Для примера возьмем класс с событиями.

 public class EventTest
    {
        public event Action<string, int> EventWithTwoParameter;
        public event Action<string> EventWithOneParameter;
        public event Action EventWithOutParameter;
        public bool IsRun = false;
        public void Test()
        {
            EventWithTwoParameter?.Invoke(DateTime.Now.ToString(), 1);
            EventWithOneParameter?.Invoke(DateTime.UtcNow.ToString());
            EventWithOutParameter?.Invoke();
        }

        public async void Run()
        {
            if (IsRun) return;
            IsRun = true;

            while (IsRun)
            {
                await Task.Delay(2000);
                Test();
            }
        }
    }

Теперь мы можем использовать этот класс в Angular 2:

export class TestEventComponent {
    EventsRes: EventRes[] = [];
    WOWE: WrapperObjectWithEvents;
   test: any;
    EventTest: any;
    constructor(private ngZone: NgZone) {
        let Net = NetObject.NetWrapper;
      // Получим тип используемого класса.
        this.EventTest = Net.GetType("TestDllForCoreClr.EventTest", "TestDllForCoreClr");
    // Создадим объект
        this.test = new this.EventTest();
// Создадим обёртку для событий, через которую будем подписываться
// и отписываться от событий.
        this.CreateWrapperForEvents(this.test);
    }

// Этот код автоматически создается для уменьшения писанины
// Описывается структура параметров.


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

    public EventWithTwoParameter(value: any) {
        this.AddComment("EventWithTwoParameter", NetObject.NetWrapper.toString(value));
        value(NetObject.FlagDeleteObject);
    }
    // параметр value:System.String
    public EventWithOneParameter(value: any) {
        this.AddComment("EventWithOneParameter ",NetObject.NetWrapper.toString(value));
    }

    public EventWithOutParameter(value: any) {
        this.AddComment("EventWithOutParameter", NetObject.NetWrapper.toString(value));
    }

    CreateWrapperForEvents(obj: any): void {
        let wrapForEvents = NetObject.GetWrapperForObjectWithEvents(obj, this.ngZone);

        wrapForEvents.AddEventHandler("EventWithTwoParameter", this.EventWithTwoParameter.bind(this));
        wrapForEvents.AddEventHandler("EventWithOneParameter", this.EventWithOneParameter.bind(this));
        wrapForEvents.AddEventHandler("EventWithOutParameter", this.EventWithOutParameter.bind(this));

        // установить переменную wrapForEvents переменной класса
        this.WOWE = wrapForEvents;
    }

Ну и не забыть очистить ссылки на стороне .Net при разрушении компонента:

ngOnDestroy() {  
                NetObject.DeleteNetObjets(this.EventTest, this.test);
                this.WOWE.Close();
                alert("Количество ссылок на стороне .Net ="+Net.CountItemsInStore());
    }

Отписаться от событий можно тремя способами.

// получим результат мотода subscribe объета Subject.

this.AddEventHandlerResult=  wrapForEvents.AddEventHandler("EventWithTwoParameter", this.EventWithTwoParameter.bind(this));

И используя его опишемся от события:

this.AddEventHandlerResult.unsubscribe();

Но события из .Net будут обрабатываться на стороне JS.

Следующие два варианта говорят сам за себя.

this.WOWE.RemoveEventHandler("EventWithTwoParameter");
this.WOWE.RemoveAllEventHandler();

Получить текст TS модуля для описания событий можно получить так:

let DescribeMethodsTS= Net.GetType("NetObjectToNative.DescribeMethodsTS", "NetObjectToNative");
 this.CodeModule = DescribeMethodsTS.GetCodeModuleTS(this.EventTest);

Для чего нужен NgZone можно почитать здесь. Что такое Зоны(Zones)?

Теперь перейдем к подноготной. Для получении обертки событий используется динамическая компиляция. Процесс подробно описан 1С,.Net Core. Динамическая компиляция класса обертки для получения событий .Net объекта в 1С

Для CEF внесены некоторые изменения:

Код динамической обертки событий
//Данный класс используется для подписки на событие и передачи данных на сторону CEF

 public class ClassForEventCEF
    {
        EventInfo EI;
        public  string EventKey;
        public IntPtr CppHandler;
        public object WrapperForEvent;
        public ClassForEventCEF(object WrapperForEvent, string EventKey, EventInfo EI, IntPtr CppHandler)
        {
            this.EventKey = EventKey;
            this.EI = EI;
            this.CppHandler = CppHandler;
            this.WrapperForEvent = WrapperForEvent;
           // Подпишемся на событие
            EI.AddEventHandler(WrapperForEvent, new System.Action<object>(CallEvent));
        }

        public void CallEvent(object value)
        {
            IntPtr ResIntPtr = AutoWrap.AllocMem(48);
            var EventKeyPtr = WorkWithVariant.WriteStringInIntPtr(EventKey);
            WorkWithVariant.SetObjectInIntPtr(AutoWrap.WrapObject(value), ResIntPtr);
 // Вызовем объектный метод на стороне CEF
// С передачей Ключа события и параметры события
            AutoWrap.EventCall(CppHandler, EventKeyPtr, ResIntPtr);

        }

        public void RemoveEventHandler()
        {
         
            EI.RemoveEventHandler(WrapperForEvent, new System.Action<object>(CallEvent));

        }

    }

Этот класс сформирован динамически:

public class WrapperForEventTestDllForCoreClr_EventTest
 {
 public IntPtr CppHandler;
 public TestDllForCoreClr.EventTest Target;
 Dictionary<string, ClassForEventCEF> EventStoage=new Dictionary<string, ClassForEventCEF>();
 public event Action<object> EventWithTwoParameter;
 public event Action<object> EventWithOneParameter;
 public event Action<object> EventWithOutParameter;
 
 public WrapperForEventTestDllForCoreClr_EventTest(IntPtr CppHandler, TestDllForCoreClr.EventTest Target)
 {
 
 this.CppHandler = CppHandler;
 this.Target = Target;
 
 Target.EventWithTwoParameter += (arg1,arg2) =>
 {
 if (EventWithTwoParameter!=null)
 {
 var EventWithTwoParameterObject = new {arg1=arg1,arg2=arg2};
 EventWithTwoParameter(EventWithTwoParameterObject);
 }
 };
 
 Target.EventWithOneParameter += (obj) =>
 {
 if (EventWithOneParameter!=null)
 EventWithOneParameter(obj);
 
 
 };
 Target.EventWithOutParameter += () =>
 {
 if (EventWithOutParameter!=null)
 EventWithOutParameter(null);
 };
 
 
 }
 
 public void AddEventHandler(string EventKey, string EventName)
 {
 EventInfo ei = GetType().GetEvent(EventName);
 var forEvent = new ClassForEventCEF(this,EventKey, ei,CppHandler);
 EventStoage.Add(EventKey, forEvent);
 
 }
 
 public void RemoveEventHandler(string EventKey)
 {
 ClassForEventCEF cfe = null;
 if (EventStoage.TryGetValue(EventKey,out cfe))
 {
 EventStoage.Remove(EventKey);
 cfe.RemoveEventHandler();
 
 }
 
 }
 public void RemoveAllEventHandler()
 {
 
 foreach( var cfe in EventStoage.Values)
 cfe.RemoveEventHandler();
 
 EventStoage.Clear();
 }
 
 
 
 public static object CreateObject(IntPtr Self, TestDllForCoreClr.EventTest Target)
 {
 
 return new WrapperForEventTestDllForCoreClr_EventTest(Self, Target);
 }
 }
 
 return new Func<IntPtr, TestDllForCoreClr.EventTest, object>(WrapperForEventTestDllForCoreClr_EventTest.CreateObject);


Ну и на стороне JS событие обрабатывается так:

Код обертки событий на стороне TS
class EventEmitter{

    public subject = new Subject<any>();

    constructor(private ngZone: NgZone) {
     //   this.data = Observable.create((observer: any) => this.dataObserver = <Observer<any>>observer);

    }

    public subscribe(EventHandler: (value: any) => void) {
        
        return this.subject.subscribe({
            next: (v) => this.ngZone.run(()=> EventHandler(v))
        });
       
        
    }

    public emit(value: any) {
        this.subject.next(value);
       

    }

    public Complete() {
        this.subject.complete();

    }
}

class EventItem {
    constructor(public EventKey: string, public Event:EventEmitter){}
}

//EventEmitter

export class WrapperObjectWithEvents {
    // словарь имен события и EventKey с EventEmitter
    EventsList = new Map<string, EventItem>();

    // Словарь EventKey и EventEmitter
    EventEmittersList = new Map<string, EventEmitter>();
    constructor(private NetTarget: any, private ngZone: NgZone) { };


    // Вызывается при получении внешнего события из .Net
    public RaiseEvent(EventKey: string, value: any) {
        // Если есть подписчики, то вызываем их
        if (this.EventEmittersList.has(EventKey)) {
            let Event = this.EventEmittersList.get(EventKey);
            Event.emit(value);

        }

    }


    public AddEventHandler(EventName: string, EventHandler: (value: any) => void): any {

        let ei: EventItem;
        let isFirst = false;

        if (!this.EventsList.has(EventName)) {
            let EventKey = window.CallNetMethod(0, "GetUniqueString");

            let Event = new EventEmitter(this.ngZone);
            ei = new EventItem(EventKey, Event);
            this.EventsList.set(EventName, ei);
            this.EventEmittersList.set(EventKey, Event);
            NetObject.EventCallers.set(EventKey, this.RaiseEvent.bind(this));
            isFirst = true;
        }
        else
            ei = this.EventsList.get(EventName);


        //  let res = ei.Event.subscribe(this.ngZone.run(() =>EventHandler));
        let res = ei.Event.subscribe((value: any) => { EventHandler(value) });


        if (isFirst)
            this.NetTarget.AddEventHandler(ei.EventKey, EventName);

        return res;


    }

    public RemoveEventHandler(EventName: string) {

        if (this.EventsList.has(EventName)) {
            let ei = this.EventsList.get(EventName);
            let EventKey = ei.EventKey
            this.NetTarget.RemoveEventHandler(EventKey);
            NetObject.EventCallers.delete(EventKey);
            this.EventEmittersList.delete(EventKey);
            this.EventsList.delete(EventName);
            ei.Event.Complete();

        }
    }
    public RemoveAllEventHandler() {
        this.NetTarget.RemoveAllEventHandler();

        for (let ei of this.EventsList.values()) {
            {
                NetObject.EventCallers.delete(ei.EventKey);
                ei.Event.Complete();
            }

            this.EventsList.clear();
            this.EventEmittersList.clear();
        }
    }

    public Close()
    {
        this.RemoveAllEventHandler();
        this.NetTarget(NetObject.FlagDeleteObject);


    }
}



Кстати неплохая статья по теме Реактивные расширения в .Net
Что касается прожорливости
CEF с начальной страницей без Angular 2, но с загруженными сборками .Net Core. занимает 20 mb
С вызовом простеньких методов доходит до 30mb
Если подключить динамическую компиляцию то вырастает до 70 mb

Если подключить Angular 2 то размер сразу достигает 90 МБ.

Но вот дальше даже использую динамическую компиляцию размер не переходит 100 МБ

При этом надо учитывать, что работают 2 сборщика мусора.
Вполне приемлемо.

Не сложно сделать передачу JS объектов и функций на сторону .Net только на время вызова метода.

Или по аналогии с Net сделать хранилище объектов JS. Благо в .Net есть финализаторы и не так критично следить за освобождением ссылок.

Хотя разработку всего скачало 5 человек. Но на самом деле там много интереного как для программирующих на TS,C# и связки между C++ и .Net.

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

Краткое описание содержимого. В каталоге cefsimple\Release\ лежит исполняемый файл с библиотеками и начальной страницей Test.html. В каталоге cefsimple\NetObjectToNative\
лежат все файлы для обмена между CEF и .Net Core. ManagedDomainLoader и ClrLoader отвечают за загрузку .Net Core, получения и передачу методов для обмена данными.

В CefV8HandlersForNet реализованы Хэндлеры для обмена между JS и CEF. В NetConverter конвертация данными между Net и Cef.

В NetObjectToCEF лежат файлы которые реализуют обмен с CEF. В TestDllForCoreClr лежат все используемые примеры для Тестовый.

В файле TestTypeScript\TestTypeScript\app\ лежат файлы ts которые и реализуют Proxy. NetProxy.ts файл реализующий Proxy.

home.component.ts тест с AngleSharp. counter.component.ts различные тесты возможностей. TestSpeed.ts тесты скорости выполнения.

Так же проект без node_modules. установите через вызов в директории TestTypeScript npm install.

Суть тестов такова. Запускаете TestTypeScript и CefProgects\cefsimple\Release\cefsimple.exe. На начальной странице можно попробовать тесты на JS. Для использования тестов на TS нужно перейти на сайт который нужно указать в поле ниже «Введите адрес сайта « что бы перейти на него»». Там три теста.

Если хотите компилировать cefsimple. То скачайте отсюда 32-разрядный Standard Distribution и замените в директории tests\cefsimple\ сс и h файлы и скопируйте директорию NetObjectToNative.

Для использования VS 2015 введите в корневом каталоге CEF cmake.exe -G «Visual Studio 14».

Для VS 2017 cmake.exe -G «Visual Studio 15 2017».
Поделиться с друзьями
-->

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


  1. kekekeks
    09.02.2017 12:05
    +2

    Такой вопрос, сколько памяти в такой конфигурации кушает Hello World? На все процессы в сумме, если их несколько.


    1. Serginio1
      09.02.2017 12:13

      Чесно не смотрел. Но CEF то кушает не мало.
      Если мы хотим создавать кроссплатформенное декстопное приложение, то память не ресурс.
      На 1С я смотрел то там не много. До 10MB. Сама coreclr.dll занимает менее 4 МБ
      Ну и на динамическую компиляцию классов.


      1. kekekeks
        09.02.2017 12:25
        +1

        Если мы хотим создавать кроссплатформенное декстопное приложение, то память не ресурс.

        Ну вот из-за этого у появляются "нативные приложения на JS", кушающие по 400 мегабайт после запуска (Skype for Linux Alpha, я смотрю на тебя). В авалонии у нас каталог контролов на старте выглядит вот так:


        Скрытый текст


        1. Serginio1
          09.02.2017 12:36

          Значит так посмотрел.
          CEF с начальной страницей без Angular занимает 20 mb
          С вызовом простеньких методов доходит до 30mb
          Если подключить динамическую компиляцию то вырастает до 70 mb

          Если подключить Angular 2 То размер сразу достигает 90 МБ.
          Но вот дальше даже использую динамическую компиляцию размер не переходит 100 МБ


        1. Serginio1
          09.02.2017 12:37

          Кстати скачай посмотри. А то всего то 5 человек скачали. Сам и посмотришь. Может, что и посоветуешь


          1. kekekeks
            09.02.2017 12:52
            +3

            Так это счастье на гитхаб бы выложить, в 2017-ом году-то. Там, глядишь, и народ подтянется.


            Посоветовать наврятли что смогу, я во всей этой истории с "а давайте напишем гуй на Angular (тогда ещё первый), запихнём в CEF (у нас это был CEFGlue) и заставим взаимодействовать с хостовым приложением (виндоформы)" разочаровался, когда оно начало кушать до гигабайта памяти, а на машинах целевой аудитории просто не могло работать.


            А так тема с нормальными вызовами дотнета из любой фигни очень интересная, да. Было бы неплохо к postgres прикрутить поддержку хранимок на шарпе, например, по аналогии с PL/Java, например.


            1. Serginio1
              09.02.2017 12:59

              Вот я с C++ не очень дружу. Они кстати тоже через файлы распространяют.
              Ну Angular 2 то развивается и WebPack тоже. Сейчас с ним разберусь.
              У меня есть статья про 1С, Linux, Excel, Word, OpenXML,ADO и Net Core

              Сейчас NetStandard 2 и .Net Core 1.2 выйдут и возможностей будет близки к взрослому .Net


        1. denismaster
          09.02.2017 17:00

          Можно ли уже говорить, что будущее за .NET Core?


          1. kekekeks
            09.02.2017 17:05

            Ну как, оно по фичам понемногу приближается к паритету с полновесным дотнетом, но всё ещё сильно ощущается острая нехватка портированных библиотек.
            Если есть выбор между полноценным дотнетом и .NET Core, то нужно… брать и использовать в качестве целевого фреймворка .NETStandard и не терзать себя муками выбора.


          1. Serginio1
            09.02.2017 17:10

            Для Net технологий однозначно. Это и UWP и Xamarin с выходом NetStandard 2 станут полностью совместимы с .Net Core 1.2
            Но к нему присматриваются и Samsung с Tizen с Xamarin Forms

            Да и Googlу тоже заинтересован Выпуск .NET Core 1.1. Google присоединился к .NET Foundation. Samsung выпустил .NET для Tizen