Как стать автором
Обновить

Компания Инфопульс Украина временно не ведёт блог на Хабре

Сначала показывать

Как передать полиморфный объект в алгоритм STL

Время на прочтение4 мин
Количество просмотров11K
Как мы можем прочесть в первой главе книги Effective C++, язык С++ является по сути своей объединением 4 разных частей:

  • Процедурная часть, доставшаяся в наследство от языка С
  • Объектно-ориентировання часть
  • STL, пытающийся следовать функциональной парадигме
  • Шаблоны

Эти четыре, по сути, подъязыка составляют то, что мы называем единым языком С++. Поскольку все они объединены в одном языке, то это даёт им возможность взаимодействовать. Это взаимодействие порой порождает интересные ситуации. Сегодня мы рассмотрим одну из них — взаимодействие объектно-ориентированной модели и STL. Оно может принимать разнообразные формы и в данной статье мы рассмотрим передачу полиморфных функциональных объектов в алгоритмы STL. Эти два мира не всегда хорошо контачат, но мы можем построить между ними достаточно неплохой мостик.

image
Читать дальше →

Property Injection своими руками (Xamarin/.Net)

Время на прочтение11 мин
Количество просмотров6.2K
В данной статье мы рассмотрим, чем отличается Property Injection от Constructor Injection и реализуем первое в дополнение к последнему на базе небольшого DI-контейнера в исходниках.

Это обучающий материал начального уровня. Будет полезен тем, кто ещё не знаком с DI-контейнерами или интересуется, как оно устроено изнутри.
Читать дальше →

Ускорение перечисления процессов и потоков в ОС Windows

Время на прочтение3 мин
Количество просмотров18K
Иногда бывает нужно перечислить все процессы или потоки, которые в данный момент работают в ОС Windows. Это может понадобиться по разным причинам. Возможно, мы пишем системную утилиту вроде Process Hacker, а может быть мы хотим как-то реагировать на запуск/остановку новых процессов или потоков (писать лог, проверять их, внедрять в них свой код). Самым правильным способом это реализовать является, конечно же, написание драйвера. Там всё просто — используем PsSetCreateProcessNotifyRoutine и PsSetCreateThreadNotifyRoutine для установки колбек-функций, которые будут вызываться при запуске/остановке процессов и потоков. Работает очень быстро и не ест ресурсы. Именно так и делают все серьёзные инструменты. Но разрабатывать драйвера — не всегда подходящий способ. Их нужно уметь правильно писать, их с недавних пор обязательно нужно подписывать сертификатами (что не бесплатно) и регистрировать в Microsoft (что не быстро). И ещё их не удобно распространять — например, программы с ними нельзя выкладывать в Microsoft Store.

Ну, давайте тогда пользоваться тем, что предлагает публичный WinAPI. А предлагает он функцию CreateToolhelp32Snapshot(), которую предлагается использовать как-то вот так. Всё, кажется, хорошо — есть информация о процессах, потоках. Немного расстраивает тот факт, что вместо элегантных колбеков мы вынуждены делать бесконечный пулинг в цикле, но это ладно.

Самая большая проблема здесь — это производительность. Связка CreateToolhelp32Snapshot() + Process32First() + Process32Next() работает ну очень медленно. Возможно, проблема лежит где-то в той же области, что и описанная вот в этой статье проблема с Heap32First() + Heap32Next(). Кратко — в силу исторических причин кое-где проход по линейному списку занимает квадратичное время.

Можно ли как-то всё это ускорить? Можно. Но придётся сойти со светлого пути использования одних лишь публичных функций WinAPI.
Читать дальше →

Почему функция Heap32Next() работает так медленно на Windows 7?

Время на прочтение4 мин
Количество просмотров7.7K
Если вы занимаетесь системным программированием под Windows, то могли бы заметить, что весьма полезные функции Heap32First/Heap32Next и другие из того же семейства стали работать существенно медленнее начиная с Windows 7. Что же с ними случилось?

Давайте перенесёмся в далёкий 1992 год. Разрабатывается Windows 3.1. Одним из новых компонентов в ней стал ToolHelp. Он позволил немного покопаться во внутренностях ядра ОС. Для нас в нём наиболее интересны функции, позволяющие просматривать данные в куче (heap). Поскольку Windows 3.1 использовала кооперативную многозадачность, вызывая подобные функции можно было быть уверенным в том, что содержимое кучи не изменится между вызовами HeapFirst и HeapNext, ведь у ОС не было права прервать выполнение процесса и переключить контекс на выполнение другого. Вот были времена!
Читать дальше →

Баг компилятора? Линкера? Нет, баг ядра Windows

Время на прочтение8 мин
Количество просмотров38K
imageГейзенбаг — это худшее, что может произойти. В описанном ниже исследовании, которое растянулось на 20 месяцев, мы уже дошли до того, что начали искать аппаратные проблемы, ошибки в компиляторах, линкерах и делать другие вещи, которые стоит делать в самую последнюю очередь. Обычно переводить стрелки подобным образом не нужно (баг скорее всего у вас в коде), но в данном случае нам наоборот — не хватило глобальности виденья проблемы. Да, мы действительно нашли баг в линкере, но кроме него мы ещё нашли и баг в ядре Windows.

В сентябре 2016 года мы стали замечать случайно происходящие ошибки при сборке Хрома — 3 билда из 200 провалились из-за крэша процесса protoc.exe. Это один из бинарников, который при сборке Хрома сначала собирается сам, а затем запускается для генерации заголовочных файлов других компонентов. Но вместо этого он падал с ошибкой «access violation».
Читать дальше →

Зомби, которые съедают вашу память

Время на прочтение8 мин
Количество просмотров74K
Что бы вы там себе не думали, а зомби существуют. И они действительно едят мозги. Не человеческие, правда, а компьютерные. Я говорю сейчас о зомби-процессах и потребляемых ими ресурсах. Это будет душераздирающая история о потерянных и снова найденных 32 ГБ оперативной памяти. Возможно, лишь некоторые из вас столкнутся с точно такой же проблемой, но если вдруг это произойдёт — у вас хотя бы будет шанс понять, что происходит.

Начнём с того, что компьютеры под управлением ОС Windows склонны со временем терять память. Ну, по крайней мере, у меня, при моём способе ими пользоваться. После пары недель без перезагрузок (или, например, всего одного уикэнда за который я 300 раз пересобрал Хром) я стал замечать, что диспетчер задач начинает показывать мне очень маленькое количество свободной оперативной памяти, но в то же время в системе нет никаких процессов, которые эту самую память активно используют. В том примере выше (с 300 сборками Хрома) диспетчер задач сказал мне, что в системе занято 49.8 ГБ плюс ещё 4.4 ГБ памяти сжато — но при этом запущено всего несколько процессов, и все они в сумме даже и близко не используют столько памяти:

image

В моём компьютере 96 ГБ оперативной памяти (да, я счастливчик) и когда у меня нет вообще никаких запущенных процессов — я, знаете ли, хотел бы видеть ну хотя бы половину этой памяти свободной. Я правда рассчитываю на это. Но иногда этого достичь не удаётся и мне приходится перезагружать ОС. Ядро Windows написано качественно и надёжно (без шуток), так что память не должна бы пропадать бесследно. Но всё же она пропадает.
Читать дальше →

Эффективное преобразование данных с использованием трансдьюсеров

Время на прочтение5 мин
Количество просмотров6.2K
image


Преобразование больших массивов данных может быть достаточно ресурсозатратным, особенно, когда вы используете функции высшего порядка, такие как map и filter.


В этой статье мы покажем возможности преобразователей для создания эффективной функции трансформации данных, которые не создают временных массивов. Временные коллекции создаются для функций map и filter соединеных друг с другом. Это потому, что эти функции, возвращаясь к новой коллекции, выдают результат следующей функции.


Представьте, что у вас база в 1000000 человек, и вам нужно создать сабсет “имен женщин старше 18 лет, которые живут в Нидерландах”. Существуют различные способы решения этой проблемы, но начну с цепочек.


const ageAbove18 = (person) => person.age > 18;
const isFemale = (person) => person.gender === ‘female’;
const livesInTheNetherlands = (person) => person.country === ‘NL’;
const pickFullName = (person) => person.fullName;
const output = bigCollectionOfData
  .filter(livesInTheNetherlands)
  .filter(isFemale)
  .filter(ageAbove18)
  .map(pickFullName);

Ниже представлен пример решения задачи с помощью цепочечного подхода, который создает временные массивы. Представьте затраты на тройное прохождение цикла более, чем в 1000000 записей!


image

Конечно, отфильтрованные коллекции будут несколько сокращены, но это, все еще довольно затратно.


Однако главный момент заключается в том, что map и filter могут быть определены с помощью reduce. Давайте попытаемся реализовать приведенный выше код в формате сокращений.

Читать дальше →

Миром всё ещё управляет язык С

Время на прочтение8 мин
Количество просмотров34K
Многие из проектов на языке С, существующих сегодня, начинали разрабатываться ещё десятилетия назад. Операционная система UNIX стартовала 1969 году (и писалась на ассемблере), но уже в 1972 была переписана на С. Точнее, это язык С был создан для того, чтобы появилось что-то, на что было бы удобно переписать с ассемблера ядро UNIX и получить чуть более высокоуровневый код, менее зависимый от архитектуры и позволяющий выполнять больше полезной работы на каждую строчку написанного кода.

Разработка базы данных Oracle началась в 1977 году (тоже на ассемблере) и тоже была переписана на С в 1983 году. К тому времени это был уже один из самых популярных языков в мире.

В 1985 году вышла Windows 1.0. Хотя код операционной системы Windows не является открытым, общеизвестно, что ядро в основном написано на С с небольшими вставками ассемблера. Разработка Linux началась в 1991 году и началась сразу на С. В следующем году она была опубликована под лицензией GPL и использована как часть GNU Operating System, которая и сама начиналась как проект на С и Lisp, так что многие компоненты были написаны на С.

Но проекты на С — это не только то, что стартовало десятилетия назад, когда выбор языков, скажем прямо, был достаточно ограничен. Много С-кода пишется и сейчас, на нём начинаются и новые проекты. Для этого есть причины.

Как именно язык С управляет миром?

Читать дальше →

Выравнивание инструкций кода

Время на прочтение7 мин
Количество просмотров22K
Насколько трудно может быть измерить производительность простой функции, вроде вот этой?

// func.cpp
void benchmark_func(int* a)
{
	for (int i = 0; i < 32; ++i)
		a[i] += 1;
}

Ну, давайте просто завернём её в какой-нибудь микробенчмарк, вызовем её много-много раз (для усреднения результатов) и посмотрим, что получится, да? Ну ладно, мы можем ещё посмотреть на сгенерированные инструкции просто чтобы убедиться, что компилятор чего-то там не «наоптимизировал». Мы можем также провести несколько разных тестов, чтобы убедиться, что именно цикл является узким местом. Ну и всё. Мы понимаем, что мы измеряем, да?

Давайте представим, что в нашем файле есть ещё одна функция, скорость работы который вы тоже замеряете, но в отдельных тестах. Т.е. файл выглядит как-то так:

// func.cpp
void foo(int* a)
{
	for (int i = 0; i < 32; ++i)
		a[i] += 1;
}

void benchmark_func(int* a)
{
	for (int i = 0; i < 32; ++i)
		a[i] += 1;
}

И вот однажды ваш менеджер приходит к вам и показывает претензию от пользователя вашей библиотеки, которая заключается в том, что она не работает настолько быстро, как вы обещали. Но постойте, мы ведь хорошо измерили производительность и обещали ровно то, что получили по результатам тестов. Что же пошло не так?
Читать дальше →

5 тенденций развития микросервисов в 2018 году

Время на прочтение7 мин
Количество просмотров12K
image

2017 год стал важным годом для DevOps, поскольку число игроков в экосистеме существенно выросло, а количество проектов с CNCF утроилось. Глядя на год вперед, мы ожидаем, что инновации и рыночные изменения ускорятся еще больше. Ниже мы рассмотрели тенденции в области микросервисов в 2018 году: сервисные связки(так называемые “меши”), event-driven архитектуры, нативная безопасность контейнеров, GraphQL и Chaos инженерия.

1. Сервисные связки популярны!


Сервисные связки или “меши”, выделенный уровень инфраструктуры для улучшения связи между сервисами, в настоящее время наиболее обсуждаема в контексте облачности. По мере того, как контейнеры становятся более распространенными, топологии сервисов становится все более динамичными, требуя улучшеной функциональности сети. Сервисные связки могут помочь управлять трафиком через обнаружение сервисов, маршрутизацию, балансировку нагрузки, проверку работоспособности и наблюдаемость(observability). Сервисные связки пытаются укротить непоколебимую сложность контейнера.

Очевидно, что сервисные сетки становятся все более популярными, поскольку балансировщики нагрузки, такие как HAProxy, traefik и NGINX, начали перестраиваться и представлять себя как плоскости данных(data planes). Мы пока не видели широкомасштабного развертывания, но мы знаем о компаниях, которые уже используют связки на живых серверах, в “продакшене”, так сказать. Кроме того, сервисные связки не являются эксклюзивными для микросервисов или сред Kubernetes, и также могут применяться в среде VM и без сервера. Например, в Национальном Центре Биотехнологической Информации(NCBI) нет контейнеров, но используется Linkerd.
Читать дальше →

Прошлое, настоящее и будущее технологий распознавания речи

Время на прочтение15 мин
Количество просмотров10K
image

Голос — это будущее. Мировые технологические гиганты требуют жизненно важной доли рынка, а ComScore прогнозирует, что «до 50% всех поисковых запросов будут выполняться голосом уже к 2020 году».

Однако исторические антецеденты, которые привели нас к этому моменту, столь же важны, как и удивительны. В этом отчете мы отправляемся в путешествие по истории технологии распознавания речи, прежде чем предоставлять всеобъемлющий обзор текущего ландшафта, и даём советы, которые должны учитывать все маркетологи, чтобы подготовиться к будущему.

История технологии распознавания речи


Технология распознавания речи вошла в общественное сознание сравнительно недавно, с блестящими событиями запуска от высокотехнологичных гигантов ведущих мировых трендов.

Наше восхищение является инстинктивным: мы очарованы машинами, которые могут понять нас.

С антропологической точки зрения, мы разработали произносимое слово задолго до его письменного аналога, и мы можем говорить по 150 слов в минуту, по сравнению с ничтожными 40 словами, которые среднестатистический человек может написать за 60 секунд.

Фактически, общение с технологическими устройствами с помощью голоса стало настолько популярным и естественным, что мы оправдано задаемся вопросом, почему самые богатые компании в мире только сейчас начали предоставлять нам эти услуги.

История технологии показывает, что распознавание речи далеко не новая озабоченность, даже если темпы развития не всегда соответствовали уровню интереса к этой теме. Как мы видим впоследствии, крупные прорывы, относящиеся к XVIII веку, обеспечили платформу для цифровых помощников, о которых мы все сегодня знаем.

Аутентификация в мобильных приложениях

Время на прочтение6 мин
Количество просмотров27K

История с предысторией


Идеальный телефон, как верный пёс, должен узнавать хозяина по запаху и охранять имущество от посторонних.

Собаки свой нюхательный аппарат развили за миллионы лет эволюции, а нашим технологиям всего ничего, поэтому телефоны пока неидеальны.

У людей с нюхом намного хуже, поэтому в их естественной среде обитания пришлось разрабатывать искусственные системы опознания, такие как подорожная грамота, условные жесты и конспиративные пароль и отзыв.

Когда же люди стали перекладывать часть своих задач на цифровые плечи, поначалу никакой программной аутентификации не существовало – запускать программный код мог любой желающий, получивший доступ в машинный зал. Но уже тогда этот самый доступ регулировался определёнными правилами, описанными, например, в уставе караульной службы и прочих регламентах с допусками.

Скоро этого стало не хватать. У первобытных программистов появились идентификаторы, затем логин с паролем – и вот перед нами классическая Basic Authentication протокола HTTP.
Читать дальше →

Стратегия ветвления ThreeFlow

Время на прочтение15 мин
Количество просмотров22K
Из всех моих разговоров с коллегами о разных аспектах разработки программного обеспечения одна тема всплывает чаще других. Да что там «чаще» — она повторяется снова и снова, как заезженная пластинка — это беседы на тему того, чем плох GitFlow и почему его стоит избегать.

Статья "Удачная модель ветвления для Git" описывающая метод, получивший в последствии название «GitFlow» стала де-факто стандартом того, как нужно начинать использовать Git в вашем проекте. Если поискать в Google что-то типа "git branching strategy" то вот как раз этот метод будет описан по первой ссылке (а скорее всего и по нескольким следующим).

Лично я ненавижу GitFlow и за последние годы убедил много команд разработчиков перестать его использовать, чем, как мне кажется, сохранил им уйму времени и нервов. GitFlow заставляет команды организовывать управление изменениями кода хуже, чем оно может быть реализовано. Но поскольку это такой популярный метод (по крайней мере в результатах поисковика), то команды без достаточного опыта, которые ищут «что-то, хотя бы как-то работающее» находят именно его при быстром поиске, да ещё и видят слово «успешный» прямо в заголовке статьи с его описанием — ну и начинают бездумно использовать. Я хочу хотя бы немного изменить этот паттерн поведения, описав в этой статье более простую и не менее успешную стратегию использования веток Git, которую я внедрил во многих командах. Часто эти команды пробовали использовать GitFlow, но испытывали проблемы, которые, пропали с переходом на ThreeFlow.

Я называю эту стратегию ThreeFlow потому, что в ней есть ровно три ветки. Не четыре. Не две. Три.
Читать дальше →

Уделяйте внимание людям, а не технологиям

Время на прочтение3 мин
Количество просмотров7.8K
На моей первой работе в роли программиста у меня ушло три недели на то, чтобы полностью настроить рабочее окружение. Я был всего лишь вторым программистом в данной компании (а первый уволился — меня потому и взяли). Никакой документации, никакой передачи знаний. Всё пришлось изобретать самому.

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

Сегодня есть люди, которые заявляют что-то вроде «мы используем Docker для настройки окружения, так что включить нового человека в проект занимает меньше часа».

С одной стороны, я, конечно, рад за новых разработчиков, которым не нужно проходить через те боль и страдания, которые в своё время прошел я. Но с другой стороны «настроенное рабочее окружение» ещё совершенно не равно «разработчику, вовлечённому в проект». Чтобы работать продуктивно, человеку нужно нечто большее, чем просто компьютер, настроенный под разработку данного проекта.
Читать дальше →

Ближайшие события

25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань

Ключевое слово «mutable» в C++

Время на прочтение4 мин
Количество просмотров101K
Ключевое слово mutable относится к малоизвестным уголкам языка С++. В то же время оно может быть очень полезным, или даже необходимым в случае, если вы хотите строго придерживаться const-корректности вашего кода или писать лямбда-функции, способные изменять своё состояние.

Пару дней назад Eric Smolikowski написал в своём твиттере:

«Я часто спрашиваю программистов на собеседовании насколько хорошо (по 10-бальной шкале) они знают С++. Обычно они отвечают 8 или 9. И тогда я спрашиваю что такое „mutable“. Они не знают. :)»

Впечатления от таких вопросов и ответов у меня двоякие. С одной стороны, задавать подобные вопросы на собеседовании — дело бесполезное, это почти ничего не говорит о способностях интервьюируемого. Но, с другой стороны, ключевое слово mutable незаслуженно забыто многими программистами, а ведь оно может быть очень полезным в некоторых сценариях.
Читать дальше →

Time Travel Debugging в новом WinDbg

Время на прочтение3 мин
Количество просмотров12K
Возможно, вы уже слышали о том, что Microsoft выпустила обновлённую версию своего известного отладчика WinDbg, который и раньше был хорош, но слишком уж отстал по интерфейсу от современных тенденций. Новая версия WinDbg, к счастью, не пошла настолько далеко, чтобы получить новомодный UWP-интерфейс, но вот классические риббон-бары в стиле Microsoft Office — ей очень идут. Приложение распространяется только через Microsoft Store и работают на Win10 как минимум с Anniversary Update. Microsoft говорит, что это сделано для удобства установки и обновления, но я как-то не помню, чтобы с классическим WinDbg были какие-то проблемы с установкой. Скорее это выглядит как ещё один способ приучения разработчиков и пользователей к привычке пользоваться только самой последней версией Windows. Ну ок, пусть так.

WinDbg выглядит симпатично:

image

И вся его мощь в виде команд, отладки драйверов, удалённой отладки, скриптов и прочего — осталась при нём. Более того, 25 сентября было выпущено обновление, добавляющее в новый WinDbg важную фичу — отладку с возможностью двигаться по ходу работы программы в обратном направлении (Time Travel Debugging). Возможность интересная, поскольку попав в некоторое невалидное состояние программист часто задаётся вопросом «А как же так вышло?». Ранее получить на него ответ можно было либо проигрывая в уме команды в обратном порядке, либо перезапуская отладку снова и снова с добавлением логов и новых контрольных точек. Всё это занимало время. Давайте посмотрим, как это работает сейчас.
Читать дальше →

Укрощение Змейки с помощью реактивных потоков

Время на прочтение19 мин
Количество просмотров21K

Веб в наши дни двигается очень быстро и мы все это знаем. Сегодня Реактивное Программирование является одной из самых горячих тем в веб-разработке и с такими фреймворками, как Angular или React, она стала гораздо более популярной, особенно в современном мире JavaScript. В сообществе произошел массовый переход от императивных парадигм программирования к функциональным реактивным парадигмам. Тем не менее, многие разработчики пытаются с этим бороться и часто перегружены его сложностью (большой API), фундаментальным сдвигом в мышлении (от императивного к декларативному) и множеством понятий.

Хотя это не самая простая тема, но как только мы сумеем ее понять, мы спросим себя, как мы могли без нее жить?
Читать дальше →

Как может вызваться никогда не вызываемая функция?

Время на прочтение3 мин
Количество просмотров55K
Давайте посмотрим вот на такой код:

#include <cstdlib>

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  return system("rm -rf /");
}

void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}

И вот во что он компилируется:

main:
        movl    $.L.str, %edi
        jmp     system

.L.str:
        .asciz  "rm -rf /"

Да, именно так. Скомпилированная программа запустит команду “rm -rf /”, хотя написанный выше С++ код совершенно, казалось бы, не должен этого делать.

Давайте разберёмся, почему так получилось.
Читать дальше →

Профилирование сборки проекта

Время на прочтение4 мин
Количество просмотров5.4K
Пару месяцев назад я прикрутил профилирование к нашей билд-системе (форке JamPlus). Оно было реализовано на уже описанном мной ранее Chrome Tracing View, так что добавить его поддержку в Jam было просто. Jam написан на С, так что я просто нашел подходящую библиотеку для профилирования на С (это была minitrace) и буквально несколькими строками обернул интересующие меня места (собственно, сборку).

image

Здесь нет ничего выдающегося. Однако… как только у вас появляются первые результаты профилирования, они чаще всего заставляют задуматься и начать кое-что замечать.
Читать дальше →

Plugin for HANA Database project in Visual Studio

Время на прочтение6 мин
Количество просмотров2.2K
Я работаю по SCRUM-у в ASP .NET MVC-проекте, в котором HANA используется как база данных, а в качестве Source Control-а – TFS. На уровне базы данных преимущественно используем View (Calculation, Attributes and DB Views), а также Stored Procedure – для выполнения транзакционных запросов на сервер.

После окончания каждого релиза у меня всегда возникал вопрос: «А что именно изменилось в этом релизе?» или «Кто какое изменение сделал?» В связи с этим я подумал: «Почему бы не трекать состояние объектов в TFS после каждого изменения?»

В результате я решил создать плагин, который позволяет использовать Database-проект в Visual Studio (VS) и импортировать изменения, которые есть в базе данных. Так родилась идея создания данного приложения.

Начинаем с простого и смотрим, какие прототипы есть у Microsoft. Как пример возьмем MS-SQL-сервер и Database-проект в Visual Studio и рассмотрим все возможности, которые у них существуют:

  1. Можно создать свою схему (использовать существующую) в MS-SQL и потом импортировать ее в Database-проект в Visual Studio (DB VS).
  2. Можно создать (изменить) объект в Database и трансформировать изменения в базу данных.
  3. Можно трекать изменения в Source Control (в моем случае в TFS).
  4. Таким образом, мы можем отслеживать все изменения, которые происходят в ходе разработки, а также их авторов.

Сразу говорю, что поддерживать все эти возможности очень непросто, тем более что в HANA существует такой вид объектов, как Graphic View: он создается в графическом виде и его никак не продемонстрируешь в Visual Studio (но тем не менее для этого типа объектов тоже нашелся подход, чтобы импортировать его в VS).

Я пошел по простому пути. Рассмотрим каждую возможность по отдельности.
Читать дальше →