Este artigo foi traduzido por máquina.

Tempo de Execução do Windows e o C++

Transferindo Aplicativos de Desktop para o Tempo de Execução do Windows

Diego Dagum

Baixar o código de exemplo

Windows 8 incorpora uma nova filosofia de design para as plataformas Microsoft. No Windows 8, você pode criar aplicativos usando tecnologias de interface do usuário como XAML e HTML5. A Microsoft fornece dois novos modelos de app: a biblioteca de tempo de execução Windows (WRL), que ajuda você a desenvolver apps da loja do Windows com c#, Visual Basic e C++ e a biblioteca do Windows para JavaScript (WinJS), que permite que você crie HTML5 e JavaScript apps.

O WRL é Windows 8 o que são a estrutura do Microsoft Foundation Class (MFC) ou as APIs do Win32 C-como para o ambiente de trabalho. Conseqüentemente, aplicações desktop existentes devem ser adaptadas para ser executado sob o Windows Runtime. O dilema vem com aplicativos que dependem fortemente de MFC, Win32 ou outras estruturas de aplicativo. O que acontece com eles, se eles são transferidos para o tempo de execução do Windows? O que acontece depois disso? É necessário manter ambas as bases de código — tablet e desktop?

Neste artigo, mostrarei como você pode identificar e extrair partes substanciais do código do aplicativo e compartilhá-los entre os dois ambientes. Você verá como esta atividade de refatoração é também uma oportunidade para alavancar alguns novo C + + 11 características de concisão e de facilidade de manutenção. E os benefícios são mais do que apenas ganhar uma nova versão da loja do Windows de uma aplicação existente; a base de código existente do aplicativo é atualizado também.

Portabilidade e seus dilemas

É sempre mais fácil construir um aplicativo com portabilidade e outros requisitos não-funcionais do zero, arquitetando-lo adequadamente. Na prática, porém, os desenvolvedores de aplicativos são frequentemente desafiados por necessidades imprevistas que surgem depois que o aplicativo foi implantado. Satisfazendo estas mais tarde necessidades pode revelar-se problemático se o aplicativo foi projetado de forma que faz com que novas características difíceis de implementar sem reescrever grandes porções. As novas peças podem eventualmente levar a uma ruptura de aplicação na produção se não cuidadosamente testado.

Por isso, resolvi usar como exemplo um aplicativo existente, em vez de criar meu próprio demo. Como você verá, eu escolhi um exemplo de calculadora MFC publicado pela Microsoft para Visual Studio 2005 (bit.ly/OO494I).

A opção de reescrever o aplicativo inteiro parece atraente num primeiro momento, porque você quer se livrar de código você não quer manter — você iria refazê-lo em vez do zero, mas desta vez fazê-lo bem. Mas a gestão não pode ser convencido porque ela corrói o retorno sobre o investimento (ROI) esperado da aplicação original, a menos que o aplicativo permanece em produção para usuários cujas plataformas não podem executar o aplicativo de novo. Se este for o caso, semelhantes duas bases de código vontade precisa ser mantida, aumentando os custos (duas vezes o trabalho — ou mais — para implementar novos recursos ou corrigir bugs, por exemplo).

É improvável que todos do código original podem ser reutilizados em diferentes ambientes (área de trabalho do Windows e o tempo de execução do Windows, neste caso). No entanto, o mais que pode ser compartilhado, os custos e, consequentemente, o maior dos lucros.

Back to Basics: Separação de preocupações

Separação de preocupações (SoC) é um conceito estabelecido hoje, publicado em muitos livros de arquitetura de software. Uma das suas consequências naturais é que código de API é coesa agrupado (para não dizer escondidos) em well-segmented componentes que oferecem uma interface abstrata para o resto. Assim, um repositório de dados concretos nunca é exposto explicitamente a código que executa a lógica de domínio, lógica de apresentação e assim por diante. Estes componentes apenas "conversar" com a interface abstrata.

SoC é hoje amplamente adotado entre os desenvolvedores. Em consequência da explosão de Web que começou no final dos anos 90, muitos aplicativos autônomos foram divididos em módulos que foram então distribuídos em camadas e camadas.

Se você tiver aplicativos desenvolvidos sem considerar a SoC, trazê-los para o mundo moderno, de refatoração. Refatoração é uma prática saudável, realizada hoje em dia, graças a ampla adoção de práticas ágeis, que promovem a construir as coisas mais simples que podem eventualmente trabalhar, recebendo todos os testes passados e maximizar taxa de transferência de software, tempo de mercado e assim por diante. No entanto, a agilidade não deixa muito espaço para habilitar novos canais até que isso se torna uma necessidade. Aqui estamos, então.

Um cenário típico de portagem

O aplicativo de exemplo do MFC que mencionei anteriormente, uma calculadora básica, é mostrado na Figura 1.


Figura 1: A calculadora do Microsoft MFC

Este exemplo é ótimo para ilustrar o processo de portabilidade, pelas seguintes razões:

  • É pequeno o suficiente para você ter a idéia geral de que ele faz.
  • É grande o suficiente para permitir-me mostrar o processo em detalhe. O aplicativo médio provavelmente terá uma base de código maior, mas a portabilidade será composto de uma repetição dos passos que vou descrever aqui.
  • O código é acoplado o suficiente para mostrar a dissociação através de refatoração. Ela foi provavelmente intencionalmente acoplada para manter a base de código compacto e compreensível. Eu vou separar o código até que recebo um comum codebase é usado pelo MFC e Windows 8 versões. Não dissocia o mais, mas eu vou sugerir que quanto mais o código pode ser dissociado.

O aplicativo Calculadora original contém duas classes: CCalc­App e CCalcDlg. CCalcApp modelos da calculadora como um processo em execução cuja InitInstance função cria um CCalcDlg (ver Figura 2). CCalcDlg modelos de janela principal, seus controles (painel e botões) e eventos associados.


Figura 2 Original calculadora amostra classe diagrama mostrando Essentials

CCalcDlg deriva de MFC CDialog e sua implementação faz tudo, desde seu mapeamento básico de mensagens de janela, suas funções e a implementação da lógica calculadora desencadeada como resposta a esses eventos. Figura 3 mostra o que acontece quando é clicado o botão de sinal de igual (presumivelmente depois foram inseridos dois operandos e um operador). Figura 4 mostra as funções de CCalcDlg que adicionar o comportamento em todos os níveis: reação do evento, a lógica de domínio e a apresentação.


Figura 3 diagrama de seqüência para um evento, clicando no botão de sinal de igual

Figure 4 CCalcDlg
// CCalcDlg.cpp
// Window messages trigger CCalcDlg function invocations
BEGIN_MESSAGE_MAP(CCalcDlg, CDialog)
  ON_WM_PAINT()
  ON_COMMAND_RANGE(IDB_0, IDB_9, OnClickedNumber)
  ON_BN_CLICKED(IDB_CLEAR, OnClickedClear)
  ON_BN_CLICKED(IDB_DIVIDE, OnClickedDivide)
  ON_BN_CLICKED(IDB_EQUAL, OnClickedEqual)
  ON_BN_CLICKED(IDB_MINUS, OnClickedMinus)
  ON_BN_CLICKED(IDB_PLUS, OnClickedPlus)
  ON_BN_CLICKED(IDB_TIMES, OnClickedTimes)
  ON_EN_SETFOCUS(IDE_ACCUM, OnSetFocusAccum)
END_MESSAGE_MAP()
 
...
// Event reaction
void CCalcDlg::OnClickedEqual() {
  PerformOperation();
  m_operator = OpNone;
}
 
// Domain logic
void CCalcDlg::PerformOperation() {
  if (m_bOperandAvail) {
    if (m_operator == OpNone)
      m_accum = m_operand;
    else if (m_operator == OpMultiply)
      m_accum *= m_operand;
    else if (m_operator == OpDivide) {
      if (m_operand == 0)
        m_errorState = ErrDivideByZero;
      else
        m_accum /= m_operand;
      }
    else if (m_operator == OpAdd)
      m_accum += m_operand;
    else if (m_operator == OpSubtract)
      m_accum -= m_operand;
  }
 
  m_bOperandAvail = FALSE;
  UpdateDisplay();
}
 
// Presentation logic
void CCalcDlg::UpdateDisplay() {
  CString str;
  if (m_errorState != ErrNone)
    str.LoadString(IDS_ERROR);
  else {
    long lVal = (m_bOperandAvail) ?
m_operand : m_accum;
    str.Format(_T("%ld"), lVal);
  }
  GetDlgItem(IDE_ACCUM)->SetWindowText(str);
}

Porque CCalcDlg é vinculado ao MFC, a lógica que quero usar na versão do aplicativo Windows Store não pode ser portada como é. Vou ter de fazer algum tipo de refatoração.

Refatoração para separar componentes reutilizáveis

Para criar uma versão Windows Store esta calculadora, não preciso de recodificar tudo. Muito do comportamento, como o que é mostrado no Figura 4, poderia ser reutilizado se não eram tão ligado para o CCalcDlg com base em MFC. Vou de refatorar o aplicativo de forma que as peças reutilizáveis (o comportamento de calculadora neste caso) estão isoladas de componentes específicos de implementação.

Eu vou assumir que você não apenas já ouviu falar sobre o padrão de arquitetura Model-View-Controller (MVC), mas também aplicaram. Eu apenas vou recapitular aqui o padrão: o modelo consiste em objetos de domínio (ambos sem monitoração de estado e não) e não sei nem se preocupam com a tecnologia de exibição. A exibição é implementada em alguma tecnologia de interação do usuário (HTML, Qt, MFC, cacau, entre outros) que é apropriada para o dispositivo de aplicação. Não sei como a lógica de domínio é implementada; sua função é apenas exibir estruturas de dados do domínio ou parte deles. O controlador atua como um intermediário, capturando a entrada do usuário para ações de gatilho no domínio, o que faz com que a vista atualizar para refletir o status mais recente.

MVC é amplamente conhecido, mas não é a única forma de isolar a interface do usuário do domínio. No cenário de calculadora vai depender uma variação de MVC, conhecida como modelo de apresentação (bit.ly/1187Bk). Inicialmente, considerei Model-View-ViewModel (MVVM), uma outra variação que é popular entre os desenvolvedores do Microsoft .NET Framework. Modelo de apresentação foi um ajuste melhor para este cenário, embora. Modelo de apresentação é ideal quando você deseja implementar uma nova tecnologia de interação do usuário (Windows 8, neste caso), mas sem alterações devem ser feitas em termos de comportamento de interface do usuário. Esse padrão ainda considera um modelo e uma vista como antes, mas a função do controlador é interpretada por uma representação abstrata do modo de exibição chamada o modelo de apresentação. Este componente implementa os comportamentos comuns do modo de exibição, incluindo parte de seu estado, sem levar em conta a tecnologia do modo de exibição.

Figura 5 retrata a versão modificada do aplicativo do MFC.


Figura 5 o Calculator::View Namespace consolida o comportamento de exibição em seu modelo de apresentação associados

CalculatorPresentationModel mantém uma referência para o View (modelado como a interface ICalculatorView), porque uma vez que é determinado que o estado de exibição foi alterado, ele chama a função UpdateDisplay. No exemplo de MFC, a vista é CCalcDlg-se porque essa é a classe que lida diretamente com o MFC.

CCalcDlg cria seu modelo de apresentação em seu construtor:

CCalcDlg::CCalcDlg(CWnd* pParent) 
  : CDialog(CCalcDlg::IDD, pParent)
{
  presentationModel_ = 
    unique_ptr<CalculatorPresentationModel>(
    new CalculatorPresentationModel(this));
  ...
}

Como você pode ver, eu alavancou um C + + 11 ponteiro inteligente aqui chamado unique_ptr (ver bit.ly/KswVGy para obter mais informações). Ponteiros inteligentes têm a capacidade de liberar o objeto que eles fazem referência quando ele já não é necessário. Eu usei o ponteiro inteligente aqui para garantir que o modelo de apresentação é destruído quando termina o ciclo de vida de vista. A vista continua capturando eventos de janela, delegando para o modelo de apresentação com ou sem massageando a entrada, como mostrado na Figura 6.

Figura 6 algumas funções de exibição mostrando a delegação

// The parameter nID contains the ASCII code of a digit
void CCalcDlg::OnClickedNumber(UINT nID) {
  ASSERT(nID >= IDB_0 && nID <= IDB_9);
  presentationModel_->ClickedNumber(nID - IDB_0);
}
 
// Unchanged delegation
void CCalcDlg::OnClickedClear() {
  presentationModel_->ClickedClear();
}
 
enum Operator { OpNone, OpAdd, OpSubtract, OpMultiply, OpDivide };
 
// The Presentation Model contains a single method for all binary operations
void CCalcDlg::OnClickedDivide() {
  presentationModel_->ClickedOperator(OpDivide);
}
 
void CalculatorPresentationModel::ClickedOperator(Operator oper) {
  // PerformOperation is now in the PresentationModel;
  // it was in CCalcDlg (now being "the View")
  PerformOperation();
  m_operator = oper;
}
 
void CalculatorPresentationModel::PerformOperation() {
  if (m_errorState != ErrNone)
    return;
 
  if (m_bOperandAvail) {
    if (m_operator == OpNone)
      m_accum = m_operand;
    else if (m_operator == OpMultiply)
      m_accum *= m_operand;
  ...
// Same as in Figure 4
 
  m_bOperandAvail = false;
  // This is an inline function defined just below
  UpdateDisplay();
}
 
// The UI refresh is deferred back to the actual View
inline void CalculatorPresentationModel::UpdateDisplay() {
  if (view_)
    view_->UpdateDisplay();
}

Você encontrará esta versão retrabalhada do MFC exemplo na pasta mfccalc na amostra complementar para download. Você pode notar que existe um modelo. Neste exemplo, a lógica de domínio teria sido uma classe que contém funções para as quatro operações aritméticas fundamentais, algo como o que é mostrado no Figura 7.

Figura 7 pura aplicação que inclui uma calculadora "Modelo"

// Namespace Calculator::Model
class CalculatorModel {
public:
  long Add(const long op1, const long op2) const {
    return op1+op2;
  }
 
  long Subtract(const long op1, const long op2) const {
    return op1-op2;
  }
 
  long Multiply(const long op1, const long op2) const {
    return op1*op2;
  }
 
  long Divide(const long op1, const long op2) const {
    if (operand2)
      return operand1/operand2;
    else
      throw std::invalid_argument("Divisor can't be zero.");  }
};
 
// Namespace Calculator::View
class CalculatorPresentationModel {
public:
  ...
void PerformOperation();
  ...
private:
  // The Presentation Model contains a reference to a Model
  unique_ptr<Model::CalculatorModel> model_;
  ...
}
 
void CalculatorPresentationModel::PerformOperation()
{
  if (m_errorState != ErrNone)
    return;
 
  // Same like before, but this time the PresentationModel asks
  // the model to execute domain activities instead of doing it itself
  if (m_bOperandAvail) {
    if (m_operator == OpNone)
      m_accum = m_operand;
    else if (m_operator == OpMultiply)
      m_accum = model_->Multiply(m_accum, m_operand);
    else if (m_operator == OpDivide) {
      if (m_operand == 0)
        m_errorState = ErrDivideByZero;
      else
        m_accum = model_->Divide(m_accum, m_operand);
    }
    else if (m_operator == OpAdd)
      m_accum = model_->Add(m_accum, m_operand);
    else if (m_operator == OpSubtract)
      m_accum = model_->Subtract(m_accum, m_operand);
  }
 
  m_bOperandAvail = false;
  UpdateDisplay();
}

Eu decidi ignorar o modelo nesse cenário pequeno, deixando sua lógica dentro do modelo de apresentação. Não é típico na maioria dos casos, e a lógica do modelo terá que ser implementada em sua própria classe ou um conjunto de classes. Se você gosta, você pode refatorar a amostra em direção a uma abordagem purista como um exercício. Se você fizer isso, você deve prestar atenção especial ao implementar a função CalculatorModel::Divide porque o modelo é um componente reutilizável que pode ser chamado por um processo automatizado, em vez de alguma interação do usuário. Nesse caso, não importaria que uma divisão por zero lança uma exceção; Nesse ponto, seria uma situação inesperada. Mas você não tem que remover o divisor diferente de zero-verificação do modelo de apresentação. Impedindo dados errôneos de encaminhamento para camadas interiores é sempre saudável. A exceção Descartado pelo modelo é uma medida de último recurso quando nada mais está disponível.

Vá em frente e executar a solução Refatorada mfccalc no companheiro de código e Observe que ele ainda se comporta como antes, que é o que eu queria: para configurar o código para uma porta sem perder a funcionalidade. Um grave processo de refatoração deve considerar um conjunto de testes automatizados para confirmar que o comportamento do aplicativo não era afectado como conseqüência. Testes de unidade nativa está disponível no Visual Studio 2012 e todas as suas edições do desenvolvedor, incluindo a livre Express para Windows 8 (o que não vem com MFC ou outros frameworks para desenvolvimento desktop, embora).

Dê uma olhada a solução Calculator\CalculatorTests no código correspondente. O namespace Calculator::Testing contém uma classe PresentationModelTest, cujos métodos de teste de CalculatorPresentationModel, como mostrado na Figura 8.

Figura 8 isolar erros com testes de unidade nativa

// Namespace Calculator::Testing
TEST_CLASS(PresentationModelTest) {
private:
  CalculatorPresentationModel presentationModel_;
public:
  TEST_METHOD_INITIALIZE(TestInit) {
    presentationModel_.ClickedClear();
  }
 
  TEST_METHOD(TestDivide) {
    // 784 / 324 = 2 (integer division)
    presentationModel_.ClickedNumber(7);
    presentationModel_.ClickedNumber(8);
    presentationModel_.ClickedNumber(4);
 
    presentationModel_.ClickedOperator(OpDivide);
 
    presentationModel_.ClickedNumber(3);
    presentationModel_.ClickedNumber(2);
    presentationModel_.ClickedNumber(4);
 
    presentationModel_.ClickedOperator(OpNone);
 
    Assert::AreEqual<long>(2, presentationModel_.GetAccum(),
      L"Divide operation leads to wrong result.");
    Assert::AreEqual<CalcError>(ErrNone, 
      presentationModel_.GetErrorState(),
      L"Divide operation ends with wrong error state.");
  }
 
  TEST_METHOD(TestDivideByZero) {
    // 784 / 0 => ErrDivideByZero
    presentationModel_.ClickedNumber(7);
    presentationModel_.ClickedNumber(8);
    presentationModel_.ClickedNumber(4);
 
    presentationModel_.ClickedOperator(OpDivide);
 
    presentationModel_.ClickedNumber(0);
 
    presentationModel_.ClickedOperator(OpNone);
 
    Assert::AreEqual<CalcError>(ErrDivideByZero, 
      presentationModel_.GetErrorState(),
      L"Divide by zero doesn't end with error state.");
  }
 
  ...
// More tests for the remaining calculator operations
};

By the way, teste de unidade é uma das consequências mais apreciadas deste padrão de modelo de apresentação: A automação de teste comportamental de interface do usuário é o mesmo para a lógica de domínio que, neste exemplo, eu incorporado no modelo de apresentação. Assim, você pode abrir novos canais para seu aplicativo (por exemplo, cacau, Qt, wxWidgets ou aquelas que mesmo não-nativas como HTML5 ou Windows Presentation Foundation) com a certeza de que erros, se houver, estão acontecendo os novos componentes de interação do usuário, em vez das existentes. Você pode aproveitar o recurso de cobertura de código para certificar-se de que todas as linhas de código são testadas pelo menos uma vez.

Abertura de uma fachada XAML para o aplicativo Calculadora

Com o código refatorado, estou pronto para criar uma nova interface de usuário do Windows para a calculadora do MFC. É simplesmente uma questão de criar uma nova visão para o modelo de apresentação padrão descrito anteriormente.

Windows 8 oferece três tecnologias: XAML, HTML e DirectX.

  • XAML é a linguagem de marcação baseada em XML que permite que você declare os elementos visuais da interface do usuário, dados para vincular controles interface do usuário e manipuladores chamar em resposta a eventos. Esses eventos são normalmente definidos no codebehind chamados componentes que podem ser criados em uma sintaxe C++ estendida conhecida como C++ componente extensões para o Runtime do Windows (C + + CX), ou criadas em outras linguagens de programação. Vou apresentar um rosto calculadora baseada em XAML.
  • HTML permite que você defina quais comportamentos de interface do usuário definidos em Java­Script executado no Internet Explorer "Chakra" motor. É possível — como vou demonstrar na próxima seção, para invocar componentes baseados em C++ a partir do código JavaScript.
  • DirectX é ideal para aplicativos de multimídia intensiva. Porque a calculadora não é um aplicativo multimídia, não vou discutir isso aqui. Aplicativos DirectX podem usar XAML por meio da interoperabilidade, que você pode ler mais sobre a bit.ly/NeUhO4.

Para criar um modo de exibição XAML, escolhi o aplicativo básico em branco na lista de modelos de Visual C++ Windows 8 e criou um projeto chamado XamlCalc.

Este modelo em branco contém apenas uma MainPage vazio, que eu vou encher com controles para tornar a contraparte Windows 8 da antiga CCalcDlg de controle na versão MFC. Isso é tudo que é necessário para o aplicativo Calculadora da porta porque consiste em uma única janela, com nenhuma navegação. Ao portar seu aplicativo, você pode considerar outros modelos, assim você pode oferecer um mecanismo de navegação de página que fornece um UX intuitivo e previsivel. Você vai encontrar orientações sobre isso na página "Projetando UX para aplicativos" (bit.ly/Izbxky).

O modelo em branco também vem com um arquivo app. XAML, efeito similar da classe CCalcApp na versão MFC (consulte Figura 2). É um carregador de bootstrap que inicializa o resto dos componentes e passa o controle para eles. A função CCalcApp::InitInstance cria uma janela de CCalcDlg, que então serve como uma interface do usuário. (Você vai encontrar todos estes na solução XamlCalc no código para download do companheiro.) No caso XAML, App::OnLaunched é gerada por padrão no arquivo de origem code-behind, App.xaml.cpp e desencadeia uma navegação inicial para MainPage:

void App::OnLaunched(
  Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ pArgs) {
  ...
// Create a Frame to act navigation context and navigate to the first page
  auto rootFrame = ref new Frame();
  if (!rootFrame->Navigate(TypeName(MainPage::typeid))) {
    throw ref new FailureException("Failed to create initial page");
  }
  ...
}

Eu uso o editor XAML built-in do Visual Studio para criar uma página calculadora imersiva arrastar controles da caixa de ferramentas e completando algum manual de edição, tais como estilos definidos pelo usuário, vinculação de dados, eventos associados e assim por diante. O XAML resultante parece com o código em Figura 9. A calculadora é mostrada no Figura 10.

Figura 9 XAML versão da calculadora MFC

<Page
  Loaded="Page_Loaded"
  x:Class="XamlCalc.MainPage" ...>
 
  <Grid Background="Maroon">
    ...
<Border Grid.Row="1" Background="White" Margin="20,0">
      <TextBlock x:Name="display_" TextAlignment="Right" FontSize="90"
        Margin="0,0,20,0" Foreground="Maroon" HorizontalAlignment="Right"
        VerticalAlignment="Center"/>
    </Border>
    <Grid Grid.Row="2">
      ...
<Button Grid.Column="0" Style="{StaticResource Number}"
        Click="Number_Click">7</Button>
      <Button Grid.Column="1" Style="{StaticResource Number}"
        Click="Number_Click">8</Button>
      <Button Grid.Column="2" Style="{StaticResource Number}"
        Click="Number_Click">9</Button>
      <Button Grid.Column="3" Style="{StaticResource Operator}"
        Click="Plus_Click">+</Button>
    </Grid>
    ...
<Grid Grid.Row="5">
      ...
<Button Grid.Column="0" Style="{StaticResource Number}"
        Click="Number_Click">0</Button>
      <Button Grid.Column="1" Style="{StaticResource Operator}"
        Click="Clear_Click">C</Button>
      <Button x:Name="button_equal_" Grid.Column="2"
        Style="{StaticResource Operator}" Click="Equal_Click"
        KeyUp="Key_Press">=</Button>
      <Button Grid.Column="3" Style="{StaticResource Operator}"
        Click="Divide_Click">/</Button>
    </Grid>
  </Grid>
</Page>


Figura 10 o olhar ea sensação da calculadora XAML

Eu defini o estilo dos botões (para números e operadores) em app. XAML, então eu não preciso repetir cores, alinhamentos, fontes e outras propriedades para cada botão. Da mesma forma, eu associado manipuladores de eventos para a propriedade clique cada botão; os manipuladores são MainPage métodos definidos no arquivo de origem code-behind MainPage.xaml.cpp. Aqui estão um par de exemplos, um para números clicados e outro para a operação de divisão:

void MainPage::Number_Click(Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ e)
{
  Button^ b = safe_cast<Button^>(sender);
  long nID = (safe_cast<String^>(b->Content)->Data())[0] - L'0';
  presentationModel_->ClickedNumber(nID);
}
 
void MainPage::Divide_Click(Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ e)
{
  presentationModel_->ClickedOperator(OpDivide);
}

Como você pode ver, esses C + + CX métodos MainPage apenas ter informações sobre o evento e dar-lhe uma instância de minha classe de C++ padrão, CalculatorPresentationModel, para realizar a atividade real da interface do usuário. Isso demonstra que é possível tomar a lógica de C++ padrão de aplicativos nativos existentes e reutilizá-lo em um novíssimo C + + aplicativo CX Windows Store. Essa reutilização diminui o custo de manter ambas as versões, como ambos eles podem aproveitar qualquer atualização dentro os componentes comuns — CalculatorPresentationModel neste caso.

Componentes específicos de implementação podem ser chamados para trás pelos componentes comuns como eles implementam interfaces bem-definidas abstratas. No meu exemplo, por exemplo, a calculadora­PresentationModel::UpdateDisplay delega o trabalho real em uma instância do ICalculatorView:

inline void CalculatorPresentationModel::UpdateDisplay(void) {
  if (view_)
    view_->UpdateDisplay();
}

A versão do MFC, ICalculatorView é implementado pela classe CCalcDlg com base em MFC. Dê uma olhada no diagrama de sequência refatorado em Figura 11 e compará-lo com o original em Figura 3.


Figura 11 diagrama de sequência para o botão de sinal de igual na versão MFC dissociado

Para manter a versão XAML análoga para o caso MFC, deve implementei ICalculatorView na MainPage. Em vez disso, eu tive que implementar ICalculatorView como uma classe diferente porque MainPage é um C + + CX de classe e, portanto, não pode derivar de uma classe C++ padrão. C++ e sua projeção no tempo de execução do Windows (C + + CX) têm sistemas de tipo diferente — que interoperam bem em qualquer caso. Implementando uma interface de C++ ICalculatorView pura não era um grande negócio:

namespace XamlCalc {
  class CalcView : public ICalculatorView {
  public:
    CalcView() {}
    CalcView(MainPage^ page) : page_(page) {}
    inline void UpdateDisplay()
      { page_->UpdateDisplay(); }
  private:
    MainPage^ page_;
  };
}

Padrão C++ e C + + classes CX não podem derivar de uns aos outros, mas eles ainda podem conter referências a si — neste caso, o page_ de membro privado, que é uma referência a um C + + CX MainPage. Para atualizar o controle de exibição na versão XAML, acabei de mudar a propriedade Text do controle TextBlock MainPage. XAML chamado display_:

void MainPage::UpdateDisplay() {
  display_->Text = (presentationModel_->GetErrorState() != ErrNone) ?
L"ERROR!" :
    safe_cast<int64_t>(presentationModel_->GetValue()).ToString();
}

Figura 12 mostra o XAML diagrama de classe calculadora.


Figura 12 o XAML-C + + CX calculadora aplicativo diagrama de classe

Figura 13 mostra a sequência correspondente à ação de pressionar o botão de sinal de igual na versão XAML.


Figura 13 o diagrama de seqüência para o botão de sinal de igual na versão XAML

Devo mencionar que o WRL fortemente promove processamento assíncrono através de todas suas APIs para manipulação de eventos. Meu código é 100 por cento síncrono, embora. Eu poderia ter feito isso assíncrono usando o baseada em tarefas biblioteca de padrões paralelos, que implementa as tarefas e continuação baseada em conceitos de assincronismo do Windows 8. Isso não era justificado no meu exemplo pequeno, mas vale a pena ler mais sobre ele na página "Programação em C++ assíncrono" em Windows Developer Center (bit.ly/Mi84D1).

Pressione F5 e executar o aplicativo para ver a versão em XAML em ação. Quando migrar ou criar seus aplicativos Windows Store, é importante que você desenha seu aplicativo interface do usuário com base em novos padrões de design experiência Windows, descritos no Microsoft Dev Center (bit.ly/Oxo3S9). Seguir os padrões recomendados para comandar, touch, orientação invertida, encantos e mais a fim de manter seu aplicativo UX intuitiva para usuários de primeira viagem.

Outro exemplo: Uma nova calculadora de HTML de interface do usuário de Windows

O exemplo XAML é suficiente para obter a amostra inicial com base em MFC calculadora execução lado a lado com Windows 8. No entanto, em determinadas circunstâncias (tais como a experiência de sua equipe ou para alavancar os ativos existentes), você pode considerar HTML e JavaScript em vez de XAML para a interface do usuário.

O padrão de design de modelo de apresentação descrito neste artigo é ainda útil, mesmo que a interface do usuário contém lógica em uma língua não C++ como JavaScript. Este milagre é possível porque, no ambiente do Windows 8, faz projetos de JavaScript para o Runtime do Windows quanto C++, tornando possível para ambos interoperar como partilham o tipo sistema estabelecido pelo WRL.

O código do companheiro, você encontrará uma solução chamada HtmlCalc que contém uma página Default. html semelhante ao MainPage. XAML. Figura 14 mostra uma descrição da interface do usuário comparável à versão XAML mostrou no Figura 9.

Figura 14 a marcação HTML para a calculadora da interface do usuário

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>HTMLCalc</title>
        <!-- WinJS references -->
        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
        <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
        <!-- HTMLCalc references -->
        <link href="/css/default.css" rel="stylesheet">
        <script src="/js/default.js"></script>
      </head>
      <body onkeypress="Key_Press()">
        <table border="0">
          <tr>
            <td class="Display" colspan="7" id="display_">0 </td>
          </tr>
          <tr>
            <td>
              <button class="Number" onclick="Number_Click(7)">7</button>
            </td>
            <td>
              <button class="Number" onclick="Number_Click(8)">8</button>
            </td>
            <td>
              <button class="Number" onclick="Number_Click(9)">9</button>
            </td>
            <td>
              <button class="Operator" onclick="Plus_Click()">+</button>
            </td>
          </tr>
          ...
    <tr>
            <td>
              <button class="Number" onclick="Number_Click(0)">0</button>
            </td>
            <td>
              <button class="Operator" onclick="Clear_Click()">C</button>
            </td>
            <td>
              <button class="Operator" onclick="Equal_Click()">=</button>
            </td>
            <td>
              <button class="Operator" onclick="Divide_Click()">/</button>
            </td>
          </tr>
        </table>
      </body>
    </html>

O papel de code-behind em páginas HTML é interpretado por código JavaScript. Na verdade, você encontrará esse código o arquivo js\default.js. Minha calculadora­PresentationModel, porque é um classe de C++ padrão, não pode ser invocado diretamente da parte de JavaScript, mas você pode fazê-lo indiretamente, através de uma ponte C + + componente CX — Calculator::View::CalcView.

Instâncias deste componente de JavaScript é tão fácil como declara o seguinte no js:

// This is JavaScript code, instancing a C++/CX proxy to my PresentationModel
var nativeBridge = new Calculator.View.CalcView();

Como exemplo desta abordagem, pressionando o botão de sinal de igual desencadeia uma chamada para a função de JavaScript a seguir:

function Equal_Click() {
    display_.textContent = nativeBridge.equal_click();
}

Propaga-se a chamada para CalcView::equal_click, que "fala" nativamente com meu CalculatorPresentationModel de C++ padrão:

String^ CalcView::equal_click() {
  presentationModel_->ClickedOperator(OpNone);
  return get_display();
}
 
String^ CalcView::get_display() {
  return (presentationModel_->GetErrorState() != ErrNone) ?
L"ERROR!" :
    safe_cast<int64_t>(presentationModel_->GetValue()).ToString();
}

Nesse cenário específico, o C + + componente CX CalcView apenas encaminha todas as solicitações para o PresentationModel de C++ padrão (consulte o diagrama de seqüência em Figura 15). Não podemos evitar isso em nosso caminho para o componente reutilizável de C++, embora (ver Figura 15).


Figura 15 o diagrama de seqüência para o botão de sinal de igual na versão HTML C++ híbrido

Porque o C + + CX proxy deve ser criado manualmente, o custo associado não deve ser ignorado. Ainda assim, você pode equilibrá-lo contra a vantagem de reutilização de componentes, como eu faço no meu cenário com CalculatorPresentationModel.

Vá em frente e pressione F5 para ver a versão HTML em ação. Mostrei como a reutilização de código C++ existente para ampliar seu alcance com o quadro romance no Windows 8, sem deixar cair os canais originais (MFC no meu caso). Agora estamos prontos para algumas reflexões finais.

Olá mundo (Real)!!!!

Meu cenário de portabilidade é um caso particular, que não pode ser o seu caso particular, que não pode ser alguém do caso particular. Muito do que eu mostrei aqui é aplicável para o cenário de calculadora de MFC, e eu poderia tomar decisões diferentes se foram portagem de um aplicativo diferente para o WRL. Por conseguinte, aqui estão algumas conclusões gerais sobre a portagem de aplicativos:

  • Objetos simples padrão — aqueles que não têm nenhuma relação específica com APIs de terceiros — têm a capacidade de reutilização máxima e, portanto, a portabilidade de baixo ou nenhum custo. Em contraste, a reutilização é restrito quando objetos têm laços explícitos para APIs não padronizados, como MFC, Qt e o WRL. Por exemplo, MFC está disponível apenas na área de trabalho Windows. Qt, por outro lado, está presente em outros ambientes, embora não em todos. Em tais casos, evite misturas que corroem a reutilização fazendo objetos de aplicativo "falar" para abstrair classes. Derivam, em seguida, essas classes para criar implementações de terceiros partidas-cientes. Olha o que eu fiz com ICalculatorView (classe abstrata) e suas implementações, CCalcDlg e XamlCalc::CalcView. Para o desenvolvimento do Windows 8, familiarizar-se com a WRL APIs e APIs do Win32 que estão substituindo. Você encontrará mais informações em bit.ly/IBKphR.
  • Apliquei o padrão de modelo de apresentação, porque meu objetivo era imitar em tempo de execução do Windows o que eu já tinha na área de trabalho. Você pode decidir cortar recursos se eles não fazem muito sentido-como aplicar estilos ao texto em um aplicativo de e-mail móvel. Ou você pode adicionar recursos que aproveitam a sua nova plataforma de destino — pense, por exemplo, imagem, estendendo-se através do multi-touch em um aplicativo do Visualizador de imagens. Em tais casos, um outro padrão de design pode ser mais apropriado.
  • O modelo de apresentação que usei é ótimo para manter a continuidade de negócios e de baixo custo de manutenção. Isso me permite entregar versões Windows loja do app sem laços de corte com clientes que preferem a opção original do MFC. Manutenção de dois canais (XAML e MFC ou HTML e MFC) não é duas vezes o custo, enquanto eu tenho componentes reutilizáveis como CalculatorPresentationModel.
  • A reutilização geral de um aplicativo é determinada pela relação de linhas de código que são comuns a todas as versões versus linhas de código para manter versões específicas (não são considerados componentes mantidos por terceiros). Há casos onde aplicativos dependem fortemente APIs não padronizados (por exemplo, um aplicativo de realidade aumentada que utiliza sensores de OpenGL e iOS). A relação de reutilização pode ser tão baixa que você pode eventualmente decidir portar o aplicativo sem reusabilidade do componente que não seja o conceitual.
  • Não comece perguntando o que poderia ter tão mal arquitetado o aplicativo existente, fazendo seu trabalho portar tão difícil. Inicie em vez de refatoração. Tenha em mente que metodologias ágeis não são destinadas a arquiteturas maduras, robustas, altamente reutilizáveis; o que eles enfatizam é o fornecimento de software. Fazendo software genérico e extensível para portabilidade e reutilização futura requer muita experiência, pois não é fácil tomar decisões no escuro.
  • Você pode portar seu aplicativo para o Windows 8, iOS ou Android, com a intenção de vendê-lo por meio de mercados destas plataformas. Nesse caso, tenha em mente que seu aplicativo deve passar um processo de certificação antes de ser aceito (bit.ly/L0sY9i). Isso poderia forçá-lo a apoiar comportamentos de interface do usuário que você nunca contemplou em sua versão original (como o primeiro toque, encantos e assim por diante). Não cumprir certas normas pode resultar em seu aplicativo ser rejeitado. Não negligencie a tal "conformidade" quando estimar os custos.

Enfrentar o desafio

O novo tempo de execução do Windows e ainda onipresente Windows desktop representam um desafio para os desenvolvedores que não querem pagar o extra custo de manter um aplicativo separado por plataforma. Ao longo deste artigo, demonstrei que existentes bases de código podem ser aproveitado não só para habilitar novos canais, mas bases de também melhorar através da qualidade do existente de refatoração código.

Diego Dagum é arquiteto de software e instrutor com mais de 20 anos de experiência na indústria. Ele pode ser contatado em email@diegodagum.com.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Marius Bancila, Angel Jesus Hernandez e a equipe do Windows 8