Можно сделать определенную конфигурацию 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();
Весь код можно посмотреть ниже под спойлером:
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:
#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».
Спасибо за внимание!
Комментарии (255)
Serginio1
01.02.2017 22:01-1На самом деле здесь много интересного. Это и Proxy и Promise для TypeScript.
Работа с CEF, а так же и Net Core. Кроме того здесь показано как просто можно использовать классы .Net очень близко к кодированию на C#. Здесь очень много информации на любой вкус.ZOXEXIVO
01.02.2017 23:06+2Я, как человек, который первый раз видит этот код могу четко сказать, что он никого ничему не научит.
Учатся на простых и ясных примерах, а не на коде, который интересен только его разработчику.
Serginio1
01.02.2017 23:20Ну у меня и не было цели учить новичков. Главная моя цель показать как можно легко использовать классы .Net в 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 Corelair
02.02.2017 00:22Главная моя цель показать как можно легко использовать классы .Net в TypeScript и каким образом это достигается
Но зачем, зачем это делать? Если у меня есть доступ к .net, то я уже и без TS обойдусь.
Serginio1
02.02.2017 10:01На самом деле я не видел где есть полный доступ к классам .Net из браузера. Если подскажешь то буду благодарен. Сейчас например для Linux нет аналога WPF, а моя разработка помогает использовать кроссплатформенный .Net Core в браузере, или просто расширить возможности браузера за счет классов .Net
lair
02.02.2017 11:46-1На самом деле я не видел где есть полный доступ к классам .Net из браузера. Если подскажешь то буду благодарен.
Не подскажу, потому что этого нет. А нет этого, потому что это небезопасно.
Сейчас например для Linux нет аналога WPF, а моя разработка помогает использовать кроссплатформенный .Net Core в браузере
Угу. В каком? У меня шесть, что ли, устройств на трех операционных системах, 5 браузеров — на скольких из них это будет работать?
Serginio1
02.02.2017 12:02-1Ну в статье же написано про CEF
Внизу статьи показаны ссылки на программы которые можно запустить. В начале статьи
Можно сделать определенную конфигурацию CEF для всех платформ и можно делать кросспалатформенные декстопные приложения.
Ребята читайте пожалуйста внимательнее, или подскажите как лучше написать.lair
02.02.2017 12:12Ну в статье же написано про CEF
Написано, ага. CEF работает на Windows Phone? iOS?
Внизу статьи показаны ссылки на программы которые можно запустить.
Агу. С сайтом (который у меня не собирается). Спасибо, но нет.
Serginio1
02.02.2017 12:43-1Для WinFone и прочего есть xamarin. А вот для Linux ничего нет.
Сайт это TypeScript? то там нужно npm install сделать, что бы установить создать папку node_modules
lair
02.02.2017 13:10Для WinFone и прочего есть xamarin.
Да там много что есть, только получается, что решение как-то не очень кросс-платформенное.
Сайт это TypeScript? то там нужно npm install сделать, что бы установить создать папку node_modules
… и
npm
у меня тоже нет. Про что и речь.Serginio1
02.02.2017 13:14Ну речь то про Angular. Я просто еще тот вэб программист. Просто JS созданные на TS напрямую не идут. Речь про await. Но внутри есть Test.html его можно посмотреть и там есть различные тесты. Правда без Proxy
lair
02.02.2017 13:16Вот я про это с самого начала и говорю: много прыжков и ужимок, чтобы получить функциональность, польза которой совершенно не очевидна.
Serginio1
02.02.2017 13:23-1Я сейчас разберусь как можно локально использовать TypeScript и ужимок не будет. Если хочешь помоги. Буду благодарен. Все ts файлы лежат в папке ap.
Еще раз я один и мне приходится изучать кучу вещей. И времени просто не хватает.
Serginio1
02.02.2017 13:16Кстати а можешь пояснить, за что минус? Проблема в том, что для Linux сейчас не UI. При этом много крика про импортозамещение.
lair
02.02.2017 13:17Не ко мне вопрос, не мой минус.
А крики про импортозамещение как были криками, так и останутся. О каком импортозамещении вы говорите, используя .net core и CEF?
Serginio1
02.02.2017 13:25Еще раз. У тебя есть приложение на WPF, UWP. C выходом NetStandard 2 возможности .Net Core приблизятся к UWP/
Можно достаточно легко перевести приложение под WPF на Angular 2 и использовать под Linuxlair
02.02.2017 13:31+1Можно достаточно легко перевести приложение под WPF на Angular 2
Серьезно?
(Не говоря уже о том, что приложение для iOS вряд ли будет на WPF)
Понимаете ли, в моих конкретных реалиях оказалось выгоднее под каждую мобилку писать свое собственное приложение с нативным интерфейсом, потому что бэкэнд все равно, как и полагается бэкэнду, на сервере. И все эти прыжки и ужимки с "кросплатформенным-html-js-в-кросплатформенном-браузере-который-общается-с-кроссплатформенным-.net" умерли еще на первой стадии, когда не взлетел кроссплатформенный интерфейс.
При этом в соседнем окне у меня Slack, написанный как раз на чем-то типа Electron (просто не помню деталей), и он как раз хорошо справляется — но у него, опять-таки, вся логика на сервере, он просто не отличается от того, что можно запускать в браузере.
Serginio1
02.02.2017 13:42Ну так CEF то есть и по WPF http://opensource.spotify.com/cefbuilds/index.html
Опять же я просто показываю решение. Я в самом начале написал, что это мало кому интересно. Просто делюсь опытом. Я же тебя не заставляю использовать.lair
02.02.2017 14:16Ну так CEF то есть и по WPF http://opensource.spotify.com/cefbuilds/index.html
Что такое "CEF по WFP"?
Опять же я просто показываю решение. Я в самом начале написал, что это мало кому интересно. Просто делюсь опытом. Я же тебя не заставляю использовать.
Вы делитесь опытом, мы делимся мнениями по поводу вашего опыта. Все логично.
Serginio1
02.02.2017 14:23Прошу прощения. Wpf здесь лишнее.
Спасибо. Просто я сейчас не смогу отвечать. Много работы. Но все равно огромное спасибо!
denismaster
02.02.2017 11:42Я думаю можно таким образом смешать Electron+Angular2, например, как UI, и .NET как бекенда.
lair
02.02.2017 11:44Если вы хотите .net на бэкенде — поставьте его там и выставьте сервис.
denismaster
02.02.2017 11:51Городить сервис на http\ws и клиентскую часть? Появятся накладные расходы на сериализацию данных, отправку запроса, десериализацию, обработку…
А тут можно все в одном процессе. У такого подхода есть плюсы и минусы, но он заслуживает право существовать.lair
02.02.2017 11:54+1… у конверсии данных JS-.net нет накладных расходов? JS выполнется в том же домене, что и .net? Не поверю.
(не говоря уже о том, что бэкенд в подавляющей части случаев все равно удаленный)
Serginio1
02.02.2017 11:58В статье есть тесты на вызовы
Скорость вызова без Proxy 60к вызовов в секунду
Скорость вызова с прокси Proxy 45k вызовов в секунду
Скорость вызова итератора 160k вызовов в секунду
Можно же скачать исходники и все пощупать.lair
02.02.2017 11:59… и как это говорит об отсутствии накладных расходов?
Serginio1
02.02.2017 12:04Они минимальны по сравнению с http\ws.
При этом часто нужно работать с торговым оборудованием или дисками и прочим оборудованием. Сейчас кстати пишу оберки для использования событий .Net классовlair
02.02.2017 12:12Они минимальны по сравнению с http\ws.
Цифры в студию.
Serginio1
02.02.2017 12:27Еще раз
Скорость вызова без Proxy 60к вызовов в секунду
Скорость вызова с прокси Proxy 45k вызовов в секунду
Скорость вызова итератора 160k вызовов в секунду
Вызываем функцию
public int ПолучитьЧисло(int число) { return число; }
Код такой
// Протестируем скорость вызовов без Proxy var start = new Date(); let count = 100000; let result = 0; for (let i = 0; i < count; i++) result += window.CallNetMethod(Id, "ПолучитьЧисло", [1]); this.speed = Math.round(count / (new Date().getTime() - start.getTime()) * 1000); // Протестирум это же но через Proxy start = new Date(); result = 0; for (let i = 0; i < count; i++) result +=TO.ПолучитьЧисло(1); this.speedWithProxy = Math.round(count / (new Date().getTime() - start.getTime()) * 1000); // Протестируем скорость вызова итератора let iter = TO.GetNumbers(count); start = new Date(); result = 0; for (let i of iter) result += i; this.spedCallIterator = Math.round(count / (new Date().getTime() - start.getTime()) * 1000); NetObject.DeleteNetObjets(TO, Тестовый, iter); }
lair
02.02.2017 12:29+1Эти цифры говорят только о том, какова скорость вызовов методов .net из TS. Они ничего не говорят ни о том, какова эта скорость в сравнении с межпроцессным транспортом, ни о том, какой оверхер будет в реальном применении.
Serginio1
02.02.2017 12:48Она показывает, что скорость вполне приемлема для большинства задач.
Кроме того в большинстве случаев тебе не нужны циклы, в большинстве случаев тебе нужен некий функционал Net классов. Например можно перенести готовую логику WPF на Angular 2 или Electron.
C http\ws у тебя будет значительно больше работы, чем использовать классы напрямую. Тем более работа с оборудованием ни как не спасет.
Ну для интереса можно вызвать Http сервис и подсчитать. Это несложно. Но, что то мне подсказывает, что межпроцеесоное взаимодействие максимум на 1к вызов в секунду выйдет.lair
02.02.2017 13:11Она показывает, что скорость вполне приемлема для большинства задач.
А у out-of-process-сервера — неприемлема?
Serginio1
02.02.2017 13:18Я предлагаю еще вариант использования. Есть альтернатива out-of-process-сервера. Чем это плохо,
Для кого то может быть и неприемлема. При этом нужно отдельно держать http\ws сервер.lair
02.02.2017 13:22Я предлагаю еще вариант использования. Есть альтернатива out-of-process-сервера. Чем это плохо,
Да ничем это не плохо, если это стабильно работает и не требует лишних усилий. Но с моей личной точки зрения, писать в двух экосистемах, если можно обойтись одной — это уже лишние усилия.
При этом нужно отдельно держать http\ws сервер.
Вы правда не знаете других способов межпроцессного взаимодействия, кроме http/ws (и я даже не буду спрашивать, что такое "ws" в этой паре)?
Serginio1
02.02.2017 13:33Угу http/ws это надстройка на Tcp/IP.
Но проблема сериализации, десериализации остается. При этом что HTTP что Вэб сервисы это сериализация через текст. Можно конечно использовать WCF на netNamedPipeBinding, что я часто и делаю? но поверь трудозатраты несоизмеримы с прямым использованиемlair
02.02.2017 14:15Угу http/ws это надстройка на Tcp/IP.
Ну и зачем мне TCIP/IP на одной машине?
Но проблема сериализации, десериализации остается.
А у вас данные между двумя виртуальными машинами совсем без сериализации/десериализации гуляют, да?
Можно конечно использовать WCF на netNamedPipeBinding, что я часто и делаю? но поверь трудозатраты несоизмеримы с прямым использованием
"Прямое использование" — это .net-.net. Да, не вопрос, несоразмеримы. Но то, что сверху — это уже куча трудозатрат, и еще понадобится вложить.
Serginio1
02.02.2017 14:21Ну там просто копирование в одном процессе.
Я показал пример, где различия минимальны. Очень близко к .net-.netlair
02.02.2017 14:24Ну там просто копирование в одном процессе.
Копирование чего, простите?
Я показал пример, где различия минимальны. Очень близко к .net-.net
А можно метрику .net-.net в тех же условиях?
Serginio1
02.02.2017 14:29Пожалуйста, на первом том примере она будет минимальна, ибо основное время это получение данных с сайта.
lair
02.02.2017 14:30"Она будет минимальна" — это ни о чем. Цифры, цифры и еще раз цифры.
Serginio1
02.02.2017 14:33-1У тебя для этого все есть. У меня нет времени.
lair
02.02.2017 14:38Тут уже как-то говорили, что не стоит делать утверждения, которые потом "нет времени" доказывать.
Serginio1
02.02.2017 14:44-1Я тебе сказал, что там основное время это получение данных с сайта. Если ты не веришь, то сам и проверь.
lair
02.02.2017 14:47… если там основное время — это получение данных с сайта, то накладные расходы на межпроцессное взаимодействие никого уже не волнуют. Что, собственно, и требовалось доказать.
Serginio1
02.02.2017 14:51-1Кто то выбирает один процесс, а кто то как ты несколько.
Что и так было ясно.lair
02.02.2017 14:52Вот только у каждого выбора есть своя аргументация. Кто-то аргументирует выбор одного процесса тем, что много накладных расходов… вот только подтвердить это пока не удалось.
Serginio1
02.02.2017 14:57Я аргументиру это тем, что писать на один процес легче.
При этом обработку данных можно вынести в .Net сборку,
а там где скорость не критична можно использовать стандартные библиотеки.
Есть большой выбор в отличие от твоего подхода. За каждой мелочью премся на сервер.lair
02.02.2017 15:01Я аргументиру это тем, что писать на один процес легче.
Легче писать в одной экосистеме. А когда вы пишете чудовище франкенштейна, где в каждый момент времени не понятно, в каком контексте выполняется код, и происходит, когда я записываю в свойство — это не легче.
(мы всего этого успели наестся еще во времена remoting).
Вот я вызвал из клиент-сайда (JS) метод сервер-сайда (.net). Тот создал
ConcurrentDictionary
, запустил отдельный поток для расчетов, и вернул словарь мне. Отдельный поток продолжает писать в словарь. Внимание, вопрос: когда я буду вызывать методы на словаре на стороне JS, они будут видеть "новое" состояние?
Есть большой выбор в отличие от твоего подхода. За каждой мелочью премся на сервер.
Это не "мой подход", а тот подход, который вы мне приписываете. Я этого нигде не говорил.
Serginio1
02.02.2017 15:16-1Угу чем это отличие от HTTP запроса? Я знаю что за классы я использую.
Ну и чем ремотинг от HTTP запроса отличается?
Через await и увидит. Сейчас прикручиваю события.
Ну а как ты же говоришь о превосходстве out процессов. А значит есть сервер.lair
02.02.2017 15:18Угу чем это отличие от HTTP запроса? Я знаю что за классы я использую.
Ну и чем ремотинг от HTTP запроса отличается?Это мы пропустим, как не имеющее отношения к делу.
Через await и увидит. Сейчас прикручиваю события.
В
ConcurrentDictionary
нет ниasync
, ни событий. Я спрашиваю про простоеdict["abc"]
.
Ну а как ты же говоришь о превосходстве out процессов. А значит есть сервер.
Не сервер, а другой процесс. И да, поскольку есть другой процесс, и это эксплицитно, интерфейс меняется с chatty на chunky, поэтому никакого "за каждой мелочью".
mayorovp
02.02.2017 15:34В ConcurrentDictionary нет ни async, ни событий. Я спрашиваю про простое dict["abc"].
Кажется, вы не поняли всего прикола его решения. Нет никакого
dict["abc"]
на стороне JS! Там делается вызовget_Item("abc")
, который прокси-объектом пробрасывается в .NET-код, где и выполняется.lair
02.02.2017 15:39Я, к сожалению, понял. И приводит это к еще более chatty-интерфейсу, чем можно было бы сделать, потому что — если я ничего не пропустил — там вообще нельзя вернуть объект с той стороны. Или структуру все-таки можно?
mayorovp
02.02.2017 15:48Да вы что! У структуры же могут тоже быть методы! И их может понадобится вызвать!
lair
02.02.2017 15:50Значит, я правильно предположил, и только базовые типы (с потерей, я так понимаю, возможности вызывать методы на них). Кстати, а что со строками?
Зато это объясняет, почему нет сериализации (ну, почти нет). Зато любые утверждения о том, что с этим просто работать после этого однозначно в топку.
Serginio1
02.02.2017 16:29-1Я же написал в чем отличие
Прежде всего видим главные отличия от C#. Для получения свойства нужно добавить "_"
let Default = Configuration._Default;
Для вызова асинхронного метода нужно добавить ключевое слово async:
let document = await Context.async.OpenAsync(address);
Для вызова дженерик метода, если нельзя вывести типы по параметрам то аргументы указываем в массиве:
let rows = ApiExtensions.QuerySelectorAll([IHtmlTableRowElement], document, rowSelector);
Ну и главное, нужно вручную удалить ссылку на объект со стороны .Net
Строки копируются.
Я никого не заставляю использовать мой продукт, я делюсь опытом.
Я в 1С прекрасно совмещаю как .Net объекты так и 1С. И никаких неудобств мне это не приносит. А здесь в отличие от 1С я могу прикрутить типизациюlair
02.02.2017 16:43Я же написал в чем отличие
Это все синтаксические отличия. Не интересно. А меня интересует рантайм.
Serginio1
02.02.2017 16:48-1Для начала я давал ссылку на Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
Там расписано как происходит маршалинг
Serginio1
02.02.2017 16:26Структуры боксятся и можно вызывать методы наравне со статческими методами, методы дженериков с автовыводом типа. Поддержка параметров по умлчанию, params
lair
02.02.2017 16:42Структуры боксятся
Все? Вообще все?
let price = netCalculator.getPrice(); if (price >= 100) ...
netCalculator
— это объект от .net, с бизнес-логикой внутри. Какого типаprice
?Serginio1
02.02.2017 16:45Мы говорим про мою проект. Там доступ к свойствам и методам производится через рефлексию. А в рефлексии все боксится
lair
02.02.2017 16:46… а теперь посмотрите на второй вопрос.
Serginio1
02.02.2017 16:57По твое логике int. Они кстати просто копируются.
А для ссылочных типов на стороне JS нужно вызывать Equals или op_ или comparelair
02.02.2017 16:58Но на стороне JS нужно вызывать Equals или op_ или compare
То есть
price
— все-таки неnumber
?Serginio1
02.02.2017 17:04Ну в Net понятия number. Будет либо int либо Double. Другие типы не поддерживаются JS и нужно их оборачивать через ChangeType
lair
02.02.2017 17:06Я, вообще-то, про JS говорю. То, что
GetPrice
возвращаетDecimal
— это данность. А вот какого типа джаваскриптовая переменнаяprice
, в которую вы записали это значение?Serginio1
02.02.2017 17:11Будет объект на стороне Net. Либо можно конвертировать в строку как я это делаю для 1С.
Сейчас посмотрел в текущей реализации делается так
if (Тип == typeof(System.Decimal)) return ((Decimal)obj).ToString(CultureInfo.InvariantCulture);lair
02.02.2017 17:13Будет объект на стороне Net.
С которым нельзя работать, как с числом. Круто, да, очень просто для разработчика на стороне JS.
Сейчас посмотрел в текущей реализации делается так
if (Тип == typeof(System.Decimal)) return ((Decimal)obj).ToString(CultureInfo.InvariantCulture);… и любая проверка типа на стороне JS радостно говорит "да это ж не число". Да?
Serginio1
02.02.2017 17:22Ну что делать если на стороне JS нет понятия decimal/
Он может либо из строки сделать свой Decimal на примере https://github.com/MikeMcl/decimal.js
Можно для Decimal делать конвертацию в этот объект.
Опять же я выдал продукт и собираю мнения, как его улучшить, и нужно ли это вообщеlair
02.02.2017 17:23+1Ну что делать если на стороне JS нет понятия decimal
Работать в тех терминах, которые в JS есть. А если их нет — по крайней мере, не прикидываться, что это "просто".
Опять же я выдал продукт и собираю мнения, как его улучшить, и нужно ли это вообще
Вот вам и говорят, что не нужно.
Serginio1
02.02.2017 17:30-2А кто прикидывается? В чем проблема распарсить строку в объект?
Про ненужно мне давно известно, только странно, что ты это так доказываешь.
Я не успеваю работать.lair
02.02.2017 17:31А кто прикидывается? В чем проблема распарсить строку в объект?
В том, что это лишнее действие, нарушающее, собственно, постулат "работать просто".
Serginio1
02.02.2017 17:39А что делать если JS этого не поддерживает?
Что ты кстати будешь делать через out process?lair
02.02.2017 17:40А что делать если JS этого не поддерживает?
Если не поддерживает, значит, в JS с этим работать неудобно.
Что ты кстати будешь делать через out process?
Получать данные в тех типах, которые JS поддерживает.
Serginio1
02.02.2017 17:59Иииии? Но вот данные храняться в Decimal. Что делать?
Ну и я передаю в виде строки.lair
02.02.2017 18:01Иииии? Но вот данные храняться в Decimal. Что делать?
Преобразовывать в семантически понятный JS тип, очевидно. У меня decimal-данные прекрасно летают между двумя машинами в JSON, и ничего.
Serginio1
02.02.2017 18:16-1Угу JSON это та же строка. Но вот только Decimal и Double разные вещи.
lair
02.02.2017 18:18Угу JSON это та же строка.
Внезапно, нет.
Но вот только Decimal и Double разные вещи.
В .net, да. Но в JS[ON] нет ни того, ни другого, есть number.
Serginio1
02.02.2017 18:23Дааа? а как данные передаются?
Угу и как JS сконвертирует число которое не подходит под Double?lair
02.02.2017 18:25Дааа? а как данные передаются?
В виде строкового представления. Не путайте представление с сообщением.
Угу и как JS сконвертирует число которое не подходит под Double?
А откуда вы вообще взяли понятие Double? Мне всегда казалось, что в JS есть только
number
, и будут использоваться его ограничения.Serginio1
02.02.2017 18:29-1Все я прекращаю диалог. Совершенно нет времени, а диалог совсем не конструктивный. Списибо за общение.
В CEF есть только Double и Intlair
02.02.2017 18:30В CEF есть только Double и Int
… так это проблемы выбранного вами CEF.
Serginio1
02.02.2017 19:48Если Number поддерживает Decimal, то могу передавать на строну CEF бинарные данные, из CEF преобразовывать в объект со строковым представлением числа и флагом, что это Decimal. На стороне JS парсить по аналогии с Json. Нет проблем.
lair
02.02.2017 21:40Да понятно, что "нет проблем", только каждое такое дополнение — это увеличение кода, который надо написать и поддерживать (что понижает "простоту" решения) и потери в производительности (что снижает привлекательность решения с этой точки зрения).
Serginio1
02.02.2017 22:19Все делается в прокси. И я тебе давал ссылку на https://mikemcl.github.io/decimal.js/
Опять же, все вычисления делаются на стороне Net в моем случае, где этих ограничений нет и я могу использовать Decimal как объектlair
02.02.2017 22:35Все делается в прокси.
Который тоже надо писать и поддерживать.
Опять же, все вычисления делаются на стороне Net в моем случае
Так я про это с самого начала и говорил: проще все сделать на .net и не возиться.
Serginio1
02.02.2017 22:37Вогт я написал прокси один раз. Зачем мне его поддерживать
Так сделай на линуксе с UI. И покажи нам всем. Я буду тебе только благодарен.lair
02.02.2017 22:41Вогт я написал прокси один раз. Зачем мне его поддерживать
Потому что нет программы без ошибок, во-первых, и вы никогда не учтете всего с первого раза во-вторых.
Так сделай на линуксе с UI. И покажи нам всем.
Берете asp.net core приложение и открываете в браузере. Enjoy.
Serginio1
02.02.2017 22:51Ну с этой точки зрения тот же CEF постоянно переписывается. Но есть вещи которые не трогаются годами.
Иииии. При этом за всеми данными ползать на сервер. Наша песня гарна нова?
То есть ты не различаешь понятия Декстоп, где мы отвязаны от сервера?
Я понимаю, что моя разработка ни кому не нужна, но я лично никакой радости не имею переводя wpf приложение на asp.net core. Это вы батенька мазохист. В моем случае мне нужно только переписать код формы, а всю остальную логику перекомпилировать под .Net Core. Учитывая, что NetStandard 2 не загорами, то переделки минимальны.lair
02.02.2017 22:55Ну с этой точки зрения тот же CEF постоянно переписывается. Но есть вещи которые не трогаются годами.
Для этого сначала надо потратить годы на то, чтобы их написать. Это не так-то просто.
То есть ты не различаешь понятия Декстоп, где мы отвязаны от сервера?
Да все я различаю, а еще я оцениваю количество нужных усилий.
В моем случае мне нужно только переписать код формы, а всю остальную логику перекомпилировать под .Net Core.
Дадада, конечно, переписать код формы с WPF на JS — это совершенно тривиальное занятие. А маппинг из JS на .net core — так и вовсе волшебным образом появился.
"Только", да.
Serginio1
02.02.2017 23:08Там кода прокси максимум строк 100. Это далеко не годы.
Ты даже не знаком с моей разработкой. Но действуешь по принципу «Не читал, но осуждаю»
Я тебе показал, что код на TS мало отличается от кода на C#. Мало того можно нагенерить ds.ts и наслаждаться Intellisense
Но опять же хозяин барин. Я же в начале статьи написал, что моя разработка мало кому интересна. Ты яркое подтверждение моих слов.lair
02.02.2017 23:11Там кода прокси максимум строк 100.
Это пока не начали всплывать вещи типа "а как сконвертировать decimal", "а что делать с событиями", "а как передать делегат", "а как передать объект".
Я тебе показал, что код на TS мало отличается от кода на C#.
Мы уже выяснили, что нет: как минимум, нет автоматического управления памятью (которое есть и в TS, и в C#), нельзя передавать объекты, и так далее.
А уж в WPF-то вообще не C#-ный код, там биндинги и прочее декларативное счастье. Кстати, для того, чтобы оно работало, нужны события...
Serginio1
02.02.2017 23:21Как передать делегат и объект написано. Ты не читаешь.
События мало отличаются от асинхронных вызовов.
Ну ручное управление мало отличаются от using ты и в C# должен своевременно закрыть ресурсы.
События я как раз пишу. Но ты мне не даешь закончить. Ты хоть работаешь?lair
02.02.2017 23:29+1Как передать делегат и объект написано. Ты не читаешь.
Ну да, написано: передача объектов не поддерживается. Думаю, что и с делегатами то же самое.
Ну ручное управление мало отличаются от using ты и в C# должен своевременно закрыть ресурсы.
Ээээ, серьезно? А то, что ресурсов, требующих
using
, намного меньше, чем обычных объектов, вы не в курсе? Я не пишуusing
для строк, я не пишуusing
для массивов и вообще коллекций — да чего там, я реже пишуusing
, чем не пишу.
События я как раз пишу. Но ты мне не даешь закончить. Ты хоть работаешь?
Конечно, нет. У меня вечер, зачем мне работать?
Serginio1
02.02.2017 23:34Написано, что не поддерживается передача JS объектов. Но название статьи
«TypeScript использование классов .Net Core „
Так, что ты пиши правильно. В JS нет понятия делегатов. Там все свойства и методы тоже.
А, днем чего делал. Заметь мы скоро рекорд побъем по количеству сообщений.
Дай мне написать события.lair
02.02.2017 23:38Написано, что не поддерживается передача JS объектов. Но название статьи
«TypeScript использование классов .Net Core „Понимаете ли, в чем дело… под использованием люди разные вещи понимают. И когда я не могу собрать на лету анонимный объект, чтобы запихнуть его в, скажем, логгер, или в транспорт, или куда-то еще — это ограничение использования. И когда я не могу получить структуру — это тоже ограничение использования. И так далее, далее, далее. Ваш прокси на 100 строк потому и укладывается в 100 строк, что не дает привычных возможностей — только те, которых лично вам, как вы думаете, достаточно.
В JS нет понятия делегатов.
Правда? А коллбек-функция — это что?
Дай мне написать события.
Да я вас вроде за руку и не держу...
Serginio1
02.02.2017 23:50Это функция. Делегат это чисто C# понятие. Вернее правильнее даже мультиделегат.
Если ты посмотришь на Proxy, то заметишь, что есть get, set и apple. А вот вызова как метода объекта нет.
То есть объект может быть функцией и иметь свойства. Свойства могут быть функциями.
Еще раз. Я завожусь и не могу остановиться пока не отвечу.
А вопросы ну никакие, но ничего не могу с собой поделать. Это по сути шизофрения. Но где то она помогает, а где то как с тобой мешает.lair
02.02.2017 23:54Делегат это чисто C# понятие.
На самом деле, нет — как минимум оно используется во всем .net. Но не суть, заменим делегат на колбек. Колбеки передавать можно? А лямбда-выражения?
Serginio1
03.02.2017 00:05Молодец. Прошу прощения неправильно высказался.
Это называется JS функция. После разговора с тобой у меня есть желание на все наплевать и прекратить мои мучения. Наверное не будет.
Зачем? Кому это нужно? Одни минуса. Немного карму подняли и плюсов дали. Огромное спасибо за это коллегам.
Конечно я не брошу и по инерции сделаю и поддерку объектов и методов, для начала только на время вызова функции.
lair
03.02.2017 00:07Это называется JS функция.
Лямбда-выражения? Которые
Expression<Func...>
? Нет, совсем нет.Serginio1
03.02.2017 00:12А при чем тут деревья выражений?
Ладно пошел я спать. И тебе советую. Прошу извинить если не буду больше отвечать. Море дел. Спокойной ночи?lair
03.02.2017 00:13А при чем тут деревья выражений?
При том, что я про них и говорил, когда спрашивал про лямбда-выражения. Когда у тебя ощутимая часть бизнес-кода на них построена — как-то поневоле задумываешься, можно ли их использовать в предлагаемом новом инструменте.
Serginio1
03.02.2017 00:20Да уж не далеко я ушел. Деревья выражений и лямбда выражения это не деревья выражений
Лямбда-выражения
Лямбда-выражение — это анонимная функция, с помощью которой можно создавать типы делегатов или деревьев выражений. С помощью лямбда-выражений можно писать локальные функции, которые можно передавать в качестве аргументов или возвращать в качестве значений из вызовов функций. Лямбда-выражения особенно полезны при написании выражений запросов LINQ.
Класс Expression
Дерево выражений является представлением данных в памяти, лямбда-выражения. Дерево выражений делает структуру лямбда-выражения прозрачной и явной. Можно взаимодействовать с данными в дерево выражения, так же, как и с любой другой структурой данных.
Возможность использовать выражения как структуры данных включает для интерфейсов API получение кода пользователя в формате, который можно проверить, преобразования и обрабатываются особым образом. Например LINQ to SQL реализацию доступа к данным использует это средство для переводы деревьев выражения в инструкции Transact-SQL, которые можно оценить в базе данных.
Многие стандартные операторы, определенные в Queryable класса имеют один или несколько параметров типа Expression.
Давай спокойной ночи.lair
03.02.2017 00:29Лямбда-выражения — это синтаксический сахар, который позволяет получить на выходе как функцию, так и дерево выражений. Меня интересовала вторая ипостась (потому что про первую мы уже выяснили, что ее нет). Когда я пишу типичный код
productRepository.Matching(p => p.Cost < 15)
, я хочу так дальше и писать, а не думать, как мне извратиться в JS/TS.
Впрочем, у меня вообще много монадического кода типа
message .If(c => m?.Credentials?.Certificate?.IsValid) .With(m => m.Registration) .CheckNull(() => new InvalidOperationException()) .Map<Client>() .Select(c => c.Secrets) .Do(Add)
А теперь, значит, выставим
message
как прокси-объект в TS ииии понеслась.
ZOXEXIVO
Смысл показывать здесь код, который никому не пригодится?
Вы, конечно, молодец, но это здесь совершенно не нужно, причем даже вредно — новички могут ужаснуться.