Программная логика приложений для Windows 8 и их взаимодействие с системой

Компоненты WinRT: введение

Разбить на страницы
Показывать лекцию целиком

Введение

Материалы к лекциям 13, 14 Вы можете скачать здесь.

В самом начале этого курса мы узнали, что приложения для Магазина Windows могут быть написаны с использованием различных языков и технологий представления данных По большей части мы говорили о возможности выбора: вы выбираете модель, которая наилучшим образом подходит к вашему опыту и нуждам вашего приложения.

В то же время, мы иногда встречались с ситуациями, где применимо, а порой и необходимо применение нескольких языков. В Главе 1 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript" я говорил о том, что приложение, на самом деле, может быть написано на нескольких языках. В Главе 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" я упоминал о том, что получение доступа к API баз данных, помимо IndexedDB, может быть реализовано с использованием WinRT-компонента. В Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" мы видели, что JavaScript может быть не лучшим выбором, если нужна подпрограмма, которая обрабатывает пиксельные данные. И, в Главе 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", мы встретились с Notifications Extensions Library, с весьма полезным набором кода, написанном с использованием C#, упрощающим создание полезных данных XML из JavaScript. Знаем мы и о фоновых задачах, которые могут быть написаны на языках, отличающихся от языка основного приложения.

Учитывая основное ограничение, в соответствии с которым приложение, использующее для презентационного уровня HTML и CSS не может делить рабочую поверхность вывода данных с WinRT-компонентами, написанными на других языках, дизайн WinRT делает подход с использованием нескольких языков вполне реальным для использования. WinRT-компоненты, написанные на любом языке, доступны из других языков посредством уровня проекции, который транслирует интерфейс компонента в среду, соответствующую целевому языку. Вся WinRT написана так, и пользовательские WinRT-компоненты используют преимущества того же механизма (Скоро мы увидим основные характеристики проекции JavaScript).

Для вас это означает, что создавая приложения с помощью HTML, CSS, и JavaScript, вы можете реализовывать различные части приложения на языке, который лучше подходит для конкретной задачи или необходим технически. Как динамический язык, JavaScript показывает свою мощь в простом совмещении функциональности, предоставленной другими компонентами, которые выполняют различные специальные задачи (наподобие захвата данных с камеры, фоновой передачи данных и так далее). Подобные компоненты часто лучше писать на других языках, таких, как C++, где скомпилированный код исполняется непосредственно на центральном процессоре, вместо того, чтобы проходить сквозь уровни времени выполнения, как JavaScript.

На самом деле, когда мы говорим о приложениях на смешанных языках, мы действительно можем смешивать разнообразные языки . Вы можете написать на C# компонент, который вызывается из JavaScript, но этот компонент, в свою очередь, может вызвать компонент, написанный на C++. Опять же, это означает, что вы можете использовать язык для любой конкретной задачи включая возможность создания собственных асинхронных операций (то есть, для исполнения кода в параллельных потоках, которые не блокируют поток пользовательского интерфейса). В этом контексте так же полезно продумать, что это значит по отношению к рабочим веб-процессам, к тому, чем, если нужно, могут пользоваться приложения для Магазина Windows.

В этой лекции мы рассмотрим различные причины, по которым вы можете использовать подход применения смешанных языков в приложении. Затем мы пройдёмся по паре кратких руководств по C# и C++, чтобы вы понимали структуру компонентов, написанных на них, как они выглядят в JavaScript, их ключевые концепции и терминологию. В лекции 14 мы рассмотрим примеры различных сценариев, не входя слишком глубоко в технические подробности. Я решил так поступить, потому что существует прекрасная документация, посвященная таким подробностям. В особенности это следующие материалы:

  • "Создание компонентов среды выполнения Windows в C++" (http://msdn.microsoft.com/library/windows/apps/hh441569.aspx)
  • "Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh755833.aspx)
  • "Создание компонентов среды выполнения Windows в C# и Visual Basic" (http://msdn.microsoft.com/library/windows/apps/br230301.aspx) (и подразделы)
  • "Пошаговое руководство. Создание простого компонента в C# или Visual Basic и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh779077.aspx)
  • Не позволяйте словам "простой" и "основной" в названиях материалов удерживать вас от их прочтения: это всесторонние руководства, которые рассматривают множество мелких деталей работы с типами данных наподобие векторов, карт и наборов свойств. Они рассказывают об объявлении событий, создании асинхронных операций и о том, Как всё это выглядит в JavaScript. Кое-что из этого мы рассмотрим здесь, но имея такие замечательные руководства, мы займёмся здесь, в первую очередь, вопросами о том, почему нужны подобные компоненты, поговорим о проблемах, которые можно решить с их помощью. Кроме того, я хочу сохранить ваши силы для последней лекции курса, где мы поговорим о том, как представить ваше приложение миру после того, как вы решите все эти проблемы!

    Примечание. По необходимости, в этой лекции я должен предположить, что у вас есть некоторое понимание языков C# и C++, так как здесь мы не будем рассматривать основы. Если эти языки полностью новы для вас, потратьте несколько часов на то, чтобы с ними познакомиться. Это значительно улучшит ваше продвижение по данной лекции.

    Выбор подхода с использованием смешанных языков (и рабочих веб-процессов)

    Выбор подхода с использованием смешанных языков (и рабочие веб-процессы)

    Есть много причин для того, чтобы применить подход с использованием смешанных языков в вашем приложении. Этот подход подразумевает применение любого количества WinRT-компонентов, написанных на C#, VisualBasic (здесь и далее просто VB) и/или на C++:

  • Вы можете выполнить некоторые задачи быстрее с кодом, обладающим более высокой производительностью. Это может уменьшить использование памяти, а так же потребовать меньше процессорного времени и мощности, что важно для фоновых задач, для которых применяется квотирование процессорного времени. Вы можете сделать гораздо больше за 2 секунды на C++, чем на JavaScript, так как в этом случае не используются вспомогательные подсистемы.
  • C#, Visual Basic, и C++ имеют доступ к значительному набору дополнительных API, которые недоступны JavaScript. Они включают в себя API .NET, Win32, b COM (Component Object Model), включая не относящиеся к пользовательскому интерфейсу возможности DirectX, такие как XAudio и Xinput. Мы увидим ряд примеров в разделе этой лекции "Доступ к дополнительным API".
  • Доступ к другим API, кроме того, может понадобиться для использования библиотек .NET/Win32/COM сторонних разработчиков, и, кроме того, даёт вам возможность повторно использовать существующий код, который может у вас быть на языках C#, VB, или C++. В материале "Разработка Bing Maps Trip Optimizer — приложения для Магазина Windows — с помощью JavaScript и C++" (http://msdn.microsoft.com/library/windows/apps/hh699893.aspx) показан полный сценарий подобной работы, в частности, сценарии миграция элемента управления ActiveX в компонент WinRT, и, в итоге, возможность использования его в приложении, так как ActiveX напрямую не поддерживается. (Мы не будем возвращаться к данному сценарию в дальнейшем).
  • Может быть легче работать в процедурах, вовлекающих использование множества асинхронных операций с использованием ключевого слова await в C# и Visual Basic, так как подобный подход имеет более четкую структуру, нежели использование promise-объектов. Пример этого можно найти в Главе 6, в приложении "Here My Am!", где функция transcodeImage, написанная в Главе 2 на JavaScript переписана с использованием C# (смотрите Utilities.ImageConverter.TranscodeImageAsync в проекте Utilities).
  • WinRT-компоненты, написанные на C++ более эффективны при необходимости скрытия ценного кода приложения, чем JavaScript и .NET-языки. Хотя интерфейс компонента это не скроет, для того, чтобы понять его внутреннее устройство реверс-инженерам потребуется несравненно больше усилий.
  • Компоненты WinRT – это лучший способ создания библиотек, не имеющих отношения к пользовательскому интерфейсу, которые другие разработчики смогут использовать в избранной ими языковой среде, или вы сами сможете использовать во множестве других собственных проектов, как библиотеку Notifications Extensions, которую мы видели ранее. В этой связи, посмотрите материал "Практическое руководство. Создание пакета средств разработки программного обеспечения" (http://msdn.microsoft.com/library/windows/apps/hh768146.aspx), который включает в себя подробности о том, как нужно структурировать компоненты для того, чтобы интегрировать их с Visual Studio.
  • Хотя вы можете использовать в JavaScript рабочие веб-процессы (http://msdn.microsoft.com/library/windows/apps/hh767434.aspx) для того, чтобы исполнять код в различных потоках, компоненты WinRT могут быть гораздо более эффективными для пользовательских асинхронных API. Другие языки так же могут выполнять определенные задачи более продуктивно, как, например использование запросов, встроенных в язык (LINQ) из C#/VB, создание параллельных циклов в C#/C++, использование C++ Accelerated Massive Parallelism (AMP) (http://msdn.microsoft.com/library/hh265136.aspx) для использования возможностей графического процессора и так далее.
  • Опять же, компоненты могут использовать друг друга – у компонентной системы нет с этим проблем. Я снова об этом говорю, так как это связано с последним из вышеприведенных пунктов – рабочими веб-процессами и исполнением кода вне потока пользовательского интерфейса.

    Вы можете использовать рабочие веб-процессы (или просто рабочие процессы) в приложениях для Магазина Windows. На самом деле, Visual Studio предоставляет шаблон элемента, предназначенный именно для этого: щёлкните правой кнопкой мыши по проекту в Обозревателе решений Visual Studio, выберите команду Добавить > Создать элемент (Add > New Item) и затем выберите Выделенный рабочий процесс (Dedicated Worker). Так же я покажу в этой лекции упражнение "JavaScript Workers" ("Рабочие процессы JavaScript"). Настоящий их недостаток, в сравнении с WinRT-компонентами, это то, что обмен данными между рабочим процессом и потоком основного приложения реализуется полностью с помощью дискретного механизма postMessage. Данные, передаваемые между приложением и рабочим процессом должны быть выражены в виде свойств сообщения. Другими словами, рабочий процесс построен с использованием клиент-серверной архитектуры, а не в виде объекта со свойствами, методами и событиями, хотя вы можете создавать структуры для подобных вещей с помощью сообщений.

    Это означает то, что использование рабочих процессов с несколькими асинхронными операциями может вызвать путаницу. Для сравнения, методы, которые мы видели для работы с асинхронными операциями WinRT – promise-объекты WinJS, имеют больше возможностей, особенно тогда, когда вам нужно объединить асинхронные операции в цепочку или использовать вложенные асинхронные операции, вызывают исключения и сообщают о процессе выполнения операции. К счастью, есть возможность заключить рабочий процесс в оболочку promise-объекта, как мы увидим в разделе "Рабочие процессы JavaScript"

    Еще интересно подумать о том, как вы можете использовать рабочие процессы, написанные на JavaScript для того, чтобы они работали в качестве агентов, делегируя задачи WinRT-компонентам. Рабочие процессы JavaScript, таким образом, служат в качестве связующего звена для сбора воедино результатов работы компонентов, возвращая общий результат основному приложению через postMessage.

    В том же духе, если у вас есть некоторый опыт работы в .NET-языках наподобие C# и Visual Basic вместе с C++, вы узнаете, что соответствующие им модели программирования имеют собственные сильные и слабые стороны. Так же, как вы можете получить преимущества от динамической природы JavaScript в тех областях, для которых он подходит лучше всего, вы можете использовать управляемую природу .NET, которая избавит вас от различных проблем наподобие управления памятью и со ссылками, и, в свою очередь, использовать C++ там, где нужен наиболее производительный код.

    Коротко говоря, поток пользовательского интерфейса приложения на JavaSctipt делегирует задачи рабочим процессам на JavaScript, которые затем делегируют некоторые задачи компонентам, написанным на C#, который затем могут передать наиболее интенсивные задачи другим компонентам, написанным на C++. На самом деле, комбинация рабочих процессов с компонентами WinRT даёт вам отличную гибкость в реализации приложений.

    Примечание. Единственный потенциальный недостаток использования в приложениях компонентов WinRT, написанных на C++ заключается в том, что, в то время, как JavaScript и .NET-языки (C# и C++) архитектурно-нейтральны и могут исполняться на любом процессоре, компоненты на C++ должны быть отдельно скомпилированы для архитектур x86, x64 и ARM. Это подразумевает наличие у приложения в Магазине Windows трех различных пакетов. Однако, пакеты для архитектуры x86 будут так же работать и на процессорах с архитектурой x64, что устраняет необходимость в пакете для одной из целевых архитектур, если создание специального пакета для x64 не способно принести реального прироста в производительности.

    Краткие руководства: создание и отладка компонентов

    Если вы задались целью добавить WinRT-компонент в своё проект, легче всего начать с шаблона элемента Visual Studio. Щёлкните правой кнопкой по решению (не по проекту) в Обозревателе решений Visual Studio, выберите команду Добавить > Создать проект (Add > New Project) и затем выберите элемент Компонент среды выполнения (Windows Runtime Component) из группы Visual Basic, Visual C#, или Visual C++ > Магазин Windows (Windows Store), как показано на рис 13.1 для проекта на C#.

    (рис 13.1) Возможности Visual Studio по созданию проекта Компонент среды выполнения (Windows Runtime Component) на C#. Похожее можно сделать на Visual Basic и Visual C++

    В следующих разделах мы посмотрим и на вариант C# и на вариант C++, улучшая упражнение, посвященное манипуляции изображениями (Image Manipulation), работа над которым начата в Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", которое можно найти в дополнительных материалах к лекции. Последний вариант этого приложения выполнял конверсию изображения в оттенки серого и затем отображал его в элементе canvas. Мы выполняли манипуляции в JavaScript-коде, который, на самом деле, работал достаточно хорошо, но мы можем улучшить работу этого алгоритма. Наши первые два WinRT-компонента, таким образом, реализуют одну и ту же задачу с использованием C# и C++. Имея все три реализации мы получим возможность сравнить относительную производительность, что мы и сделаем в разделе "Сравнение результатов".

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

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

    Врезка: WinRT-компоненты против библиотек классов (C#/VB) и динамически связываемых библиотек

    В диалоговом окне Добавить новый проект Add New Project) в Visual Studio вы можете заметить параметры для создания библиотек классов (Class Library), показанные для Visual C# и Visual Basic и опцию для создания DLL (dynamic-link library, динамически связываемой библиотеки) показанной для C++. Они, соответственно, эффективно компилируются в сборки и в DLL, которые имеют сходство с компонентами WinRT. Разница, однако, заключается в том, что их можно использовать лишь из того же языка. Библиотека класса (Class Library) (.NET-сборка) может быть использована приложениями, написанными на .NET-языках, но не из JavaScript. Похожим образом, DLL можно вызвать из C++ и .NET-языков (в последнем случае – только с помощью механизма P/Invoke), но не доступны для JavaScript. Для этой цели подходят лишь компоненты WinRT.

    Иногда нужны простые DLL, как в случае с мультимедиа-расширениями, которые предоставляют пользовательские аудио/видео эффекты или кодеки. Это не WinRT-компоненты, так как у них отсутствуют необходимые метаданные, благодаря которым они могут быть спроецированы в другие языки. Подробности об использовании DLL в мультимедиа-платформах можно найти в материале "Применение расширений мультимедиа" (http://msdn.microsoft.com/library/windows/apps/hh700365.aspx) и в примере "Расширения мультимедиа" (http://code.msdn.microsoft.com/windowsapps/Media-extensions-sample-7b466096).

    Краткое руководство №1: Создание компонента на C#

    Как показано на рис 13.1, я добавил WinRT-компонент на C# в упражнение "Image Manupulation", назвав компонент PixelCruncherCS. Как только данный проект будет добавлен в решение, в этом проекте окажется файл, который называется class1.cs, который содержит пространство имен PixelCruncherCS с одним классом:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace PixelCruncherCS
    {
    public sealed class Class1
    {
    }
    }

    Ничего особенного в этом коде сейчас нет, но этого достаточно, чтобы мы могли начать. Вы можете видеть, что класс объявлен как public, то есть, он будет виден приложениям, которые будут использовать компонент. Кроме того, использование модификатора sealed говорит нам о том, что запрещено наследование от этого класса (по причине текущих технических ограничений). Оба ключевых слова необходимы для WinRT-компонентов в Windows 8. (В Visual Basic эти два ключевых слова выглядят как Public и NotInheritable.)

    Для проверки взаимодействия с JavaScript, я дал классу и его файлу более подходящие имена (Tests и grayscale.cs, с учетом того, что мы собираемся реализовать) и создал тестовй метод и тестовое свойство:

    public sealed class Tests
    {
    public static string TestMethod(Boolean throwAnException)
    {
    if (throwAnException)
    {
    throw new System.Exception("Tests.TestMethod was asked to throw an exception.");
    }
    return "Tests.TestMethod succeeded";
    }
    
    public int TestProperty { get; set; }
    }

    Если сейчас вы выполните построение решения (Построение > Построить решение) (Build > Build Solution), вы увидите, что результат построения проекта PixelCruncherCS – это файл, который называется PixelCruncher.winmd. Расширение .winmd применяется для метаданных Windows (Windows Metadata): WinRT-компонент, написанный на C# это .NET-сборка, которая включает расширенные метаданные, которые называются двоичным интерфейсом приложения (Application Binary Interface или ABI). Это то, что сообщает Windows полную информацию о том, что компонент может предоставить другим языком (его общедоступные классы) и так же то, что предоставляет возможности IntelliSense для этого компонента в Visual Studio и Blend.

    В приложении вы должны добавить ссылку на компонент, чтобы он стал доступен в пространстве имен JavaSctipt, так же, как API WinRT. Для того, чтобы это сделать, щёлкните правой кнопкой мыши Ссылки (References) в проекте приложения на JavaScript, выберите команду Добавить ссылку (Add Reference). В появившемся диалоговом окне, выберите в правой его части Решение (Solution) и установить флаг около проекта WinRT-компонента, как показано на рис 13.2.

    (рис 13.2) Добавление ссылки на WinRT-компонент, который расположен в том же решении, что и приложение

    При написании кода, который ссылается на компонент, всегда начинают с пространства имен, в данном случае – PixelCruncherCS. Как только вы введете это имя и точку, появится подсказка IntelliSense, показывающая классы, доступные в данном пространстве имен:

    Как только вы добавить имя класса и введете еще одну точку, подсказка IntelliSense покажет его методы и свойства:

    Примечание. Если вы внесли изменение в пространство имен, класс, или в другие имена в проекте WinRT-компонента, вам нужно выполнить команду Построение > Построить решение (Build > Build Solution) для того, чтобы увидеть обновленные имена в IntelliSense.

    Здесь вы можете видеть, что хотя имя метода в коде C# выглядит как TestMethod, оно проецируется в JavaScript как testMethod, соответствуя соглашениями об именовании, принятым в JavaScript. Это изменение регистра, применяется автоматически с помощью уровня проекции JavaSctipt для всех WinRT-компонентов, включая те, которые находятся в вашем приложении.

    Кроме того, обратите внимание на то, что IntelliSense показывает здесь лишь testMethod, но не testProperty (регистр символов имени которого так же изменен). Почему это так? Потому, что в C# TestMethod объявлен как static, что подразумевает возможность его исполнения без предварительного создания экземпляра объекта этого класса:

          var result = PixelCruncherCS.Tests.testMethod(false);

    С другой стороны, testProperty, но не testMethod, доступно для конкретного экземпляра:

    Я, кстати, настроил TestMethod на выдачу исключения при его вызове, таким образом, мы можем увидеть, как они обрабатываются в JavaScript с помощью блока try/catch:

    try {
    result = PixelCruncherCS.Tests.testMethod(true);
    } catch (e) {
    console.log("PixelCruncherCS.Tests.testMethod threw: '" + e.description + "'.");
    }
          

    Испытаем этот код. Привяжем его к какой-нибудь кнопке (смотрите функцию testComponentCS в файле js/default.js), установим точку останова в верхней части и запустим приложение в отладчике. Когда сработает точка останова, выполните пошаговое исполнение кода, используя возможность Visual Studio Шаг с заходом (F11 или Отладка > Шаг с заходом (Debug > Step Into)). Обратите внимание на то, что вы не попадаете в код C#, это последствие того факта, что Visual Studio может отлаживать либо скриптовый, либо управляемый (C#) / машинный (C++) код в одном сеансе отладке, но, к сожалению, не то и другое вместе. Очевидно, при существовании подобных ограничений, вы можете воспользоваться консольным выводом как инструментом отладки.

    Для того, чтобы задать режим отладки, щёлкните правой кнопкой мыши по проекту в Обозревателе решений, откройте окно свойств, в его левой части выберите Отладка (Debugging) и затем установить Тип отладчика (Debugger Type) в правой части окна так, как показано на рис 13.3. Для отладки C# выберите Только управляемый код (Managed Only) или Смешанный (Управляемый и машинный) ( Mixed (Managed and Native)). Затем установите точки останова в коде компонента и перезапустите приложение (нужно выполнить перезапуск для того, чтобы эти изменения возымели действие). Когда вы вызываете компонент из JavaScript, срабатывают точки останова в коде компонента.

    (рис 13.3) Типы отладчика в Visual Studio

    Когда основные механизмы отработаны, теперь мы готовы к тому, чтобы добавить сюда реальную функциональность. Первый шаг заключается в том, чтобы понять, как передать WinRT-компоненту массив с данными пикселей из элемента canvas и как получить из него результаты работы. В коде JavaScript (в методе copyGrayscaleToCanvas) имеется массив pixels, который содержит исходные пиксельные данные, и еще один пустой массив в imgData.data, где imgData получают следующим образом:

          var imgData = ctx.createImageData(canvas.width, canvas.height);

    Мы можем напрямую передать оба этих массива в компонент. Здесь есть лишь ограничение на то, что массивы, переданные компоненту WinRT могут быть использованы либо для ввода, либо для вывода, но не для того и другого одновременно – компонент просто не может манипулировать массивом по месту его размещения. Материал "Передача массивов в компонент среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh975353.aspx) содержит описание тонкостей этого процесса. Для того, чтобы сократить усилия, мы, к счастью, уже являемся обладателями входного массива, pixels, и выходного массива – imgData.data, которые мы можем передать методу компонента:

         var pc = new PixelCruncherCS.Grayscale();
          pc.convert(pixels, imageData.data); // Обратите внимание на изменение имени метода

    Примечание. Подход, показанный здесь и в вышеупомянутом материале применим лишь для синхронных методов в WinRT-компонентах. Массивы нельзя применять с асинхронными операциями. Смотрите ниже раздел "Ключевые концепции и подробности" для того, чтобы узнать подробности об этом.

    Для того чтобы принять этот массив в C#, оба параметра должны быть соответствующим образом маркированы в зависимости от их направления. Подобная маркировка в C# называется атрибутом (attributes), не спутайте их с похожим понятием в HTML, атрибуты приводятся в прямоугольных скобках, [ ] перед именем параметра. В данном случае атрибуты выглядят как [ReadOnlyArray()] и [WriteOnlyArray()] и предшествуют параметрам (методы ReadOnlyArray и WriteOnlyArray можно найти в пространстве имен System.Runtime.InteropServices.WindowsRuntime). Таким образом, объявление метода в компоненте, который, опять же, должен быть общедоступным, выглядит так, с учетом того, что сейчас в качестве возвращаемого типа используется логический (Boolean):

    public Boolean Convert([ReadOnlyArray()] Byte[] imageDataIn, 
          [WriteOnlyArray()] Byte[] imageDataOut)

    Когда всё это готово, довольно просто конвертировать код JavaScript для перевода изображения в оттенки серого, в C#-код:

    public Boolean Convert([ReadOnlyArray()] Byte[] imageDataIn, 
          [WriteOnlyArray()] Byte[] imageDataOut)
          {
          int i;
          int length = imageDataIn.Length;
          const int colorOffsetRed = 0; const int
          colorOffsetGreen = 1; const int
          colorOffsetBlue = 2; const int
          colorOffsetAlpha = 3;
    
          Byte r, g, b, gray;
    
          for (i = 0; i < length; i += 4)	
    {	
    r = imageDataIn[i + colorOffsetRed];	
    g = imageDataIn[i + colorOffsetGreen];
    b = imageDataIn[i + colorOffsetBlue];
    
    //Присовение каждого rgb-значения значению яркости для изображения в оттенках серого 
    gray = (Byte)(.3 * r + .55 * g + .11 * b);
    
    imageDataOut[i + colorOffsetRed] = gray; imageDataOut[i + 
    colorOffsetGreen] = gray; imageDataOut[i + 
    colorOffsetBlue] = gray;
    imageDataOut[i + colorOffsetAlpha] = imageDataIn[i + colorOffsetAlpha];
    }
    
    return true;
    }

    Врезка: особенности отладки

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

    Когда я работал над упражнениями для этой лекции, я обнаружил, что я разработал шаблон работы, когда я устанавливал точки останова и в JavaScript и в C#, после чего просто переключал режимы отладки и перезапускал приложение. То есть я запускал приложение для отладки скрипта и пошагово исполнял код (перезапуская программу, когда находил и исправлял ошибки), до тех пор, пока не стало понятно, что какая-то проблема есть в компоненте. Тогда я переключил режим отладки на работу с управляемым кодом и перезапустил приложение. С использованием всех точек останова, установленных в компоненте, я мог точно так же исполнять приложение, пошагово выполнять код компонента, исправлять ошибки и перезапускаться для повторения процесса. Немного позже, однако, я нашёл другую проблему со стороны JavaScript, тогда я переключил режим отладки и снова перезапустился. Это не так удобно, как возможность переходить из одного кода в другую, но, в конце концов, это работало достаточно хорошо.

    Одно из предложений, которое могло бы ускорить процесс, заключается в написании некоторого кода для целей тестирования в C# или VB, который позволит вам напрямую обращаться к коду компонента. Вы можете встроить подобную тестировочную процедуру напрямую в код компонента, привязать кнопку или две к её методам в JavaScript. Таким образом вы, возможно, уменьшите количество работы по тестированию кода компонента, будучи уверенными в его нормальной работе перед вызовом его из JavaScript с реальными данными.

    Краткое руководство №2: создание компонента в C++

    Для того, чтобы продемонстрировать работу с WinRT-компонентами, написанными на C++, я так же добавил проект в упражнение "Image Manipulation example", назвав его PixelCruncherCPP. Основной код там такой же самый, как и в примере на C# - процедура манипуляции с пикселями довольно универсальна! Необходимая структура кода компонента, с другой стороны, уникальна для языка: у C++ есть собственные особенности.

    Как мы поступали с C#, давайте добавим новый проект, использовав шаблон Visual C++ > Компонент среды выполнения (Visual C++ > Windows Runtime Component) и имя PixelCruncherCPP. После переименования Class1 в Tests в коде и переименования файла, мы видим следующий код в заголовочном файле (я назвал его grayscale.h, и опустил директивы компилятора):

    namespace PixelCruncherCPP
    {
    public ref class Tests sealed
    {
    public:
    Tests();
    };
    }

    Здесь мы видим, что класс должен быть объявлен public ref и sealed, с общедоступным конструктором. Всё это вместе даёт возможность создать экземпляр объекта в качестве WinRT-компонента. В Tests.cpp мы видим следующее:

    #include "pch.h"
    #include "Grayscale.h"
    
    using namespace PixelCruncherCPP;
    using namespace Platform;
    
    Tests::Tests()
    {
    }

    Опять же, не так уж и много всего, но вполне достаточно (Документация по пространству имен Platform (http://msdn.microsoft.com/library/windows/apps/hh710417.aspx), кстати, это часть Справочника по языку Visual C++.) Для того, чтобы реализовать то же самое, что мы сделали для C++, добавим статический тестовый метод и соответствующее свойство. Определение класса выглядит так:

    public ref class Tests sealed
    {
    public: Tests();
    
    static Platform::String^ TestMethod(bool throwAnException);
    property int TestProperty;
    };
    
    А вот код для TestMethod:
    String^ Tests::TestMethod(bool throwAnException)
    {
    if (throwAnException)
    {
    throw ref new InvalidArgumentException;
    }
    return ref new String(L"Tests.TestMethod succeeded");
    }

    Когда вы выполните построение этого проекта (Построение > Построить проект (Build > Build Solution)), вы увидите, что теперь у нас имеются файлы PixelCruncherCPP.dll и PixelCruncherCPP.winmd. В то время, как C#-сборка может содержать и код и метаданные, C++-компонент компилируется в виде отдельных файлов для кода и метаданных. Эти метаданные, опять же, используются для проецирования ABI компонента в другие языки и предоставления данных IntelliSense Visual Studio и Blend. Если теперь вы добавите ссылку на этот компонент в проекте приложения, щёлкнув правой кнопкой по проекту, выбрав Добавить ссылку > Решение (Add Reference > Solution) и затем выбрав PixelCruncherCPP, как на рис 13.2., вы обнаружите, что IntelliSense работает с классом, когда вы пишете JavaScript код.

    Так же вы можете обнаружить соответствующее приведение имен свойств и методов к виду, принятому в JavaScript. На самом деле, за исключением пространства имен, PixelCruncherCPP, всё, что мы делали, используя C#-компонент в JavaScript, выглядит точно так же, как и должно: приложение, использующее возможности WinRT-компонента не должно беспокоиться о языке, который использован для реализации этого компонента. И отладка, более того, выглядит точно так же, за исключением того, что вам нужно выбрать тип отладки Только машинный код или Смешанный (управляемый и машинный) (Native Only или Mixed (Managed And Native)) в диалоговом окне, показанном ранее на рис 13.3.

    Теперь нам нужно сделать то же самое для того, чтобы использовать массивы в компоненте, в качестве справки посмотрите материал "Классы ). В C++ входной массив объявляется с помощью Platform::Array<T> ^, а выходной – Platform::WriteOnlyArray<T> ^, где мы используем в качестве типа uint8 вместо типа Byte in C#:

       
      bool Grayscale::Convert(Platform::Array<uint8>
           ^ imageDataIn,
        Platform::WriteOnlyArray<uint8>^ imageDataOut)

    Остаток кода точно такой же, как раньше, за исключением этого изменения типа и того, как мы получаем длину входного массива, поэтому нам не нужно его здесь приводить. Код для вызова этого класса из JavaScript тоже такой же точно, как для C++.

    var pc2 = new PixelCruncherCPP.Grayscale();
    pc2.convert(pixels, imgData.data);

    Врезка: Библиотека шаблонов C++ среды выполнения Windows (WRL)

    Visual Studio включает в себя то, что называется Библиотека шаблонов C++ среды выполнения (Windows Runtime C++ Template Library или WRL) (http://msdn.microsoft.com/library/hh438466%28v=vs.110%29.aspx), которая поможет вам писать низкоуровневые WinRT-компоненты на C++. Это настоящий посредник между низкоуровневым COM и тем, что называется расширениями компонентов C++/CX, которые мы, на самом деле, и используем в этом разделе. Если у вас есть некоторый опыт работы с Active Template Library (ATL) для COM, вы будете чувствовать себя как дома с WRL. Для того, чтобы узнать подробности, посмотрите вышеупомянутую документацию и пример "WinRT-компонент с использованием WRL" (http://code.msdn.microsoft.com/windowsapps/Windows-Runtime-Component-e3e1e38d).

    Сравнение результатов

    Упражнение к этой главе "Image Manipulation", материалы которого содержатся в дополнительных материалах, содержит эквивалентный код на JavaScript, C#, и C++ для выполнения конвертации пикселей изображеня в оттенки серого. Выполняя замеры времени с помощью данных, полученных от new Date()в коде каждой процедуры, я составил таблицу показателей производительности, приведенную ниже .

    Среднее время исполнения, в миллисекундах (пять образцов; двухьядерный 2.5GHz процессор)
    Размер изображения JavaScript C# C++
    14.8K 8.4 7.2 6.4
    231K 45.2 40 33.8
    656K 76.6 65.8 54.4
    1.98MB 798 728 598
    4.57MB 796 750 637

    Несколько замечаний и наблюдений об этих показателях и их измерении:

  • Выполняя тестирование производительности, наподобие этого, не забудьте установить цель построения в Release (Развертывание) вместо Debug (Отладка). Это приведет к значительной разнице в производительности C++-кода, так как компилятор добавляет множество дополнительных механизмов при построении проекта для отладочных целей.
  • Выполняя измерения, удостоверьтесь в том, что запускаете приложение, предназначенное для развертывания вне отладчика (в Visual Studio выберите команду Отладка > Запуск без отладки (Debug > Start Without Debugging)). Если вы включили отладку скрипта, JavaScript-код будет работать заметно медленнее, чем в варианте построения для развертывания, что может привести к неправильному впечатлению того, что этот язык гораздо менее эффективен, чем на самом деле.
  • Если вы выполняете похожие тесты самостоятельно, вы можете отметить, что время, замеренное для операции конвертации гораздо меньше, чем время, когда приложение снова способно реагировать на действия пользователя. Это потому, что исполнение метода putImageData элемента canvas занимает довольно много времени для копирования конвертированных пикселей. На самом деле, основное количество всего процесса занимает именно работа putImageData, а не процесс конверсии в оттенки серого.
  • Если предположить, что нагрузка на процессор для конверсии в оттенки серого примерно одинакова для разных реализаций, вы можете видеть, что более высокопроизводительные компоненты уменьшают время, в течение которого процессор подвергается подобной нагрузке. При множестве вызовов подобных процедур, в итоге, высокопроизводительные компоненты значительно способствуют делу экономии электроэнергии.
  • При первом использовании WinRT – компонента для любой задачи, немного больше времени уходит на загрузку компонента и его метаданных. Вышеприведенные значения не включают измерение времени при первом запуске. Таким образом, если вы хотите оптимизировать процесс загрузки, эта дополнительная нагрузка может означать, что лучше выполнить все операции, используя JavaScript.
  • Опираясь на эти данные, мы можем увидеть, что код на C# исполняется на 6–21% быстрее, чем эквивалентный JavaScript, а C++ на 25–46% быстрее. C++, кроме того, на 13–22% быстрее, чем C#. Это означает, что для некритичного кода написание компонента необязательно даст вам результат, достойный потраченного времени. Более продуктивным будет сделать это средствами JavaScript. Но использование компонентов там, где производительность действительно важна, безусловно, принесет замечательные результаты.

    ) и "Анализ качества кода приложений для Магазина Windows с помощью средств анализа кода Visual Studio" (http://msdn.microsoft.com/library/windows/apps/hh441471) позволят вам произвести более глубокую оценку вашего приложения.

    Мне так же хочется добавить, что когда я впервые запускал вышеописанные тесты, я иногда видел примерно 100% превосходство в производительности C#/C++ над JavaScript. Причина этого заключается, скорее, в особенностях объекта ImageData элемента canvas (возвращаемого методом createImageData этого элемента), чем в самом JavaScript. В исходном JavaScript коде (с тех пор, как он был исправлен в Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), я обращался к элементам данных массива ImageData.data array для установки каждого значения r, g, b, и a каждоо пикселя. Когда я понял, как подобные обращения замедляют код, я поменял код, добавил кэширование массива в другой переменной и неожиданно JavaScript-версия стала работать гораздо быстрее. На самом деле, уменьшение использования ссылок на идентификаторы – это обычно хорошая практика повышения производительности в JavaScript. Для того, чтобы узнать больше об этом и других аспектах производительности, обратитесь к книге "High Performance JavaScript", которую написал Nicholas C. Zakas (O’Reilly, 2010).

    Врезка: Управляемый код против машинного

    Как показано в предыдущем разделе, переход от JavaScript к C# даёт вам первый уровень улучшения производительности, а переход от C# к C++ добавляет еще один. Учитывая то, что работа с C++ обычно сложнее, полезно задаться вопросом, стоит ли вкладывать в это дополнительные усилия. В особенно критических ситуациях, где дополнительные 13–22% производительности имеют реальное значение, ответ очевиден: стоит. Но есть и другие факторы для рассмотрения: разница между управляемой средой исполнения .NET-языков (вместе с JavaScript, если на то пошло) и машинной средой исполнения C++.

    Проще говоря, причина, по которой код на C#/VB чаще легче писать, чем код на C++ заключается в том, что .NET Common Language Runtime (CLR) предоставляет множество служб, наподобие сборки мусора, в итоге вы не должны беспокоиться о каждом небольшом выделении памяти. Что это означает, однако, что ваши действия в C#/VB так же могут вызывать выполнение дополнительных задач в среде исполнения, что может изменить характеристики производительности компонентов.

    Например, в упражнении "Image Manipulation" к этой главе, которое я по-настоящему расширил до приложения для тестирования компонентов, я добавил простую функцию, выполняющую сложение чисел в JavaScript, C#, и C++ (они все выглядят примерно одинаково):

    function countFromZero(max, increment) {
    var sum = 0;
    
      for (var x = 0; x < max; x += increment) {
    sum += x;
    }
    return sum;
    }

    Выполняя подсчет с заданным максимумом (max) в 1000 и приращением в 0.000001 (используйте подобное приращение вне отладчика, иначе вам придется ждать некоторое время), я получил следующие средние значения измерения времени исполнения функции: 2112 мс для JavaScript, 1568 мс для C#, и 1534 мс для C++. Снова мы видим, что различие между JavaScript и другими языками весьма значительно (прирост в 35–38%), но оно не так уж и отличается при сравнении C# и C++.

    Однако, я иногда обнаруживал, что после загрузки некоторого количества изображений и выполнения тестирования перевода их в оттенки серого, которое происходило в JavaScript и/или C#, эта операция могла занять гораздо больше времени, чем раньше, вероятнее всего из-за сборки мусора, которая действовала на производительность среды исполнения JavaScript и CLR. В C++ подобного не происходило, хотя, конечно, высокая нагрузка на процессор в любом случае замедляет любые действия.

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

    Страницы:

    Введение

    Материалы к лекциям 13, 14 Вы можете скачать здесь.

    В самом начале этого курса мы узнали, что приложения для Магазина Windows могут быть написаны с использованием различных языков и технологий представления данных По большей части мы говорили о возможности выбора: вы выбираете модель, которая наилучшим образом подходит к вашему опыту и нуждам вашего приложения.

    В то же время, мы иногда встречались с ситуациями, где применимо, а порой и необходимо применение нескольких языков. В Главе 1 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript" я говорил о том, что приложение, на самом деле, может быть написано на нескольких языках. В Главе 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" я упоминал о том, что получение доступа к API баз данных, помимо IndexedDB, может быть реализовано с использованием WinRT-компонента. В Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" мы видели, что JavaScript может быть не лучшим выбором, если нужна подпрограмма, которая обрабатывает пиксельные данные. И, в Главе 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", мы встретились с Notifications Extensions Library, с весьма полезным набором кода, написанном с использованием C#, упрощающим создание полезных данных XML из JavaScript. Знаем мы и о фоновых задачах, которые могут быть написаны на языках, отличающихся от языка основного приложения.

    Учитывая основное ограничение, в соответствии с которым приложение, использующее для презентационного уровня HTML и CSS не может делить рабочую поверхность вывода данных с WinRT-компонентами, написанными на других языках, дизайн WinRT делает подход с использованием нескольких языков вполне реальным для использования. WinRT-компоненты, написанные на любом языке, доступны из других языков посредством уровня проекции, который транслирует интерфейс компонента в среду, соответствующую целевому языку. Вся WinRT написана так, и пользовательские WinRT-компоненты используют преимущества того же механизма (Скоро мы увидим основные характеристики проекции JavaScript).

    Для вас это означает, что создавая приложения с помощью HTML, CSS, и JavaScript, вы можете реализовывать различные части приложения на языке, который лучше подходит для конкретной задачи или необходим технически. Как динамический язык, JavaScript показывает свою мощь в простом совмещении функциональности, предоставленной другими компонентами, которые выполняют различные специальные задачи (наподобие захвата данных с камеры, фоновой передачи данных и так далее). Подобные компоненты часто лучше писать на других языках, таких, как C++, где скомпилированный код исполняется непосредственно на центральном процессоре, вместо того, чтобы проходить сквозь уровни времени выполнения, как JavaScript.

    На самом деле, когда мы говорим о приложениях на смешанных языках, мы действительно можем смешивать разнообразные языки . Вы можете написать на C# компонент, который вызывается из JavaScript, но этот компонент, в свою очередь, может вызвать компонент, написанный на C++. Опять же, это означает, что вы можете использовать язык для любой конкретной задачи включая возможность создания собственных асинхронных операций (то есть, для исполнения кода в параллельных потоках, которые не блокируют поток пользовательского интерфейса). В этом контексте так же полезно продумать, что это значит по отношению к рабочим веб-процессам, к тому, чем, если нужно, могут пользоваться приложения для Магазина Windows.

    В этой лекции мы рассмотрим различные причины, по которым вы можете использовать подход применения смешанных языков в приложении. Затем мы пройдёмся по паре кратких руководств по C# и C++, чтобы вы понимали структуру компонентов, написанных на них, как они выглядят в JavaScript, их ключевые концепции и терминологию. В лекции 14 мы рассмотрим примеры различных сценариев, не входя слишком глубоко в технические подробности. Я решил так поступить, потому что существует прекрасная документация, посвященная таким подробностям. В особенности это следующие материалы:

  • "Создание компонентов среды выполнения Windows в C++" (http://msdn.microsoft.com/library/windows/apps/hh441569.aspx)
  • "Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh755833.aspx)
  • "Создание компонентов среды выполнения Windows в C# и Visual Basic" (http://msdn.microsoft.com/library/windows/apps/br230301.aspx) (и подразделы)
  • "Пошаговое руководство. Создание простого компонента в C# или Visual Basic и его вызов из кода JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh779077.aspx)
  • Не позволяйте словам "простой" и "основной" в названиях материалов удерживать вас от их прочтения: это всесторонние руководства, которые рассматривают множество мелких деталей работы с типами данных наподобие векторов, карт и наборов свойств. Они рассказывают об объявлении событий, создании асинхронных операций и о том, Как всё это выглядит в JavaScript. Кое-что из этого мы рассмотрим здесь, но имея такие замечательные руководства, мы займёмся здесь, в первую очередь, вопросами о том, почему нужны подобные компоненты, поговорим о проблемах, которые можно решить с их помощью. Кроме того, я хочу сохранить ваши силы для последней лекции курса, где мы поговорим о том, как представить ваше приложение миру после того, как вы решите все эти проблемы!

    Примечание. По необходимости, в этой лекции я должен предположить, что у вас есть некоторое понимание языков C# и C++, так как здесь мы не будем рассматривать основы. Если эти языки полностью новы для вас, потратьте несколько часов на то, чтобы с ними познакомиться. Это значительно улучшит ваше продвижение по данной лекции.

    Выбор подхода с использованием смешанных языков (и рабочих веб-процессов)

    Выбор подхода с использованием смешанных языков (и рабочие веб-процессы)

    Есть много причин для того, чтобы применить подход с использованием смешанных языков в вашем приложении. Этот подход подразумевает применение любого количества WinRT-компонентов, написанных на C#, VisualBasic (здесь и далее просто VB) и/или на C++:

  • Вы можете выполнить некоторые задачи быстрее с кодом, обладающим более высокой производительностью. Это может уменьшить использование памяти, а так же потребовать меньше процессорного времени и мощности, что важно для фоновых задач, для которых применяется квотирование процессорного времени. Вы можете сделать гораздо больше за 2 секунды на C++, чем на JavaScript, так как в этом случае не используются вспомогательные подсистемы.
  • C#, Visual Basic, и C++ имеют доступ к значительному набору дополнительных API, которые недоступны JavaScript. Они включают в себя API .NET, Win32, b COM (Component Object Model), включая не относящиеся к пользовательскому интерфейсу возможности DirectX, такие как XAudio и Xinput. Мы увидим ряд примеров в разделе этой лекции "Доступ к дополнительным API".
  • Доступ к другим API, кроме того, может понадобиться для использования библиотек .NET/Win32/COM сторонних разработчиков, и, кроме того, даёт вам возможность повторно использовать существующий код, который может у вас быть на языках C#, VB, или C++. В материале "Разработка Bing Maps Trip Optimizer — приложения для Магазина Windows — с помощью JavaScript и C++" (http://msdn.microsoft.com/library/windows/apps/hh699893.aspx) показан полный сценарий подобной работы, в частности, сценарии миграция элемента управления ActiveX в компонент WinRT, и, в итоге, возможность использования его в приложении, так как ActiveX напрямую не поддерживается. (Мы не будем возвращаться к данному сценарию в дальнейшем).
  • Может быть легче работать в процедурах, вовлекающих использование множества асинхронных операций с использованием ключевого слова await в C# и Visual Basic, так как подобный подход имеет более четкую структуру, нежели использование promise-объектов. Пример этого можно найти в Главе 6, в приложении "Here My Am!", где функция transcodeImage, написанная в Главе 2 на JavaScript переписана с использованием C# (смотрите Utilities.ImageConverter.TranscodeImageAsync в проекте Utilities).
  • WinRT-компоненты, написанные на C++ более эффективны при необходимости скрытия ценного кода приложения, чем JavaScript и .NET-языки. Хотя интерфейс компонента это не скроет, для того, чтобы понять его внутреннее устройство реверс-инженерам потребуется несравненно больше усилий.
  • Компоненты WinRT – это лучший способ создания библиотек, не имеющих отношения к пользовательскому интерфейсу, которые другие разработчики смогут использовать в избранной ими языковой среде, или вы сами сможете использовать во множестве других собственных проектов, как библиотеку Notifications Extensions, которую мы видели ранее. В этой связи, посмотрите материал "Практическое руководство. Создание пакета средств разработки программного обеспечения" (http://msdn.microsoft.com/library/windows/apps/hh768146.aspx), который включает в себя подробности о том, как нужно структурировать компоненты для того, чтобы интегрировать их с Visual Studio.
  • Хотя вы можете использовать в JavaScript рабочие веб-процессы (http://msdn.microsoft.com/library/windows/apps/hh767434.aspx) для того, чтобы исполнять код в различных потоках, компоненты WinRT могут быть гораздо более эффективными для пользовательских асинхронных API. Другие языки так же могут выполнять определенные задачи более продуктивно, как, например использование запросов, встроенных в язык (LINQ) из C#/VB, создание параллельных циклов в C#/C++, использование C++ Accelerated Massive Parallelism (AMP) (http://msdn.microsoft.com/library/hh265136.aspx) для использования возможностей графического процессора и так далее.
  • Опять же, компоненты могут использовать друг друга – у компонентной системы нет с этим проблем. Я снова об этом говорю, так как это связано с последним из вышеприведенных пунктов – рабочими веб-процессами и исполнением кода вне потока пользовательского интерфейса.

    Вы можете использовать рабочие веб-процессы (или просто рабочие процессы) в приложениях для Магазина Windows. На самом деле, Visual Studio предоставляет шаблон элемента, предназначенный именно для этого: щёлкните правой кнопкой мыши по проекту в Обозревателе решений Visual Studio, выберите команду Добавить > Создать элемент (Add > New Item) и затем выберите Выделенный рабочий процесс (Dedicated Worker). Так же я покажу в этой лекции упражнение "JavaScript Workers" ("Рабочие процессы JavaScript"). Настоящий их недостаток, в сравнении с WinRT-компонентами, это то, что обмен данными между рабочим процессом и потоком основного приложения реализуется полностью с помощью дискретного механизма postMessage. Данные, передаваемые между приложением и рабочим процессом должны быть выражены в виде свойств сообщения. Другими словами, рабочий процесс построен с использованием клиент-серверной архитектуры, а не в виде объекта со свойствами, методами и событиями, хотя вы можете создавать структуры для подобных вещей с помощью сообщений.

    Это означает то, что использование рабочих процессов с несколькими асинхронными операциями может вызвать путаницу. Для сравнения, методы, которые мы видели для работы с асинхронными операциями WinRT – promise-объекты WinJS, имеют больше возможностей, особенно тогда, когда вам нужно объединить асинхронные операции в цепочку или использовать вложенные асинхронные операции, вызывают исключения и сообщают о процессе выполнения операции. К счастью, есть возможность заключить рабочий процесс в оболочку promise-объекта, как мы увидим в разделе "Рабочие процессы JavaScript"

    Еще интересно подумать о том, как вы можете использовать рабочие процессы, написанные на JavaScript для того, чтобы они работали в качестве агентов, делегируя задачи WinRT-компонентам. Рабочие процессы JavaScript, таким образом, служат в качестве связующего звена для сбора воедино результатов работы компонентов, возвращая общий результат основному приложению через postMessage.

    В том же духе, если у вас есть некоторый опыт работы в .NET-языках наподобие C# и Visual Basic вместе с C++, вы узнаете, что соответствующие им модели программирования имеют собственные сильные и слабые стороны. Так же, как вы можете получить преимущества от динамической природы JavaScript в тех областях, для которых он подходит лучше всего, вы можете использовать управляемую природу .NET, которая избавит вас от различных проблем наподобие управления памятью и со ссылками, и, в свою очередь, использовать C++ там, где нужен наиболее производительный код.

    Коротко говоря, поток пользовательского интерфейса приложения на JavaSctipt делегирует задачи рабочим процессам на JavaScript, которые затем делегируют некоторые задачи компонентам, написанным на C#, который затем могут передать наиболее интенсивные задачи другим компонентам, написанным на C++. На самом деле, комбинация рабочих процессов с компонентами WinRT даёт вам отличную гибкость в реализации приложений.

    Примечание. Единственный потенциальный недостаток использования в приложениях компонентов WinRT, написанных на C++ заключается в том, что, в то время, как JavaScript и .NET-языки (C# и C++) архитектурно-нейтральны и могут исполняться на любом процессоре, компоненты на C++ должны быть отдельно скомпилированы для архитектур x86, x64 и ARM. Это подразумевает наличие у приложения в Магазине Windows трех различных пакетов. Однако, пакеты для архитектуры x86 будут так же работать и на процессорах с архитектурой x64, что устраняет необходимость в пакете для одной из целевых архитектур, если создание специального пакета для x64 не способно принести реального прироста в производительности.

    Краткие руководства: создание и отладка компонентов

    Если вы задались целью добавить WinRT-компонент в своё проект, легче всего начать с шаблона элемента Visual Studio. Щёлкните правой кнопкой по решению (не по проекту) в Обозревателе решений Visual Studio, выберите команду Добавить > Создать проект (Add > New Project) и затем выберите элемент Компонент среды выполнения (Windows Runtime Component) из группы Visual Basic, Visual C#, или Visual C++ > Магазин Windows (Windows Store), как показано на рис 13.1 для проекта на C#.

    (рис 13.1) Возможности Visual Studio по созданию проекта Компонент среды выполнения (Windows Runtime Component) на C#. Похожее можно сделать на Visual Basic и Visual C++

    В следующих разделах мы посмотрим и на вариант C# и на вариант C++, улучшая упражнение, посвященное манипуляции изображениями (Image Manipulation), работа над которым начата в Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", которое можно найти в дополнительных материалах к лекции. Последний вариант этого приложения выполнял конверсию изображения в оттенки серого и затем отображал его в элементе canvas. Мы выполняли манипуляции в JavaScript-коде, который, на самом деле, работал достаточно хорошо, но мы можем улучшить работу этого алгоритма. Наши первые два WinRT-компонента, таким образом, реализуют одну и ту же задачу с использованием C# и C++. Имея все три реализации мы получим возможность сравнить относительную производительность, что мы и сделаем в разделе "Сравнение результатов".

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

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

    Врезка: WinRT-компоненты против библиотек классов (C#/VB) и динамически связываемых библиотек

    В диалоговом окне Добавить новый проект Add New Project) в Visual Studio вы можете заметить параметры для создания библиотек классов (Class Library), показанные для Visual C# и Visual Basic и опцию для создания DLL (dynamic-link library, динамически связываемой библиотеки) показанной для C++. Они, соответственно, эффективно компилируются в сборки и в DLL, которые имеют сходство с компонентами WinRT. Разница, однако, заключается в том, что их можно использовать лишь из того же языка. Библиотека класса (Class Library) (.NET-сборка) может быть использована приложениями, написанными на .NET-языках, но не из JavaScript. Похожим образом, DLL можно вызвать из C++ и .NET-языков (в последнем случае – только с помощью механизма P/Invoke), но не доступны для JavaScript. Для этой цели подходят лишь компоненты WinRT.

    Иногда нужны простые DLL, как в случае с мультимедиа-расширениями, которые предоставляют пользовательские аудио/видео эффекты или кодеки. Это не WinRT-компоненты, так как у них отсутствуют необходимые метаданные, благодаря которым они могут быть спроецированы в другие языки. Подробности об использовании DLL в мультимедиа-платформах можно найти в материале "Применение расширений мультимедиа" (http://msdn.microsoft.com/library/windows/apps/hh700365.aspx) и в примере "Расширения мультимедиа" (http://code.msdn.microsoft.com/windowsapps/Media-extensions-sample-7b466096).

    Краткое руководство №1: Создание компонента на C#

    Как показано на рис 13.1, я добавил WinRT-компонент на C# в упражнение "Image Manupulation", назвав компонент PixelCruncherCS. Как только данный проект будет добавлен в решение, в этом проекте окажется файл, который называется class1.cs, который содержит пространство имен PixelCruncherCS с одним классом:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace PixelCruncherCS
    {
    public sealed class Class1
    {
    }
    }

    Ничего особенного в этом коде сейчас нет, но этого достаточно, чтобы мы могли начать. Вы можете видеть, что класс объявлен как public, то есть, он будет виден приложениям, которые будут использовать компонент. Кроме того, использование модификатора sealed говорит нам о том, что запрещено наследование от этого класса (по причине текущих технических ограничений). Оба ключевых слова необходимы для WinRT-компонентов в Windows 8. (В Visual Basic эти два ключевых слова выглядят как Public и NotInheritable.)

    Для проверки взаимодействия с JavaScript, я дал классу и его файлу более подходящие имена (Tests и grayscale.cs, с учетом того, что мы собираемся реализовать) и создал тестовй метод и тестовое свойство:

    public sealed class Tests
    {
    public static string TestMethod(Boolean throwAnException)
    {
    if (throwAnException)
    {
    throw new System.Exception("Tests.TestMethod was asked to throw an exception.");
    }
    return "Tests.TestMethod succeeded";
    }
    
    public int TestProperty { get; set; }
    }

    Если сейчас вы выполните построение решения (Построение > Построить решение) (Build > Build Solution), вы увидите, что результат построения проекта PixelCruncherCS – это файл, который называется PixelCruncher.winmd. Расширение .winmd применяется для метаданных Windows (Windows Metadata): WinRT-компонент, написанный на C# это .NET-сборка, которая включает расширенные метаданные, которые называются двоичным интерфейсом приложения (Application Binary Interface или ABI). Это то, что сообщает Windows полную информацию о том, что компонент может предоставить другим языком (его общедоступные классы) и так же то, что предоставляет возможности IntelliSense для этого компонента в Visual Studio и Blend.

    В приложении вы должны добавить ссылку на компонент, чтобы он стал доступен в пространстве имен JavaSctipt, так же, как API WinRT. Для того, чтобы это сделать, щёлкните правой кнопкой мыши Ссылки (References) в проекте приложения на JavaScript, выберите команду Добавить ссылку (Add Reference). В появившемся диалоговом окне, выберите в правой его части Решение (Solution) и установить флаг около проекта WinRT-компонента, как показано на рис 13.2.

    (рис 13.2) Добавление ссылки на WinRT-компонент, который расположен в том же решении, что и приложение

    При написании кода, который ссылается на компонент, всегда начинают с пространства имен, в данном случае – PixelCruncherCS. Как только вы введете это имя и точку, появится подсказка IntelliSense, показывающая классы, доступные в данном пространстве имен:

    Как только вы добавить имя класса и введете еще одну точку, подсказка IntelliSense покажет его методы и свойства:

    Примечание. Если вы внесли изменение в пространство имен, класс, или в другие имена в проекте WinRT-компонента, вам нужно выполнить команду Построение > Построить решение (Build > Build Solution) для того, чтобы увидеть обновленные имена в IntelliSense.

    Здесь вы можете видеть, что хотя имя метода в коде C# выглядит как TestMethod, оно проецируется в JavaScript как testMethod, соответствуя соглашениями об именовании, принятым в JavaScript. Это изменение регистра, применяется автоматически с помощью уровня проекции JavaSctipt для всех WinRT-компонентов, включая те, которые находятся в вашем приложении.

    Кроме того, обратите внимание на то, что IntelliSense показывает здесь лишь testMethod, но не testProperty (регистр символов имени которого так же изменен). Почему это так? Потому, что в C# TestMethod объявлен как static, что подразумевает возможность его исполнения без предварительного создания экземпляра объекта этого класса:

          var result = PixelCruncherCS.Tests.testMethod(false);

    С другой стороны, testProperty, но не testMethod, доступно для конкретного экземпляра:

    Я, кстати, настроил TestMethod на выдачу исключения при его вызове, таким образом, мы можем увидеть, как они обрабатываются в JavaScript с помощью блока try/catch:

    try {
    result = PixelCruncherCS.Tests.testMethod(true);
    } catch (e) {
    console.log("PixelCruncherCS.Tests.testMethod threw: '" + e.description + "'.");
    }
          

    Испытаем этот код. Привяжем его к какой-нибудь кнопке (смотрите функцию testComponentCS в файле js/default.js), установим точку останова в верхней части и запустим приложение в отладчике. Когда сработает точка останова, выполните пошаговое исполнение кода, используя возможность Visual Studio Шаг с заходом (F11 или Отладка > Шаг с заходом (Debug > Step Into)). Обратите внимание на то, что вы не попадаете в код C#, это последствие того факта, что Visual Studio может отлаживать либо скриптовый, либо управляемый (C#) / машинный (C++) код в одном сеансе отладке, но, к сожалению, не то и другое вместе. Очевидно, при существовании подобных ограничений, вы можете воспользоваться консольным выводом как инструментом отладки.

    Для того, чтобы задать режим отладки, щёлкните правой кнопкой мыши по проекту в Обозревателе решений, откройте окно свойств, в его левой части выберите Отладка (Debugging) и затем установить Тип отладчика (Debugger Type) в правой части окна так, как показано на рис 13.3. Для отладки C# выберите Только управляемый код (Managed Only) или Смешанный (Управляемый и машинный) ( Mixed (Managed and Native)). Затем установите точки останова в коде компонента и перезапустите приложение (нужно выполнить перезапуск для того, чтобы эти изменения возымели действие). Когда вы вызываете компонент из JavaScript, срабатывают точки останова в коде компонента.

    (рис 13.3) Типы отладчика в Visual Studio

    Когда основные механизмы отработаны, теперь мы готовы к тому, чтобы добавить сюда реальную функциональность. Первый шаг заключается в том, чтобы понять, как передать WinRT-компоненту массив с данными пикселей из элемента canvas и как получить из него результаты работы. В коде JavaScript (в методе copyGrayscaleToCanvas) имеется массив pixels, который содержит исходные пиксельные данные, и еще один пустой массив в imgData.data, где imgData получают следующим образом:

          var imgData = ctx.createImageData(canvas.width, canvas.height);

    Мы можем напрямую передать оба этих массива в компонент. Здесь есть лишь ограничение на то, что массивы, переданные компоненту WinRT могут быть использованы либо для ввода, либо для вывода, но не для того и другого одновременно – компонент просто не может манипулировать массивом по месту его размещения. Материал "Передача массивов в компонент среды выполнения Windows" (http://msdn.microsoft.com/library/windows/apps/hh975353.aspx) содержит описание тонкостей этого процесса. Для того, чтобы сократить усилия, мы, к счастью, уже являемся обладателями входного массива, pixels, и выходного массива – imgData.data, которые мы можем передать методу компонента:

         var pc = new PixelCruncherCS.Grayscale();
          pc.convert(pixels, imageData.data); // Обратите внимание на изменение имени метода

    Примечание. Подход, показанный здесь и в вышеупомянутом материале применим лишь для синхронных методов в WinRT-компонентах. Массивы нельзя применять с асинхронными операциями. Смотрите ниже раздел "Ключевые концепции и подробности" для того, чтобы узнать подробности об этом.

    Для того чтобы принять этот массив в C#, оба параметра должны быть соответствующим образом маркированы в зависимости от их направления. Подобная маркировка в C# называется атрибутом (attributes), не спутайте их с похожим понятием в HTML, атрибуты приводятся в прямоугольных скобках, [ ] перед именем параметра. В данном случае атрибуты выглядят как [ReadOnlyArray()] и [WriteOnlyArray()] и предшествуют параметрам (методы ReadOnlyArray и WriteOnlyArray можно найти в пространстве имен System.Runtime.InteropServices.WindowsRuntime). Таким образом, объявление метода в компоненте, который, опять же, должен быть общедоступным, выглядит так, с учетом того, что сейчас в качестве возвращаемого типа используется логический (Boolean):

    public Boolean Convert([ReadOnlyArray()] Byte[] imageDataIn, 
          [WriteOnlyArray()] Byte[] imageDataOut)

    Когда всё это готово, довольно просто конвертировать код JavaScript для перевода изображения в оттенки серого, в C#-код:

    public Boolean Convert([ReadOnlyArray()] Byte[] imageDataIn, 
          [WriteOnlyArray()] Byte[] imageDataOut)
          {
          int i;
          int length = imageDataIn.Length;
          const int colorOffsetRed = 0; const int
          colorOffsetGreen = 1; const int
          colorOffsetBlue = 2; const int
          colorOffsetAlpha = 3;
    
          Byte r, g, b, gray;
    
          for (i = 0; i < length; i += 4)	
    {	
    r = imageDataIn[i + colorOffsetRed];	
    g = imageDataIn[i + colorOffsetGreen];
    b = imageDataIn[i + colorOffsetBlue];
    
    //Присовение каждого rgb-значения значению яркости для изображения в оттенках серого 
    gray = (Byte)(.3 * r + .55 * g + .11 * b);
    
    imageDataOut[i + colorOffsetRed] = gray; imageDataOut[i + 
    colorOffsetGreen] = gray; imageDataOut[i + 
    colorOffsetBlue] = gray;
    imageDataOut[i + colorOffsetAlpha] = imageDataIn[i + colorOffsetAlpha];
    }
    
    return true;
    }

    Врезка: особенности отладки

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

    Когда я работал над упражнениями для этой лекции, я обнаружил, что я разработал шаблон работы, когда я устанавливал точки останова и в JavaScript и в C#, после чего просто переключал режимы отладки и перезапускал приложение. То есть я запускал приложение для отладки скрипта и пошагово исполнял код (перезапуская программу, когда находил и исправлял ошибки), до тех пор, пока не стало понятно, что какая-то проблема есть в компоненте. Тогда я переключил режим отладки на работу с управляемым кодом и перезапустил приложение. С использованием всех точек останова, установленных в компоненте, я мог точно так же исполнять приложение, пошагово выполнять код компонента, исправлять ошибки и перезапускаться для повторения процесса. Немного позже, однако, я нашёл другую проблему со стороны JavaScript, тогда я переключил режим отладки и снова перезапустился. Это не так удобно, как возможность переходить из одного кода в другую, но, в конце концов, это работало достаточно хорошо.

    Одно из предложений, которое могло бы ускорить процесс, заключается в написании некоторого кода для целей тестирования в C# или VB, который позволит вам напрямую обращаться к коду компонента. Вы можете встроить подобную тестировочную процедуру напрямую в код компонента, привязать кнопку или две к её методам в JavaScript. Таким образом вы, возможно, уменьшите количество работы по тестированию кода компонента, будучи уверенными в его нормальной работе перед вызовом его из JavaScript с реальными данными.

    Краткое руководство №2: создание компонента в C++

    Для того, чтобы продемонстрировать работу с WinRT-компонентами, написанными на C++, я так же добавил проект в упражнение "Image Manipulation example", назвав его PixelCruncherCPP. Основной код там такой же самый, как и в примере на C# - процедура манипуляции с пикселями довольно универсальна! Необходимая структура кода компонента, с другой стороны, уникальна для языка: у C++ есть собственные особенности.

    Как мы поступали с C#, давайте добавим новый проект, использовав шаблон Visual C++ > Компонент среды выполнения (Visual C++ > Windows Runtime Component) и имя PixelCruncherCPP. После переименования Class1 в Tests в коде и переименования файла, мы видим следующий код в заголовочном файле (я назвал его grayscale.h, и опустил директивы компилятора):

    namespace PixelCruncherCPP
    {
    public ref class Tests sealed
    {
    public:
    Tests();
    };
    }

    Здесь мы видим, что класс должен быть объявлен public ref и sealed, с общедоступным конструктором. Всё это вместе даёт возможность создать экземпляр объекта в качестве WinRT-компонента. В Tests.cpp мы видим следующее:

    #include "pch.h"
    #include "Grayscale.h"
    
    using namespace PixelCruncherCPP;
    using namespace Platform;
    
    Tests::Tests()
    {
    }

    Опять же, не так уж и много всего, но вполне достаточно (Документация по пространству имен Platform (http://msdn.microsoft.com/library/windows/apps/hh710417.aspx), кстати, это часть Справочника по языку Visual C++.) Для того, чтобы реализовать то же самое, что мы сделали для C++, добавим статический тестовый метод и соответствующее свойство. Определение класса выглядит так:

    public ref class Tests sealed
    {
    public: Tests();
    
    static Platform::String^ TestMethod(bool throwAnException);
    property int TestProperty;
    };
    
    А вот код для TestMethod:
    String^ Tests::TestMethod(bool throwAnException)
    {
    if (throwAnException)
    {
    throw ref new InvalidArgumentException;
    }
    return ref new String(L"Tests.TestMethod succeeded");
    }

    Когда вы выполните построение этого проекта (Построение > Построить проект (Build > Build Solution)), вы увидите, что теперь у нас имеются файлы PixelCruncherCPP.dll и PixelCruncherCPP.winmd. В то время, как C#-сборка может содержать и код и метаданные, C++-компонент компилируется в виде отдельных файлов для кода и метаданных. Эти метаданные, опять же, используются для проецирования ABI компонента в другие языки и предоставления данных IntelliSense Visual Studio и Blend. Если теперь вы добавите ссылку на этот компонент в проекте приложения, щёлкнув правой кнопкой по проекту, выбрав Добавить ссылку > Решение (Add Reference > Solution) и затем выбрав PixelCruncherCPP, как на рис 13.2., вы обнаружите, что IntelliSense работает с классом, когда вы пишете JavaScript код.

    Так же вы можете обнаружить соответствующее приведение имен свойств и методов к виду, принятому в JavaScript. На самом деле, за исключением пространства имен, PixelCruncherCPP, всё, что мы делали, используя C#-компонент в JavaScript, выглядит точно так же, как и должно: приложение, использующее возможности WinRT-компонента не должно беспокоиться о языке, который использован для реализации этого компонента. И отладка, более того, выглядит точно так же, за исключением того, что вам нужно выбрать тип отладки Только машинный код или Смешанный (управляемый и машинный) (Native Only или Mixed (Managed And Native)) в диалоговом окне, показанном ранее на рис 13.3.

    Теперь нам нужно сделать то же самое для того, чтобы использовать массивы в компоненте, в качестве справки посмотрите материал "Классы ). В C++ входной массив объявляется с помощью Platform::Array<T> ^, а выходной – Platform::WriteOnlyArray<T> ^, где мы используем в качестве типа uint8 вместо типа Byte in C#:

       
      bool Grayscale::Convert(Platform::Array<uint8>
           ^ imageDataIn,
        Platform::WriteOnlyArray<uint8>^ imageDataOut)

    Остаток кода точно такой же, как раньше, за исключением этого изменения типа и того, как мы получаем длину входного массива, поэтому нам не нужно его здесь приводить. Код для вызова этого класса из JavaScript тоже такой же точно, как для C++.

    var pc2 = new PixelCruncherCPP.Grayscale();
    pc2.convert(pixels, imgData.data);

    Врезка: Библиотека шаблонов C++ среды выполнения Windows (WRL)

    Visual Studio включает в себя то, что называется Библиотека шаблонов C++ среды выполнения (Windows Runtime C++ Template Library или WRL) (http://msdn.microsoft.com/library/hh438466%28v=vs.110%29.aspx), которая поможет вам писать низкоуровневые WinRT-компоненты на C++. Это настоящий посредник между низкоуровневым COM и тем, что называется расширениями компонентов C++/CX, которые мы, на самом деле, и используем в этом разделе. Если у вас есть некоторый опыт работы с Active Template Library (ATL) для COM, вы будете чувствовать себя как дома с WRL. Для того, чтобы узнать подробности, посмотрите вышеупомянутую документацию и пример "WinRT-компонент с использованием WRL" (http://code.msdn.microsoft.com/windowsapps/Windows-Runtime-Component-e3e1e38d).

    Сравнение результатов

    Упражнение к этой главе "Image Manipulation", материалы которого содержатся в дополнительных материалах, содержит эквивалентный код на JavaScript, C#, и C++ для выполнения конвертации пикселей изображеня в оттенки серого. Выполняя замеры времени с помощью данных, полученных от new Date()в коде каждой процедуры, я составил таблицу показателей производительности, приведенную ниже .

    Среднее время исполнения, в миллисекундах (пять образцов; двухьядерный 2.5GHz процессор)
    Размер изображения JavaScript C# C++
    14.8K 8.4 7.2 6.4
    231K 45.2 40 33.8
    656K 76.6 65.8 54.4
    1.98MB 798 728 598
    4.57MB 796 750 637

    Несколько замечаний и наблюдений об этих показателях и их измерении:

  • Выполняя тестирование производительности, наподобие этого, не забудьте установить цель построения в Release (Развертывание) вместо Debug (Отладка). Это приведет к значительной разнице в производительности C++-кода, так как компилятор добавляет множество дополнительных механизмов при построении проекта для отладочных целей.
  • Выполняя измерения, удостоверьтесь в том, что запускаете приложение, предназначенное для развертывания вне отладчика (в Visual Studio выберите команду Отладка > Запуск без отладки (Debug > Start Without Debugging)). Если вы включили отладку скрипта, JavaScript-код будет работать заметно медленнее, чем в варианте построения для развертывания, что может привести к неправильному впечатлению того, что этот язык гораздо менее эффективен, чем на самом деле.
  • Если вы выполняете похожие тесты самостоятельно, вы можете отметить, что время, замеренное для операции конвертации гораздо меньше, чем время, когда приложение снова способно реагировать на действия пользователя. Это потому, что исполнение метода putImageData элемента canvas занимает довольно много времени для копирования конвертированных пикселей. На самом деле, основное количество всего процесса занимает именно работа putImageData, а не процесс конверсии в оттенки серого.
  • Если предположить, что нагрузка на процессор для конверсии в оттенки серого примерно одинакова для разных реализаций, вы можете видеть, что более высокопроизводительные компоненты уменьшают время, в течение которого процессор подвергается подобной нагрузке. При множестве вызовов подобных процедур, в итоге, высокопроизводительные компоненты значительно способствуют делу экономии электроэнергии.
  • При первом использовании WinRT – компонента для любой задачи, немного больше времени уходит на загрузку компонента и его метаданных. Вышеприведенные значения не включают измерение времени при первом запуске. Таким образом, если вы хотите оптимизировать процесс загрузки, эта дополнительная нагрузка может означать, что лучше выполнить все операции, используя JavaScript.
  • Опираясь на эти данные, мы можем увидеть, что код на C# исполняется на 6–21% быстрее, чем эквивалентный JavaScript, а C++ на 25–46% быстрее. C++, кроме того, на 13–22% быстрее, чем C#. Это означает, что для некритичного кода написание компонента необязательно даст вам результат, достойный потраченного времени. Более продуктивным будет сделать это средствами JavaScript. Но использование компонентов там, где производительность действительно важна, безусловно, принесет замечательные результаты.

    ) и "Анализ качества кода приложений для Магазина Windows с помощью средств анализа кода Visual Studio" (http://msdn.microsoft.com/library/windows/apps/hh441471) позволят вам произвести более глубокую оценку вашего приложения.

    Мне так же хочется добавить, что когда я впервые запускал вышеописанные тесты, я иногда видел примерно 100% превосходство в производительности C#/C++ над JavaScript. Причина этого заключается, скорее, в особенностях объекта ImageData элемента canvas (возвращаемого методом createImageData этого элемента), чем в самом JavaScript. В исходном JavaScript коде (с тех пор, как он был исправлен в Главе 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), я обращался к элементам данных массива ImageData.data array для установки каждого значения r, g, b, и a каждоо пикселя. Когда я понял, как подобные обращения замедляют код, я поменял код, добавил кэширование массива в другой переменной и неожиданно JavaScript-версия стала работать гораздо быстрее. На самом деле, уменьшение использования ссылок на идентификаторы – это обычно хорошая практика повышения производительности в JavaScript. Для того, чтобы узнать больше об этом и других аспектах производительности, обратитесь к книге "High Performance JavaScript", которую написал Nicholas C. Zakas (O’Reilly, 2010).

    Врезка: Управляемый код против машинного

    Как показано в предыдущем разделе, переход от JavaScript к C# даёт вам первый уровень улучшения производительности, а переход от C# к C++ добавляет еще один. Учитывая то, что работа с C++ обычно сложнее, полезно задаться вопросом, стоит ли вкладывать в это дополнительные усилия. В особенно критических ситуациях, где дополнительные 13–22% производительности имеют реальное значение, ответ очевиден: стоит. Но есть и другие факторы для рассмотрения: разница между управляемой средой исполнения .NET-языков (вместе с JavaScript, если на то пошло) и машинной средой исполнения C++.

    Проще говоря, причина, по которой код на C#/VB чаще легче писать, чем код на C++ заключается в том, что .NET Common Language Runtime (CLR) предоставляет множество служб, наподобие сборки мусора, в итоге вы не должны беспокоиться о каждом небольшом выделении памяти. Что это означает, однако, что ваши действия в C#/VB так же могут вызывать выполнение дополнительных задач в среде исполнения, что может изменить характеристики производительности компонентов.

    Например, в упражнении "Image Manipulation" к этой главе, которое я по-настоящему расширил до приложения для тестирования компонентов, я добавил простую функцию, выполняющую сложение чисел в JavaScript, C#, и C++ (они все выглядят примерно одинаково):

    function countFromZero(max, increment) {
    var sum = 0;
    
      for (var x = 0; x < max; x += increment) {
    sum += x;
    }
    return sum;
    }

    Выполняя подсчет с заданным максимумом (max) в 1000 и приращением в 0.000001 (используйте подобное приращение вне отладчика, иначе вам придется ждать некоторое время), я получил следующие средние значения измерения времени исполнения функции: 2112 мс для JavaScript, 1568 мс для C#, и 1534 мс для C++. Снова мы видим, что различие между JavaScript и другими языками весьма значительно (прирост в 35–38%), но оно не так уж и отличается при сравнении C# и C++.

    Однако, я иногда обнаруживал, что после загрузки некоторого количества изображений и выполнения тестирования перевода их в оттенки серого, которое происходило в JavaScript и/или C#, эта операция могла занять гораздо больше времени, чем раньше, вероятнее всего из-за сборки мусора, которая действовала на производительность среды исполнения JavaScript и CLR. В C++ подобного не происходило, хотя, конечно, высокая нагрузка на процессор в любом случае замедляет любые действия.

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

    Вернуться к учебному плану