Pull to refresh

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

Reading time 8 min
Views 6.8K
Это продолжение статьи 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».
Tags:
Hubs:
+12
Comments 10
Comments Comments 10

Articles