Pull to refresh

CEF, ES6, Angular 2, TypeScript использование классов .Net Core. Создание кроссплатформенного GUI для .Net с помощью CEF

Reading time21 min
Views11K
Меня все спрашивают — «Зачем это нужно?». На что, я гордо отвечаю — «Я в 1С использую для доступа к торговому оборудованию, к Вэб-сервисам по ws-протоколам, готовым компонентам. 1С, Linux, Excel, Word, OpenXML,ADO и Net Core. Кроме того, сделаю передачу JS объектов на сторону .Net с использованием через DynamicObject.

Можно сделать определенную конфигурацию CEF для всех платформ и можно делать кросспалатформенные декстопные приложения. Как аналог Electron. .Net Core развивается и можно достаточно легко перевести приложения под WPF и UWP на Angular 2» сделав описание классов и использовать IntelliSense при кодировании на TypeScript.

Но я прекрасно понимаю, что это всего лишь высокопарные слова, и мало кому это нужно. Но мне чертовски интересно, особенно после программирования на 1С.

Для показа возможностей, возьму пример из моей статьи Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux II.

В нем куча сахара и показывает все проблемы. Сразу прошу прощения за Руслиш. Я очень стараюсь, но у меня на нем куча примеров, а времени очень мало.

 // Метод расширения
    //IConfiguration WithDefaultLoader(this IConfiguration configuration, Action<LoaderSetup> setup = null, IEnumerable<IRequester> requesters = null);
    var config = Configuration.Default.WithDefaultLoader();
    // Устанавливаем адрес страницы сайта
    var address = "https://en.wikipedia.org/wiki/List_of_The_Big_Bang_Theory_episodes";
    // загружаем страницу и разбираем её
 
    //Метод расширения
    //Task<IDocument> OpenAsync(this IBrowsingContext context, string address);
    var document = BrowsingContext.New(config).OpenAsync(address).Result;
    // Используем CSS селектор для получения строк таблицы с классом  
    var rowSelector = "tr.vevent";
    var Строки = document.QuerySelectorAll<IHtmlTableRowElement>(rowSelector);
    foreach (var str in Строки)

На TypeScript это выглядит так:

            let Net = NetObject.NetWrapper;
            let $$ = NetObject.FlagDeleteObject; // Символ для признака удаления при вызове объекта как метода

            // Загрузим сборку AngleSharpж
            let СборкаAngleSharp = Net.Сборка("AngleSharp");
            // Получим из неё используемые типы
            let Configuration = СборкаAngleSharp.GetType("AngleSharp.Configuration");
            let BrowsingContext = СборкаAngleSharp.GetType("AngleSharp.BrowsingContext");
            let HtmlParser = СборкаAngleSharp.GetType("AngleSharp.Parser.Html.HtmlParser");
            let IHtmlTableRowElement = СборкаAngleSharp.GetType("AngleSharp.Dom.Html.IHtmlTableRowElement");
            let ApiExtensions = СборкаAngleSharp.GetType("AngleSharp.Extensions.ApiExtensions");

            let Default = Configuration._Default;
            var config = Default.WithDefaultLoader();
            // Устанавливаем адрес страницы сайта
            var address = "https://en.wikipedia.org/wiki/List_of_The_Big_Bang_Theory_episodes";
            // загружаем страницу и разбираем её

            let Context = BrowsingContext.New(config);

            //Метод расширения
            //Task<IDocument> OpenAsync(this IBrowsingContext context, string address);
            let document = await Context.async.OpenAsync(address);
            // Не могу установить результат асинхронной функции класс Proxy с Target fuction
            // Поэтому для объектов нужно вручную обернуть
            document = NetObject.WrapResult(document, true);

            // Используем CSS селектор для получения строк таблицы с классом  
            let rowSelector = "tr.vevent";

            // Для дженериков пока не сделал поиск в расширениях поэтому вместо
            //let rows = document.QuerySelectorAll([IHtmlTableRowElement], rowSelector);
           
            // используем метод расширения явно
            //IEnumerable < TElement > QuerySelectorAll<TElement>(this IParentNode parent, string selectors) where TElement : IElement;
             let rows = ApiExtensions.QuerySelectorAll([IHtmlTableRowElement], document, rowSelector);
         
            // можно и так вызвать, но нужно обыграть все варианты
            //let rows = document.QuerySelectorAll(rowSelector);

// Пройдемся по нужным строкам распарсенной таблицы
            for (let row of rows) {
                let Cells = row._Cells;
                let i = 0;
                let данныеСайта = new ДанныеСайта();
                this.ResultParse.push(данныеСайта);
// Загрузим данные ячеек в поля объекта ДанныеСайта
                for (let Cell of Cells) {
 // Нужно дождаться окончания итератора, что бы освободить ссылку на итератор
                    if (i < 8)                        {
                    данныеСайта[this.Colums[i]] = Cell._TextContent;
                    Cell($$); // Удалим ссылку из хранилища объектов 
                    i++;
                    }
                }
                Cells($$);
                row($$);
            }
            rows($$);

            // Удалим вручную испльзуемые объекты
            NetObject.DeleteNetObjets(СборкаAngleSharp, Configuration, BrowsingContext, HtmlParser, IHtmlTableRowElement, ApiExtensions, Default, config, Context, document); 
            alert("Количество элементов в хранилище "+Net.КоличествоЭлементовВХранилище());

Прежде всего видим главные отличия от C#. Для получения свойства нужно добавить "_"

let Default = Configuration._Default;

Для вызова асинхронного метода нужно добавить ключевое слово async:

let document = await Context.async.OpenAsync(address);

Для вызова дженерик метода, если нельзя вывести типы по параметрам то аргументы указываем в массиве:

let rows = ApiExtensions.QuerySelectorAll([IHtmlTableRowElement], document, rowSelector);

Ну и главное, нужно вручную удалить ссылку на объект со стороны .Net

Cells(NetObject.FlagDeleteObject);
для уменьшения писанины
Cells($$);

По скорости вызовов на моем Intel Core i3-2120 CPU 3.3 GHz.

Скорость вызова без Proxy 60к вызовов в секунду
Скорость вызова с прокси Proxy 45k вызовов в секунду
Скорость вызова итератора 160k вызовов в секунду

Что в общем-то вполне приемлемо.

Приведу еще небольшой пример.

 public class Тестовый
    {
       public string СвойствоОбъекта { get; set; }
       public  Тестовый(string СвойствоОбъекта)
            {
            this.СвойствоОбъекта = СвойствоОбъекта;
            }
 public object ПолучитьExpandoObject()
        {

            dynamic res = new ExpandoObject();
            res.Имя = "Тест ExpandoObject";
            res.Число = 456;
            res.ВСтроку = (Func<string>)(() => res.Имя);
            res.Сумма = (Func<int, int, int>)((x, y) => x + y);

            return res;
        }
     }

На TypeScript можно вызвать так:

// Получим Тип из сборки лежащей в каталоге приложения
let Тестовый = Net.GetType("TestDllForCoreClr.Тестовый", "TestDllForCoreClr");
// Создадим объект используя new
let TO = new Тестовый("Свойство из Конструктора");
// Получим ExpandoObject
var EO = TO.ПолучитьExpandoObject();
let Имя=EO._Имя;// Свойства через _
let Число=EO._Число;
let делегат = EO._ВСтроку;
let res= делегат());// Вызовем как делегат
 // Для ExpandoObject можно вызвать как метод
 res= EO.ВСтроку());// Для ExpandoObject

Теперь за счет чего это достигается. Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux. Через CEF встраиваем нужные методы:

   declare var window: WindowInterface;
    export interface WindowInterface {
    CallNetMethod(Id: number, MethodName: string, args?: any[]): any;// Вызов метода
    CallNetDelegate(Id: number, args?: any[]): any; // Вызов делегата
    CallNetPropertySet(Id: number, PropertyName: string, value: any): void; // Установка свойства
    CallNetPropertyGet(Id: number, PropertyName: string): any; // Получение значения свойства
    DeleteNetObject(Id: number): void; // Освободить ссылку на объект со стороны .Net
    // Асинхронный вызов метода возвращающий Task или Task<T>
    CallAsyncNetObjectFunction(Id: number, MethodName: string, TaskId: string, args?: any[]): any; 
    // Регистрация метода на стороне CEF, для установки результата Promise 
    RegisterCallBacks(SetAsyncResult: (Successfully: boolean, TaskId: string, result: any) => void): void; 
    // Вызов дженерик метода с указанием типов аргументов
    CallNetObjectGenericFunction(Id: number, MethodName: string, types: any[], args?: any[]): any;
    // Вызов итератора IEnumerator MoveNext на стороне .Net
    IteratorNext(Id: number): any;
}

Для удобного использования этих методов создадим класс прокси с Target гибридного типа:

export interface NetObjectinterface {
    (): void;
    Id: number;
    isNetObject: boolean;
    IsAsyncCall?: boolean;
    CallAsProp(target: NetObject, name: any): ResultCallAsProp;
    Execute: (target: NetObject, name: any, args: any[]) => any;

}

Target должен быть функцией для возможности использования new и (). Теперь нам понадобится Handler:

export var NetObjectHandler: ProxyHandler<NetObjectinterface> = {
    get: (target, name: any) => {
// Вызывается как PropertyGet как для свойств так и методов
// Что бы их разделить  свойства начинаются с "_"

        let res = target.CallAsProp(target, name);
        if (res.Successfully)
            return res.result;

        return (...args: any[]) => {
            return target.Execute(target, name, args);
        }

    },
    set: function (target, prop, value, receiver) {
        return NetObject.SetPropertyValue(target, prop, value, receiver);
    },

    apply: (target, that, args) => {
        if (args.length == 1) {
            var param = args[0];
            if (param === NetObject.FlagGetObject)
                return target;
            else if (param === NetObject.FlagDeleteObject) {
                window.DeleteNetObject(target.Id);
                return undefined;
            }
        }

        NetObject.SetArgs(args);
        let res = window.CallNetDelegate(target.Id, args)
        return NetObject.WrapResult(res, true);
    },

    construct: (target, argumentsList, newTarget) => {
// Используем метод на стороне Net 
// object Новый(object Тип, params object[] argOrig)
        NetObject.SetArgs(argumentsList);
        argumentsList.unshift(target);
        let res = window.CallNetMethod(0, "Новый", argumentsList);
        return NetObject.WrapResult(res, true);

    }
}

Ну и понадобится сам Target:

function getNetObject(id: number): NetObjectinterface {
    let netObject = <NetObjectinterface>function (start: number) { };
    netObject.Id = id;
    netObject.isNetObject = true;
    netObject[NetObject.isNetclass] = true;
    netObject.Execute = NetObject.Execute;
    netObject.CallAsProp = NetObject.CallAsProp;
    return netObject;
}

Для обертки результата из CEF используется:

static WrapResult(value: any, ReturnProxy: boolean = false): any {
        if (typeof value == "object") {
            if ("IsNetObject" in value) {
                let res = getNetObject(value.Id);
                if (ReturnProxy)
                    return new Proxy(res, NetObjectHandler);
                else
                    return res

            }


        }
        return value;
    }

Что касается асинхронных методов то они работают через два метода:

static GetPromise(Target: NetObjectinterface, name: any, args: any[]) {

        let key = window.CallNetMethod(0, "GetUniqueString");
        let promise = new Promise((resolve, reject) => {
            NetObject.PromiseDictioanary.set(key, { resolve: resolve, reject: reject });
            window.CallAsyncNetObjectFunction(Target.Id, name, key, args);
        });
        return promise;
    }

И при получении асинхронного результата:

 static SetPromiseResult(Successfully: boolean, TaskId: string, result: any) {
        let item = NetObject.PromiseDictioanary.get(TaskId);
        try {

            NetObject.PromiseDictioanary.delete(TaskId);
           // Вот здесь не могу установить результат Proxy с Target function
            //  result = NetObject.WrapResult(result, true);
           // возникает исключение "Не найден then"
            if (Successfully)
                item.resolve(result);
            else
                item.reject(result);
        }

        catch (e) {
            item.reject("ошибка установки асинхронного результата " + e);
            alert("ошибка установки асинхронного результата " + e);
        }
    }

где:

 static PromiseDictioanary = new Map();

Весь код можно посмотреть ниже под спойлером:

Весь код NetProxy
declare var window: WindowInterface;
declare var $_: Symbol;
declare var _$: Symbol;

export interface WindowInterface {
    CallNetMethod(Id: number, MethodName: string, args?: any[]): any;// Вызов метода
    CallNetDelegate(Id: number, args?: any[]): any; // Вызов делегата
    CallNetPropertySet(Id: number, PropertyName: string, value: any): void; // Установка свойства
    CallNetPropertyGet(Id: number, PropertyName: string): any; // Полусение значения свойства
    DeleteNetObject(Id: number): void; // Освободить ссылку на объект со стороны .Net
    // Асинхронный вызов метода возвращающий Task или Task<T>
    CallAsyncNetObjectFunction(Id: number, MethodName: string, TaskId: string, args?: any[]): any; 
    // Регистрация метода на стороне CEF, для установки результата Promise 
    RegisterCallBacks(SetAsyncResult: (Successfully: boolean, TaskId: string, result: any) => void): void; 
    // Вызов дженерик метода с указанием типов аргументов
    CallNetObjectGenericFunction(Id: number, MethodName: string, types: any[], args?: any[]): any;
    // Вызов итератора IEnumerator MoveNext на стороне .Net
    IteratorNext(Id: number): any;
}

class ResultCallAsProp {
    constructor(public Successfully: boolean, public result?: any) { };
}
export interface NetObjectinterface {
    (): void;
    Id: number;
    isNetObject: boolean;
    IsAsyncCall?: boolean;
    CallAsProp(target: NetObject, name: any): ResultCallAsProp;
    Execute: (target: NetObject, name: any, args: any[]) => any;

}

export var NetObjectHandler: ProxyHandler<NetObjectinterface> = {
    get: (target, name: any) => {


        let res = target.CallAsProp(target, name);
        if (res.Successfully)
            return res.result;

        return (...args: any[]) => {
            return target.Execute(target, name, args);
        }


    },
    set: function (target, prop, value, receiver) {
        return NetObject.SetPropertyValue(target, prop, value, receiver);
    },

    apply: (target, that, args) => {
        if (args.length == 1) {
            var param = args[0];
            if (param === NetObject.FlagGetObject)
                return target;
            else if (param === NetObject.FlagDeleteObject) {
                window.DeleteNetObject(target.Id);
                return undefined;
            }
        }

        NetObject.SetArgs(args);
        let res = window.CallNetDelegate(target.Id, args)
        return NetObject.WrapResult(res, true);
    },

    construct: (target, argumentsList, newTarget) => {

        //  var res = NetObject.GetNetObject(5);
        //  return new Proxy(res, NetObjectHandler)
        NetObject.SetArgs(argumentsList);
        argumentsList.unshift(target);
        let res = window.CallNetMethod(0, "Новый", argumentsList);
        return NetObject.WrapResult(res, true);

    }


}

function getNetObject(id: number): NetObjectinterface {
    let netObject = <NetObjectinterface>function (start: number) { };
    netObject.Id = id;
    netObject.isNetObject = true;
    netObject[NetObject.isNetclass] = true;
    netObject.Execute = NetObject.Execute;
    netObject.CallAsProp = NetObject.CallAsProp;
    return netObject;
}

function GetNetProxy(): any {
    let res = getNetObject(0);
    if (NetObject.FlagFirstLoad) {
        try {
            window.RegisterCallBacks(NetObject.SetPromiseResult);

        }
        catch (e) {
            // alert("ошибка " + e);
        }

        NetObject.FlagFirstLoad = false;
    }

    return new Proxy(res, NetObjectHandler);

}
export class NetObject {
    static GetNetObject(id: number) { return getNetObject(id); }
    static isNetclass = Symbol();
    static IsAsyncCall = Symbol();
    static FlagGetObject = Symbol();
    static FlagDeleteObject = Symbol();
    static FlagFirstLoad = true;
    static NetWrapper = GetNetProxy();

    static PromiseDictioanary = new Map();
    static GetIterator(target: NetObjectinterface): any {
        return function () {
            let IdIterator = window.CallNetMethod(0, "GetIterator", [target]).Id;

            return {
                next: function () {

                    let value = window.IteratorNext(IdIterator);
                    if (value === undefined) {
                        return { value: undefined, done: true };

                    } else
                        return { value: NetObject.WrapResult(value, true), done: false }

                }
            }


        }
    }


    static WrapResult(value: any, ReturnProxy: boolean = false): any {
        if (typeof value == "object") {
            if ("IsNetObject" in value) {
                let res = getNetObject(value.Id);
                if (ReturnProxy)
                    return new Proxy(res, NetObjectHandler);
                else
                    return res

            }


        }
        return value;
    }

    static WrapObject(value: any): any {
        if (typeof value == "function") {
            if (NetObject.isNetclass in value)
                return new Proxy(value, NetObjectHandler);
        }
    }

    static GetPropertyValue(target: NetObjectinterface, name: any): any {
        let res = window.CallNetPropertyGet(target.Id, name);
        return NetObject.WrapResult(res, true);

    }

    static SetPropertyValue(target: NetObjectinterface, prop: any, value: any, receiver: any): any {
        let res = window.CallNetPropertySet(target.Id, prop, NetObject.GetTarget(value));
        return true;

    }

    static CallAsProp(Target: NetObjectinterface, name: any): ResultCallAsProp {
        if (name === Symbol.iterator) {
            return new ResultCallAsProp(true, NetObject.GetIterator(Target));
        }

        if (name === Symbol.toPrimitive) {
            return new ResultCallAsProp(true, () => { return `Id= ${Target.Id}, isNetObject= ${Target.isNetObject}` });
        }
        if (name.startsWith('_')) {

            return new ResultCallAsProp(true, NetObject.GetPropertyValue(Target, name.substring(1)));

        }

        if (name === "async") {

            let res = getNetObject(Target.Id);

            res.Execute = NetObject.ExecuteAsync;
            res.CallAsProp = NetObject.CallAsPropAsync;
            return new ResultCallAsProp(true, new Proxy(res, NetObjectHandler));
        }

        return new ResultCallAsProp(false);
    }

    static CallAsPropAsync(Target: NetObjectinterface, name: any): ResultCallAsProp {


        return new ResultCallAsProp(false);
    }

    static GetPromise(Target: NetObjectinterface, name: any, args: any[]) {

        let key = window.CallNetMethod(0, "GetUniqueString");
        let promise = new Promise((resolve, reject) => {
            NetObject.PromiseDictioanary.set(key, { resolve: resolve, reject: reject });
            window.CallAsyncNetObjectFunction(Target.Id, name, key, args);
        });
        return promise;
    }

    static GetTarget(obj: any): any {
        if (typeof obj == "function") {
            if (NetObject.isNetclass in obj)
                return obj(NetObject.FlagGetObject);


        }
        return obj;
    }

    static SetArgs(args: any[]) {
        for (let i in args) {
            let obj = args[i];
            if (typeof obj == "function") {
                if (NetObject.isNetclass in obj)
                    args[i] = obj(NetObject.FlagGetObject);


            }

        }
    }


    static SetPromiseResult(Successfully: boolean, TaskId: string, result: any) {
        let item = NetObject.PromiseDictioanary.get(TaskId);
        try {

            NetObject.PromiseDictioanary.delete(TaskId);
            //  result = NetObject.WrapResult(result, true);
            if (Successfully)
                item.resolve(result);
            else
                item.reject(result);
        }

        catch (e) {
            item.reject("ошибка установки асинхронного результата " + e);
            alert("ошибка установки асинхронного результата " + e);
        }
    }

    static CheckGenericMethod(args: any[]): any {

        var argsCount = args.length;

        if (argsCount > 0 && args[0] instanceof Array) {
            var types = args[0].slice();
            NetObject.SetArgs(types);

            var args2 = args.slice(1);
            NetObject.SetArgs(args2);
            return { IsGeneric: true, types: types, args: args2 }
        }

        return { IsGeneric: false };

    }
    static Execute(Target: NetObjectinterface, name: any, args: any[]) {

        let res = undefined;
        let chek = NetObject.CheckGenericMethod(args);

        if (chek.IsGeneric) {
            res = window.CallNetObjectGenericFunction(Target.Id, name, chek.types, chek.args);

        }
        else {
            NetObject.SetArgs(args);

            res = window.CallNetMethod(Target.Id, name, args);
        }

        return NetObject.WrapResult(res, true);
    }

    static ExecuteAsync(Target: NetObjectinterface, name: any, args: any[]) {


        let res = undefined;
        let chek = NetObject.CheckGenericMethod(args);

        if (chek.IsGeneric) {
            let Target0 = getNetObject(0);
            let task = window.CallNetObjectGenericFunction(Target.Id, name, chek.types, chek.args);
            res = NetObject.GetPromise(Target0, "ReturnParam", [getNetObject(task.Id)]);
            window.DeleteNetObject(task.Id);

        }
        else {
            NetObject.SetArgs(args);

            res = NetObject.GetPromise(Target, name, args);
        }

        return res;
    }

    static New(Target: NetObjectinterface, name: any, args: any[]): any {
        NetObject.SetArgs(args);
        var res = window.CallNetMethod(0, "Новый", args);
        return NetObject.WrapResult(res, true);
    }

    static DeleteNetObjets(...args: any[]) {

        for (let item of args)
            item(NetObject.FlagDeleteObject);
    }


}


Прошу прощение за моё незнание С++. Но, делать было нужно сейчас, а я на нем не пишу.
Не смог прикрутить Dev Tools к CefSimple:

Код CEF обертки для обмена между JS и .Net Core
#include "include/CEF_V8.H"
#include "ManagedDomainLoader.h"
#include "CefV8HandlersForNet.h"

#include "types.h"
#include "NetConverter.h"


#include "include/base/cef_bind.h"
#include "include/wrapper/cef_closure_task.h"
#include <thread>
#include "include/base/cef_platform_thread.h"
namespace NetObjectToNative{

	BaseClassForNetHandlers::BaseClassForNetHandlers(ManagedDomainLoader* mD)
	{
		this->mD = mD;
	}

	 bool CallNetObjectFunction::Execute(const CefString& name,
		CefRefPtr<CefV8Value> object,
		const CefV8ValueList& arguments,
		CefRefPtr<CefV8Value>& retval,
		CefString& exception)  {


		const size_t argumentsCount = arguments.size();
		vector<wstring> savedstrings;
		NetObjectToNative::tVariant* Params = nullptr;

		int Target = arguments[0]->GetIntValue();
		wstring MethodMame = arguments[1]->GetStringValue().ToWString();

		CefRefPtr<CefV8Value> params;

		size_t  argCount = 0;
		if (argumentsCount == 3)
		{


			params = arguments[2];

			if (!params->IsArray())
			{
				exception = CefString(L"Для вызова метода 3 параметр должен быть массивом");
				return true;

			}
			argCount = params->GetArrayLength();


		}

		if (argCount > 0)
		{
			savedstrings.reserve(argCount);
			Params = new NetObjectToNative::tVariant[argumentsCount];
			NetObjectToNative::tVariant* Param = Params;

			for (size_t i = 0; i < argCount; ++i)
			{

				NetObjectToNative::ConvertCEFtoNet(params->GetValue(i), &Param[i], savedstrings);
			}

		}
		wchar_t*  Error = nullptr;
		NetObjectToNative::tVariant RetVal;

		bool res = mD->pCallAsFunc(Target, MethodMame.c_str(), &RetVal, Params, argCount, &Error);

		if (res)
		{

			retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);
		}
		else
		{
			if (Error)
				exception = CefString(std::wstring(Error));
			delete Error;
		}

		if (Params) delete[] Params;

		return true;
	}

	 //====================== ============================================
	 bool CallAsyncNetObjectFunction::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();
		 vector<wstring> savedstrings;
		 NetObjectToNative::tVariant* Params = nullptr;

		 int Target = arguments[0]->GetIntValue();
		 wstring MethodMame = arguments[1]->GetStringValue().ToWString();
		 wstring TaskId = arguments[2]->GetStringValue().ToWString();

		 CefRefPtr<CefV8Value> params;

		 size_t  argCount = 0;
		 if (argumentsCount == 4)
		 {


			 params = arguments[3];

			 if (!params->IsArray())
			 {
				 exception = CefString(L"Для вызова асинхронного метода 4 параметр должен быть массивом");
				 return true;

			 }
			 argCount = params->GetArrayLength();


		 }

		 if (argCount > 0)
		 {
			 savedstrings.reserve(argCount);
			 Params = new NetObjectToNative::tVariant[argumentsCount];
			 NetObjectToNative::tVariant* Param = Params;

			 for (size_t i = 0; i < argCount; ++i)
			 {

				 NetObjectToNative::ConvertCEFtoNet(params->GetValue(i), &Param[i], savedstrings);
			 }

		 }
		 wchar_t*  Error = nullptr;
		 NetObjectToNative::tVariant RetVal;

		 //bool res = mD->pCallAsFunc(Target, MethodMame.c_str(), &RetVal, Params, argCount, &Error);
		 bool res = mD->pCallAsyncFunc(Target, MethodMame.c_str(), this->cfn, TaskId.c_str(), Params, argCount, &Error);

		 if (res)
		 {

			 retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);
		 }
		 else
		 {
			 if (Error)
				 exception = CefString(std::wstring(Error));
			 delete Error;
		 }

		 if (Params) delete[] Params;

		 return true;
	 }

	//============================ Call Generic Function

	 bool CallNetObjectGenericFunction::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();
		 vector<wstring> savedstrings;
		 NetObjectToNative::tVariant* Params = nullptr;
		 NetObjectToNative::tVariant* ParamsTypes = nullptr;

		 int Target = arguments[0]->GetIntValue();
		 wstring MethodMame = arguments[1]->GetStringValue().ToWString();

		 CefRefPtr<CefV8Value> params;
		 CefRefPtr<CefV8Value> types= arguments[2];
		 size_t  typesCount= types->GetArrayLength();


		 size_t  argCount = 0;
		 if (argumentsCount == 4)
		 {


			 params = arguments[3];

			 if (!params->IsArray())
			 {
				 exception = CefString(L"Для вызова метода 4 параметр должен быть массивом");
				 return true;

			 }
			 argCount = params->GetArrayLength();


		 }

		 
		 savedstrings.reserve(argCount+ typesCount);
		 ParamsTypes = new NetObjectToNative::tVariant[typesCount];
		 for (size_t i = 0; i < typesCount; ++i)
		 {

			 NetObjectToNative::ConvertCEFtoNet(types->GetValue(i), &ParamsTypes[i], savedstrings);
		 }


		 if (argCount > 0)
		 {
			
			 Params = new NetObjectToNative::tVariant[argumentsCount];
			 NetObjectToNative::tVariant* Param = Params;

			 for (size_t i = 0; i < argCount; ++i)
			 {

				 NetObjectToNative::ConvertCEFtoNet(params->GetValue(i), &Param[i], savedstrings);
			 }

		 }



		 wchar_t*  Error = nullptr;
		 NetObjectToNative::tVariant RetVal;

		 bool res = mD->pCallAsGenericFunc(Target, MethodMame.c_str(), &RetVal, ParamsTypes, typesCount, Params, argCount, &Error);

		 if (res)
		 {

			 retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);
		 }
		 else
		 {
			 if (Error)
				 exception = CefString(std::wstring(Error));
			 delete Error;
		 }

		 if (Params) delete[] Params;
		 delete[] ParamsTypes;

		 return true;
	 }


	 //===================== CallNetDelegate

	 bool CallNetDelegate::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();
		 vector<wstring> savedstrings;
		 NetObjectToNative::tVariant* Params = nullptr;

		 int Target = arguments[0]->GetIntValue();
		

		 CefRefPtr<CefV8Value> params;

		 size_t  argCount = 0;
		 if (argumentsCount == 2)
		 {


			 params = arguments[1];

			 if (!params->IsArray())
			 {
				 exception = CefString("Для вызова делегата  2 параметр должен быть массивом");
				 return true;

			 }
			 argCount = params->GetArrayLength();


		 }

		 if (argCount > 0)
		 {
			 savedstrings.reserve(argCount);
			 Params = new NetObjectToNative::tVariant[argumentsCount];
			 NetObjectToNative::tVariant* Param = Params;

			 for (size_t i = 0; i < argCount; ++i)
			 {

				 NetObjectToNative::ConvertCEFtoNet(params->GetValue(i), &Param[i], savedstrings);
			 }

		 }
		 wchar_t*  Error = nullptr;
		 NetObjectToNative::tVariant RetVal;

		 bool res = mD->pCallAsDelegate(Target, &RetVal, Params, argCount, &Error);

		 if (res)
		 {

			 retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);
		 }
		 else
		 {
			 if (Error)
				 exception = CefString(std::wstring(Error));
			 delete Error;
		 }

		 if (Params) delete[] Params;

		 return true;
	 }

	 // CallNetObjectPropertySet
	 bool 	CallNetObjectPropertySet::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();

		 if (argumentsCount != 3)
		 {


				 exception = CefString(L"Для PropertySet должно быть 3 параметра");
				 return true;

			}
			 
		 vector<wstring> savedstrings;


		 int Target = arguments[0]->GetIntValue();
		 wstring PropertyName = arguments[1]->GetStringValue().ToWString();
		 CefRefPtr<CefV8Value> value = arguments[2];

			 
			 savedstrings.reserve(1);
			 NetObjectToNative::tVariant Param;
			
			 NetObjectToNative::ConvertCEFtoNet(value, &Param, savedstrings);
			 


		 wchar_t*  Error = nullptr;
	
		 bool res = mD->pSetPropVal(Target, PropertyName.c_str(), &Param, &Error);

		 if (!res)
		 {
			 if (Error)
			 {
				 exception = CefString(std::wstring(Error));
				 delete Error;
			 }
			 else 
				 exception = CefString(L"Ошибка при установке свойства"+ PropertyName);
			
		 }

		

		 return true;
	 }

	 bool 	DeleteNetObject::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();

		 if (argumentsCount != 1)
		 {
			 exception = CefString(L"Для DeleteObject Должно быть 1 параметра");
			 return true;
		 }

		 CefRefPtr<CefV8Value> value = arguments[0];
		 
		 
		 mD->pDeleteObject(value->GetIntValue());

		

		 return true;
	 }

	 bool 	IteratorNext::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 const size_t argumentsCount = arguments.size();

		 if (argumentsCount != 1)
		 {
			 exception = CefString(L"Для IteratorNext Должно быть 1 параметра");
			 return true;
		 }

		 CefRefPtr<CefV8Value> value = arguments[0];
		 wchar_t*  Error = nullptr;
		 NetObjectToNative::tVariant RetVal;

		bool res= mD->pIteratorNext(value->GetIntValue(),&RetVal, &Error);
		if (res)
		{
			retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);

		}
		else
		{
			retval = CefV8Value::CreateUndefined();
			if (Error)
			{
				exception = CefString(std::wstring(Error));
				delete Error;
			}

		}
		 return true;
	 }

	 bool 	CallNetObjectPropertyGet::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {


		 int Target = arguments[0]->GetIntValue();
		 wstring PropertyName = arguments[1]->GetStringValue().ToWString();

		 wchar_t*  Error = nullptr;
		 NetObjectToNative::tVariant RetVal;

		 bool res = mD->pGetPropVal(Target, PropertyName.c_str(), &RetVal,&Error);

		 if (!res)
		 {
			 if (Error)
			 {
				 exception = CefString(std::wstring(Error));
				 delete Error;
			 }
			 else
				 exception = CefString(L"Ошибка при установке свойства " + PropertyName);

		 }
		 else
			 retval = NetObjectToNative::ConvertNetToCef(&RetVal, true);



		 return true;
	 }

	 void SetHandlerToContex(CefRefPtr<CefV8Handler> Handler, CefRefPtr<CefV8Value> object, const char* MetodName)
	 {

		 CefRefPtr<CefV8Value> CallNetObject = CefV8Value::CreateFunction(MetodName, Handler);

		 // Add the "myfunc" function to the "window" object.
		 object->SetValue(MetodName, CallNetObject, V8_PROPERTY_ATTRIBUTE_NONE);
	 }

	 void ContextForNetHandlers::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
	 {
		 this->context = context;
		 // Retrieve the context's window object.
		 CefRefPtr<CefV8Value> object = context->GetGlobal();
		 NetObjectToNative::ManagedDomainLoader* mD = NetObjectToNative::ManagedDomainLoader::InitManagedDomain(L"c:\\Program Files\\DNX\\runtimes\\dnx-coreclr-win-x86.1.0.0-rc1-update1\\bin\\", L"", L"");

		 //=========== CallNetMethod =======================================================
		 SetHandlerToContex(new CallNetObjectFunction(mD), object, "CallNetMethod");
		 //=========== CallNetDelegate =======================================================
		 SetHandlerToContex(new CallNetDelegate(mD), object, "CallNetDelegate");
		 //=========== PropertySet =======================================================
		 SetHandlerToContex(new CallNetObjectPropertySet(mD), object, "CallNetPropertySet");
		 //=========== PropertyGet =======================================================
		 SetHandlerToContex(new CallNetObjectPropertyGet(mD), object, "CallNetPropertyGet");
		 //=========== PropertyGet =======================================================
		 SetHandlerToContex(new DeleteNetObject(mD), object, "DeleteNetObject");
		 //=========== SetCallBacks =======================================================
		 SetHandlerToContex(new SetCallBacks(mD, this, object), object, "RegisterCallBacks");
		 //============ CallAsyncNetObjectFunction ================================
		 SetHandlerToContex(new CallAsyncNetObjectFunction(mD, this), object, "CallAsyncNetObjectFunction");
		 //============ CallNetObjectGenericFunction ================================
		 SetHandlerToContex(new CallNetObjectGenericFunction(mD), object, "CallNetObjectGenericFunction");
		 //============ IteratorNext ================================
		 SetHandlerToContex(new IteratorNext(mD), object, "IteratorNext");

	 }

	

	 
	 void ContextForNetHandlers::AsyncCalBack(const wchar_t* TaskID, bool Successfully, tVariant* ReturnValue)
	 {
		

		 if (!CefCurrentlyOn(TID_RENDERER)) {
			 // Execute on the UI thread.
			// CefPostTask(TID_UI, base::Bind(&AsyncCalBack2, TaskID, Successfully,ReturnValue, CallbackContext));
			 CefPostTask(TID_RENDERER, base::Bind(&SetCallBacks::AsyncCalBack, this->scb, TaskID, Successfully, ReturnValue));
			 return;
		 }

		 scb->AsyncCalBack(TaskID, Successfully, ReturnValue);
	 }

	 //==================== Set CallBacs
	 bool SetCallBacks::Execute(const CefString& name,
		 CefRefPtr<CefV8Value> object,
		 const CefV8ValueList& arguments,
		 CefRefPtr<CefV8Value>& retval,
		 CefString& exception) {

		    this_id = std::this_thread::get_id();
			 if (arguments.size() == 1 && arguments[0]->IsFunction()) {
				 AsyncMetodCall = arguments[0];
				 CallbackContext = CefV8Context::GetCurrentContext();
				 cfn->scb = this;

				 /*CefV8ValueList args;
				 args.push_back(CefV8Value::CreateBool(true));
				 args.push_back(CefV8Value::CreateString(L"Первый"));
				 args.push_back(CefV8Value::CreateString(L"Второй"));

				 if (AsyncMetodCall->ExecuteFunctionWithContext(CallbackContext, globalObj, args)) {
					
				 }*/
				
				 return true;
			 }
		

		 return true;

	 }

	 void SetCallBacks::AsyncCalBack(const wchar_t* TaskID, bool Successfully, tVariant* ReturnValue)
	 {
		 CefV8ValueList args;
		 
		 std::thread::id Curr_id = std::this_thread::get_id();
		 if (this_id != Curr_id)
		 {
		 }

		 if (CallbackContext.get() && CallbackContext->Enter()) {

			 args.push_back(CefV8Value::CreateBool(true));

			 args.push_back(CefV8Value::CreateString(TaskID));
			 delete[] TaskID;

			 if (ReturnValue==nullptr)
				 args.push_back(CefV8Value::CreateUndefined());
			 else
			 {
			 args.push_back(NetObjectToNative::ConvertNetToCef(ReturnValue, true));
			 delete[] ReturnValue;
			 }
 
			 
			 if (AsyncMetodCall->ExecuteFunctionWithContext(CallbackContext, globalObj, args)) {
				 
			 }
			 CallbackContext->Exit();
		 }
	 }	
}


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

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

Краткое описание содержимого. В каталоге 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. То скачайте отсюда opensource.spotify.com/cefbuilds/index.html 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:
Total votes 21: ↑12 and ↓9+3
Comments255

Articles