CEF, Angular 2 використання подій класів .Net Core

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

Як і очікувалося, вона не привернула особливої уваги. Але величезне спасибі тим, кого зацікавила моя творчість. Саме ви даєте мені стимул на продовження пошуків.

Хочу трохи зупинитися на CEF.

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

Сьогодні я покажу як використовувати події об'єктів .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;
Словник<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 = Вами.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.

Якщо раптом когось зацікавило, то проекти і вихідні коди можна скачати тут.

Короткий опис вмісту. В каталозі 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».
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.