Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Windows (до Windows 10) Стратегии совместного использования кода для приложений Windows Store и Windows Phone RSS

Стратегии совместного использования кода для приложений Windows Store и Windows Phone

Текущий рейтинг: 4.5 (проголосовало 2)
 Посетителей: 661 | Просмотров: 769 (сегодня 0)  Шрифт: - +

Благодаря Visual Studio 2012 в вашем распоряжении имеется великолепный набор инструментов для создания приложений Windows 8 и Windows Phone 8. А значит, есть смысл исследовать, сколько кода ваших приложений можно сделать общим для их версий под Windows Store и Windows Phone.

Приложения Windows Store можно писать, используя несколько разных языков программирования: XAML с C#, Visual Basic, C++ и даже HTML5 с JavaScript.

Приложения Windows Phone 8 обычно пишутся на XAML в сочетании с C# или Visual Basic, но Windows Phone 8 SDK теперь позволяет создавать Direct3D-приложения с применением XAML и C++. Хотя Windows Phone 8 SDK также предоставляет шаблон для приложений на основе HTML5, на самом деле они все равно базируются на XAML с простым добавлением элемента управления WebBrowser, в котором размещаются веб-страницы с поддержкой HTML5.

В этой статье я рассмотрю три стратегии совместного использования кода между приложениями Windows Store и Windows Phone: Portable Class Libraries (PCL), компоненты Windows Runtime (WinRT) (и компоненты Windows Phone Runtime) и вариант Add as Link в Visual Studio. Дополнительные сведения по разделению кода между приложениями Windows Store и Windows Phone вы найдете в Dev Center (aka.ms/sharecode).

Стоит отметить, что, хотя у приложений Windows Store и Windows Phone много схожего (например, активные плитки), это все же разные платформы со своей спецификой.

Архитектура

Архитектурные принципы, позволяющие увеличить долю общего кода, — в целом, те же, что и способствующие разделению обязанностей (separation of concerns). Если вы уже применяете шаблоны, которые обеспечивают разделение обязанностей, например Model-View-ViewModel (MVVM) или Model-View-Controller (MVC), то сможете легко добиться совместного использования кода; то же самое относится и к случаю, когда в вашей архитектуре задействованы шаблоны встраивания зависимостей (dependency injection). Вы определенно должны подумать о применении этих шаблонов, проектируя архитектуру новых приложений, чтобы максимально повысить уровень совместного использования кода. В существующих приложениях вы, возможно, захотите выполнить рефакторинг архитектуры, чтобы содействовать разделению обязанностей и тем самым более высокой степени совместного использования кода. Разделение обязанностей, обеспечиваемое MVVM или MVC, дает дополнительные преимущества, такие как возможность параллельной работы проектировщиков и разработчиков. Первые проектируют структуру программы и UI с помощью инструментария вроде Expression Blend, а вторые пишут код в Visual Studio, рождающий эту программу на свет.

Portable Class Libraries

PCL-проект в Visual Studio 2012 обеспечивает кросс-платформенную разработку, позволяя выбирать целевые инфраструктуры, которые будут поддерживаться конечными сборками. Шаблон PCL-проект, введенный в Visual Studio 2010 как дополнительная надстройка, теперь включается в Visual Studio Professional 2012 и выше.

Итак, какой же код можно сделать общим в рамках PCL?

Библиотеки PCL называются так потому, что они позволяют совместно использовать портируемый код, и для того, чтобы он был портируемым, его нужно писать как управляемый на C# или Visual Basic. Так как PCL дает на выходе один двоичный файл, портируемый код не использует директивы условной компиляции; вместо этого средства, специфичные для конкретной платформы, абстрагируются с помощью интерфейсов или абстрактных базовых классов. Когда портируемому коду нужно взаимодействовать с кодом, специфичным для платформы, применяются шаблоны встраивания зависимостей, через которые предоставляются реализации, специфичные для платформ. В результате компиляции PCL дает единую сборку, на которую можно ссылаться из любого проекта, основанного на целевых инфраструктурах.

На рис. 1 показан один из рекомендованных подходов к архитектуре, обеспечивающих создание общего кода с применением PCL. В случае шаблона MVVM модели (models) и модели представлений (view models) содержатся внутри PCL; там же находятся абстракции любых специфичных для платформы средств. Приложения Windows Store и Windows Phone предоставляют стартовую логику, представления и реализации любых абстракций специфичных для платформы средств. Хотя проектировочный шаблон MVVM не обязателен для написания портируемого кода, этот шаблон разделения обязанностей способствует созданию четкой и расширяемой архитектуры.

*
Рис. 1. Создание общего кода с помощью проектировочного шаблона MVVM

Windows Store AppПриложение Windows Store
StartupСтартовая логика
ViewsПредставления
Platform-Specific FunctionalityСпецифичная для платформы функциональность
Portable Class LibraryPortable Class Library
View ModelsМодели представлений
ModelsМодели
Platform Functionality AbstractionsСпецифичные для платформы абстракции
Windows Phone AppПриложение Windows Phone
ReferenceСсылка


Диалог Add Portable Class Library в Visual Studio 2012 позволяет выбрать целевые инфраструктуры, которые будут поддерживаться конечной сборкой.

Чтобы код был портируемым, его нужно писать как управляемый на C# или Visual Basic.

Поначалу вы могли решить, что должны установить флажок для Silverlight 5, но для совместного использования кода между приложениями Windows Store и Windows Phone это не обязательно. По сути, выбор Silverlight 5 означает, что ваш портируемый код не будет использовать преимущества некоторых очень полезных новых типов, таких как класс CallerMemberNameAttribute, введенный в Microsoft .NET Framework 4.5.

Если вы уже занимались разработкой для Windows Phone, то скорее всего знакомы с классом MessageBox, который позволяет выводить сообщения пользователю. Приложения Windows Store для той же цели используют класс MessageDialog из Windows Runtime. Давайте рассмотрим, как абстрагировать эту специфичную для платформ функциональность в PCL.

Интерфейс IMessagingManager на рис. 2 абстрагирует специфичную для платформы функциональность, относящуюся к выводу сообщений. Этот интерфейс предоставляет перегруженный метод ShowAsync, который принимает сообщение и его заголовок, а потом отображает пользователю.

Рис. 2. Интерфейс IMessagingManager

/// <summary>
/// Предоставляет абстракцию специфичных для платформы
/// средств вывода сообщений пользователю
/// </summary>
public interface IMessagingManager
{
  /// <summary>
  /// Выводит указанное сообщение средствами,
  /// специфичными для платформы
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the user's response.</returns>
  Task<MessagingResult> ShowAsync(string message, string title);
  /// <summary>
  /// Выводит указанное сообщение средствами,
  /// специфичными для платформы
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">
  ///   The buttons to be displayed.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the user's response.</returns>
  Task<MessagingResult> ShowAsync(string message, string title,
    MessagingButtons buttons);
}

Метод ShowAsync перегружен, чтобы вы могли при необходимости задавать кнопки, отображаемые вместе с сообщением. Перечисление MessagingButtons предоставляет независимую от платформы абстракцию для вывода кнопки OK или кнопок OK и Cancel, или кнопок Yes и No (рис. 3).

Рис. 3. Перечисление MessagingButtons

/// <summary>
/// Указывает кнопки, включаемые в отображаемое сообщение
/// </summary>
public enum MessagingButtons
{
  /// <summary>
  /// Показывает только кнопку OK
  /// </summary>
  OK = 0,
  /// <summary>
  /// Показывает кнопки OK и Cancel
  /// </summary>
  OKCancel = 1,
  /// <summary>
  /// Показывает кнопки Yes и No
  /// </summary>
  YesNo = 2
}

Нижележащие целочисленные значения перечисления MessagingButtons были намеренно сопоставлены с перечислением MessageBoxButton из Windows Phone, чтобы безопасно привести MessagingButtons к MessageBoxButton.

ShowAsync — это асинхронный метод, который возвращает Task<MessagingResult>, указывающее кнопку, выбранную пользователем для удаления сообщения с экрана. Перечисление MessagingResult (рис. 4) также является аппаратно-независимой абстракцией.

Рис. 4. Перечисление MessagingResult

/// <summary>
/// Представляет результат сообщения,
/// отображаемого пользователю
/// </summary>
public enum MessagingResult
{
  /// <summary>
  /// Это значение в настоящее время не используется
  /// </summary>
  None = 0,
  /// <summary>
  /// Пользователь щелкнул кнопку OK
  /// </summary>
  OK = 1,
  /// <summary>
  /// Пользователь щелкнул кнопку Cancel
  /// </summary>
  Cancel = 2,
  /// <summary>
  /// Пользователь щелкнул кнопку Yes
  /// </summary>
  Yes = 6,
  /// <summary>
  /// Пользователь щелкнул кнопку No
  /// </summary>
  No = 7
}

В этом примере интерфейс IMessagingManager и перечисления MessagingButtons и MessagingResult являются портируемыми, а значит, и общим кодом внутри PCL.

Абстрагировав специфичную для платформ функциональность в PCL, вы должны предоставить реализации интерфейса IMessagingManager, специфичные для приложений Windows Store и Windows Phone. На рис. 5 показана реализация для приложений Windows Phone, а на рис. 6 — для приложений Windows Store.

Рис. 5. MessagingManager — реализация для Windows Phone

/// <summary>
/// Реализация для Windows Phone интерфейса
/// <see cref="T:IMessagingManager"/>
/// </summary>
internal class MessagingManager : IMessagingManager
{
  /// <summary>
  /// Инициализирует новый экземпляр класса
  /// <see cref="T:MessagingManager"/>
  /// </summary>
  public MessagingManager()
  {
  }
  /// <summary>
  /// Отображает указанное сообщение,
  /// используя специфичные для платформы средства
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(string message, string title)
  {
    MessagingResult result = await this.ShowAsync(message, title,
       MessagingButtons.OK);
    return result;
  }
  /// <summary>
  /// Отображает указанное сообщение,
  /// используя специфичные для платформы средства
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">
  ///   The buttons to be displayed.</param>
  /// <exception cref="T:ArgumentException"/> The specified
  ///   value for message or title is <c>null</c> or empty.
  /// </exception>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(
    string message, string title, MessagingButtons buttons)
  {
    if (string.IsNullOrEmpty(message))
    {
      throw new ArgumentException(
        "The specified message cannot be null or empty.", "message");
    }
    if (string.IsNullOrEmpty(title))
    {
      throw new ArgumentException(
        "The specified title cannot be null or empty.", "title");
    }
    MessageBoxResult result = MessageBoxResult.None;
    // Определяем, связан ли вызвавший поток с Dispatcher
    if (App.RootFrame.Dispatcher.CheckAccess())
    {
      result = MessageBox.Show(message, title,
        (MessageBoxButton)buttons);
    }
    else
    {
      // Асинхронно выполняем в потоке,
      // с которым сопоставлен Dispatcher
      App.RootFrame.Dispatcher.BeginInvoke(() =>
      {
        result = MessageBox.Show(message, title,
          (MessageBoxButton)buttons);
      });
    }
    return (MessagingResult) result;
  }
}

Рис. 6. MessagingManager — реализация для Windows Store

/// <summary>
/// Реализация для Windows Store интерфейса
/// <see cref="T:IMessagingManager"/>
/// </summary>
internal class MessagingManager : IMessagingManager
{
  /// <summary>
  /// Инициализирует новый экземпляр класса
  /// <see cref="T:MessagingManager"/>
  /// </summary>
  public MessagingManager()
  {
  }
  /// <summary>
  /// Отображает указанное сообщение,
  /// используя специфичные для платформы средства
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(string message, string title)
  {
    MessagingResult result = await this.ShowAsync(message, title,
      MessagingButtons.OK);
    return result;
  }
  /// <summary>
  /// Отображает указанное сообщение,
  /// используя специфичные для платформы средства
  /// </summary>
  /// <param name="message">
  ///   The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">
  ///   The buttons to be displayed.</param>
  /// <exception cref="T:ArgumentException"/> The specified
  ///   value for message or title is <c>null</c> or empty.
  /// </exception>
  /// <exception cref="T:NotSupportedException"/>
  /// The specified <see cref="T:MessagingButtons"/> value
  ///   is not supported.</exception>
  /// <returns>A <see cref="T:MessagingResult"/>
  ///   value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(
    string message, string title, MessagingButtons buttons)
  {
    if (string.IsNullOrEmpty(message))
    {
      throw new ArgumentException(
        "The specified message cannot be null or empty.", "message");
    }
    if (string.IsNullOrEmpty(title))
    {
      throw new ArgumentException(
        "The specified title cannot be null or empty.", "title");
    }
    MessageDialog dialog = new MessageDialog(message, title);
    MessagingResult result = MessagingResult.None;
    switch (buttons)
    {
      case MessagingButtons.OK:
        dialog.Commands.Add(new UICommand("OK",
          new UICommandInvokedHandler((o) => result = MessagingResult.OK)));
        break;
      case MessagingButtons.OKCancel:
        dialog.Commands.Add(new UICommand("OK",
          new UICommandInvokedHandler((o) => result = MessagingResult.OK)));
        dialog.Commands.Add(new UICommand("Cancel",
          new UICommandInvokedHandler((o) => result = MessagingResult.Cancel)));
        break;
      case MessagingButtons.YesNo:
        dialog.Commands.Add(new UICommand("Yes",
          new UICommandInvokedHandler((o) => result = MessagingResult.Yes)));
        dialog.Commands.Add(new UICommand("No",
          new UICommandInvokedHandler((o) => result = MessagingResult.No)));
        break;
      default:
        throw new NotSupportedException(
          string.Format("MessagingButtons.{0} is not supported.",
          buttons.ToString()));
            }
    dialog.DefaultCommandIndex = 1;
    // Определяем, связан ли вызвавший поток с Dispatcher
    if (Window.Current.Dispatcher.HasThreadAccess)
    {
      await dialog.ShowAsync();
    }
    else
    {
      // Асинхронно выполняем в потоке,
      // с которым сопоставлен Dispatcher
      await Window.Current.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal, async () =>
      {
        await dialog.ShowAsync();
      });
    }
    return result;
  }
}

Чтобы вывести сообщение, версия класса MessagingManager для Windows Phone использует специфичный для платформы класс MessageBox. Нижележащие целочисленные значения перечисления MessagingButtons были намеренно сопоставлены с перечислением MessageBoxButton из Windows Phone, чтобы вы могли безопасно приводить MessagingButtons к MessageBoxButton. Точно так же нижележащие целочисленные значения перечисления MessagingResult обеспечивают безопасное преобразование в перечисление MessageBoxResult.

Версия класса MessagingManager для Windows Store на рис. 6 использует с той же целью класс MessageDialog из Windows Runtime. Нижележащие целочисленные значения перечисления MessagingButtons были намеренно сопоставлены с перечислением MessageBoxButton из Windows Phone, чтобы вы могли безопасно приводить MessagingButtons к MessageBoxButton.

Встраивание зависимостей

При архитектуре приложения, продемонстрированной на рис. 1, IMessagingManager предоставляет специфичную для платформы абстракцию для вывода сообщений пользователям. Теперь я задействую шаблоны встраивания зависимостей, чтобы включить специфичные для платформ реализации этой абстракции в портируемый код. В примере на рис. 7 HelloWorldViewModel использует встраивание конструктора, чтобы включить специфичную для платформы реализацию интерфейса IMessagingManager. Затем метод HelloWorldViewModel.DisplayMessage использует встроенную реализацию для отображения сообщения пользователю. Если вы хотите узнать больше о встраивании зависимостей, советую прочитать книгу Марка Симанна (Mark Seemann) «Dependency Injection in .NET» (Manning Publications, 2011, bit.ly/dotnetdi).

Рис. 7. Портируемый класс HelloWorldViewModel

/// <summary>
/// Предоставляет портируемую модель представления
/// приложению Hello World
/// </summary>
public class HelloWorldViewModel : BindableBase
{
  /// <summary>
  /// Сообщение, отображаемое диспетчером сообщений
  /// </summary>
  private string message;
  /// <summary>
  /// Заголовок сообщения, отображаемого диспетчером сообщений
  /// </summary>
  private string title;
  /// <summary>
  /// Специфичный для платформы экземпляр интерфейса
  /// <see cref="T:IMessagingManager"/>
  /// </summary>
  private IMessagingManager MessagingManager;
  /// <summary>
  /// Инициализирует новый экземпляр класса
  /// <see cref="T:HelloWorldViewModel"/>
  /// </summary>
  public HelloWorldViewModel(IMessagingManager messagingManager,
    string message, string title)
  {
    this.messagingManager = MessagingManager;
    this.message = message;
    this.title = title;
    this.DisplayMessageCommand = new Command(this.DisplayMessage);
  }
  /// <summary>
  /// Получает команду отображения сообщения
  /// </summary>
  /// <value>The display message command.</value>
  public ICommand DisplayMessageCommand
  {
    get;
    private set;
  }
  /// <summary>
  /// Выводит сообщение, используя специфичный
  /// для платформы диспетчер сообщений
  /// </summary>
  private async void DisplayMessage()
  {
    await this.messagingManager.ShowAsync(
      this.message, this.title, MessagingButtons.OK);
  }
}

Компоненты Windows Runtime

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

Благодаря возможности использования неуправляемого C++-кода между приложениями Windows Store и Windows Phone компоненты Windows Runtime являются великолепным выбором для написания операций с интенсивными вычислениями на C++, который обеспечивает наибольшее быстродействие.

API-определения внутри компонентов Windows Runtime предоставляются в метаданных, содержащихся в файлах .winmd. С помощью этих метаданных языковые проекции (language projections) позволяют определять на применяемом вами языке, как в нем используются такие API. В табл. 1 перечислены поддерживаемые языки для создания и использования компонентов Windows Runtime. На момент написания этой статьи создавать оба типа компонентов можно было только на C++.

Табл. 1. Создание и использование компонентов Windows Runtime

ПлатформаСоздаютсяИспользуются
Компоненты Windows RuntimeC++, C#, Visual BasicC++, C#, Visual Basic, JavaScript
WКомпоненты Windows Phone RuntimeC++C++, C#, Visual Basic


В следующем примере я продемонстрирую, как C++-класс, написанный для вычисления чисел Фибоначчи, можно использовать между приложениями Windows Store и Windows Phone. На рис. 8 и 9 показаны реализации класса FibonacciCalculator на C++/CX (Component Extensions).

Рис. 8. Fibonacci.h

#pragma once
namespace MsdnMagazine_Fibonacci
{
  public ref class FibonacciCalculator sealed
  {
  public:
    FibonacciCalculator();
    uint64 GetFibonacci(uint32 number);
  private:
    uint64 GetFibonacci(uint32 number, uint64 p0, uint64 p1);
  };
}

Рис. 9. Fibonacci.cpp

#include "pch.h"
#include "Fibonacci.h"
using namespace Platform;
using namespace MsdnMagazine_Fibonacci;
FibonacciCalculator::FibonacciCalculator()
{
}
uint64 FibonacciCalculator::GetFibonacci(uint32 number)
{
  return number == 0 ? 0L : GetFibonacci(number, 0, 1);
}
uint64 FibonacciCalculator::GetFibonacci(
  uint32 number, uint64 p0, uint64 p1)
{
  return number == 1 ? p1 : GetFibonacci(
    number - 1, p1, p0 + p1);
}

На рис. 10 вы видите структуру решения в Visual Studio Solution Explorer для примеров в этой статье и можете сами убедиться, что в обоих компонентах содержатся одни и те же файлы исходного кода на C++.

*
Рис. 10. Visual Studio Solution Explorer

Вариант Add as Link в Visual Studio

Добавляя существующий элемент в проект Visual Studio, вы, вероятно, замечали небольшую стрелку справа от кнопки Add. Если щелкнуть эту стрелку, у вас появится выбор: Add или Add as Link. Если вы выберете Add по умолчанию для файла, этот файл будет скопирован в проект, и на диске (а также в системе контроля версий, если она применяется) окажутся две независимые копии данного файла. Если же вы выберете Add as Link, на диске и в системе контроля версий будет только один экземпляр файла, что может быть чрезвычайно полезно для управлениями его версиями. При добавлении существующих файлов в проекты Visual C++ это поведение по умолчанию, и в диалоге Add Existing Item другой вариант для кнопки Add не появится. Подробнее о совместном использовании кода через Add as Link см. в Dev Center по ссылке bit.ly/addaslink.

Windows Runtime API не являются портируемыми, и поэтому их нельзя совместно использовать для разных платформ в PCL. Windows 8 и Windows Phone 8 предоставляют подмножество Windows Runtime API, поэтому вы можете писать код с применением этого подмножества и в дальнейшем разделять его между приложениями двух типов, используя Add as Link. Все детали по этому общему подмножеству Windows Runtime API см. в Dev Center по ссылке bit.ly/wpruntime.

Заключение

В Windows 8 и Windows Phone 8 есть разные способы совместного использования кода между этими двумя платформами. В этой статье я рассмотрел, как сделать общим портируемый код, используя PCL с совместимостью на уровне двоичного кода, и как можно абстрагировать специфичную для платформ функциональность. Затем я продемонстрировал, как совместно использовать непортируемый неуправляемый код с помощью компонентов Windows Runtime. Наконец, мы кратко обсудили вариант Add as Link в Visual Studio.

Применительно к архитектуре я обратил ваше внимание на то, что шаблоны, способствующие разделению обязанностей, такие как MVVM, могут быть полезны для поддержки совместного использования кода и что шаблоны встраивания зависимостей позволяют задействовать в общем коде специфичную для платформ функциональность. Более подробное руководство по совместному использованию кода между приложениями Windows Store and Windows Phone вы найдете в Windows Phone Dev Center по ссылке aka.ms/sharecode, а полезное приложение-пример PixPresenter — по ссылке bit.ly/pixpresenter.

Автор: Дуг Холланд  •  Иcточник: MSDN  •  Опубликована: 16.09.2013
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Windows Store.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.