Опубликован: 15.05.2013 | Доступ: свободный | Студентов: 265 / 9 | Длительность: 24:25:00
Специальности: Системный архитектор
Лекция 13:

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

< Лекция 12 || Лекция 13: 123 || Лекция 14 >

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

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

Возможности Visual Studio по созданию проекта Компонент среды выполнения (Windows Runtime Component) на C#. Похожее можно сделать на Visual Basic и Visual 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.

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

Рис. 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, срабатывают точки останова в коде компонента.

Типы отладчика в Visual Studio

Рис. 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 с реальными данными.

< Лекция 12 || Лекция 13: 123 || Лекция 14 >