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

    Это продолжение статьи 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».
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      +2

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

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

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


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

          Если прокликать все вкладки слева, то прогрузятся картинки и кушать начнёт 33МБ. Это на полноценный XAML-фреймворк с биндингами и прочим. И мы считаем, что это очень много и надо ужать потребление на таком простом приложении хотя бы мегабайт до 20. .NET Core, кстати, ощутимо лучше в этом плане себя ведёт чем десктопный дотнет и Mono.

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

            Если подключить Angular 2 То размер сразу достигает 90 МБ.
            Но вот дальше даже использую динамическую компиляцию размер не переходит 100 МБ
              0
              Кстати скачай посмотри. А то всего то 5 человек скачали. Сам и посмотришь. Может, что и посоветуешь
                +3

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


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


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

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

                  Сейчас NetStandard 2 и .Net Core 1.2 выйдут и возможностей будет близки к взрослому .Net
                0
                Можно ли уже говорить, что будущее за .NET Core?
                  0

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

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

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

            Only users with full accounts can post comments. Log in, please.