- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

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

Сегодня я покажу как использовать события объектов .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)? [4]

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

Для 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);


    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Для VS 2017 cmake.exe -G «Visual Studio 15 2017».

Автор: Serginio1

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/c-2/241778

Ссылки в тексте:

[1] CEF, ES6, Angular 2, TypeScript использование классов .Net Core для расширения возможностей: https://habrahabr.ru/post/320960/

[2] CEF: https://en.wikipedia.org/wiki/Chromium_Embedded_Framework

[3] Chrome 57 принесёт поддержку языков C и C++ для веб-сайтов: http://4pda.ru/2017/02/06/335097/

[4] Что такое Зоны(Zones)?: http://www.sdblog.ru/archives/zony-i-change-detection-v-ionic-2-i-angular-2/

[5] 1С,.Net Core. Динамическая компиляция класса обертки для получения событий .Net объекта в 1С : https://habrahabr.ru/post/309850/

[6] здесь: https://yadi.sk/d/0If7C5jZ3CEhmf

[7] отсюда: http://opensource.spotify.com/cefbuilds/index.html

[8] Источник: https://habrahabr.ru/post/321452/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best