Desenvolvimento - Mobile

MSMQ Message Queueing usando o Compact Framework 2.0

Neste artigo iremos abordar o uso de filas (queues) nas aplicações para dispositivos móveis e criaremos uma simples aplicação para enviar e receber mensagens. Esta aplicação irá instalar automaticamente o serviço do MSMQ (caso já não esteja instalado).

por José Antonio Leal de Farias



Introdução

Neste artigo iremos abordar o uso de filas (queues) nas aplicações para dispositivos móveis e criaremos uma simples aplicação para enviar e receber mensagens. Esta aplicação irá instalar automaticamente o serviço do MSMQ (caso já não esteja instalado).

O MSMQ é a tecnologia da Microsoft para o enfileiramento de mensagens, que permite que aplicações distintas, não necessariamente na mesma máquina, enviem mensagens de uma para a outra. Parte desta tecnologia de "armazene-e-envie" contém um mecanismo de tolerância a falhas que garante (ou pelo menos tenta) a entrega dessas mensagens. A prioridade de mensagens também pode ser definida facilmente.

O uso de filas é bastante popular em aplicações transacionais e para garantir escalabilidade em soluções corporativas de alto desempenho. Muitas vezes nossa aplicação móvel tem de se comunicar com o "back-end" através dessas filas e mostraremos aqui um meio simples de tornar isso possível. Não vou tratar do conceito de filas em si aqui pois tornaria o artigo muito longo. Vou supor que o leitor tem conhecimento básico do MSMQ e do uso de filas em suas soluções. Vamos lá!

Criando o projeto

Como sempre, crie um novo "Smart Device Project" e adicione uma referência para o assembly System.Messaging. Esse assembly contém as classes que iremos precisar.

Depois modifique a propriedade MinimizeBox do form para false, para que os usuários possam fechar realmente a aplicação e não apenas minimizá-la. Depois adicione um TextBox, 2 Buttons e um Label, nomeando-os como txtSendMsg, btnSend, btnReceive e lblReceiveMsg respectivamente. O primeiro botão enfileira a mensagem que o usuário digitou no TextBox na fila de mensagens e o segundo botão recebe essa mensagem da fila. Mesmo se o usuário fechar a aplicação, as mensagens não recebidas continuarão na fila e poderão ser recebidas normalmente (porém um simples reset limpa a fila). Tente fazer uma interface como na figura abaixo:

Agora, como nós queremos que a aplicação instale automaticamente o MSMQ, caso seja necessário, vou ensinar um artifício que é interessante em diversas aplicações.

Primeiro vá ao Microsoft Mobile Development Center e clique no link "Redistributable Server Components for Windows Móbile 5.0" para baixar o pacote. Nós vamos usar o arquivo msmq.ARM.CAB que está neste pacote. Quando baixar o pacote, descompacte esse arquivo do pacote pois só precisamos desse arquivo CAB para instalar o MSMQ no device.

De volta ao Visual Studio, adicione esse arquivo CAB ao seu projeto (Add->Existing Item..). Clique no arquivo CAB que você adicionou e modifique a propriedade "Copy to Output Directory" para "Copy if newer" para ter certeza de que teremos sempre o CAB mais novo no mesmo diretório de deploy da aplicação.

Agora vamos programa. Antes de mais nada adicione os namespaces ao formulário:

using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;

No formulário, adicione as seguintes declarações necessárias ao P/Invoke da função CreateProcess da API do Windows Mobile. Nós precisamos dessa função para dar início a instalação do arquivo CAB:

public class ProcessInfo
{
  public IntPtr hProcess;
  public IntPtr hThread;
  public Int32 ProcessId;
  public Int32 ThreadId;
}

[DllImport("CoreDll.DLL", SetLastError = true)]
private extern static
    int CreateProcess(String imageName,
    String cmdLine,
    IntPtr lpProcessAttributes,
    IntPtr lpThreadAttributes,
    Int32 boolInheritHandles,
    Int32 dwCreationFlags,
    IntPtr lpEnvironment,
    IntPtr lpszCurrentDir,
    IntPtr lpsiStartInfo,
    ProcessInfo pi);
[DllImport("CoreDll.dll")]
private extern static  Int32 GetLastError();

[DllImport("CoreDll.dll")]
private extern static
    Int32 GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);

[DllImport("CoreDll.dll")]
private extern static
     Int32 CloseHandle(IntPtr hProcess);

[DllImport("CoreDll.dll")]
private extern static
    IntPtr ActivateDevice(
      string lpszDevKey,
        Int32 dwClientInfo);

[DllImport("CoreDll.dll")]
private extern static
    Int32 WaitForSingleObject(IntPtr Handle,
    Int32 Wait);

public static bool CreateProcess(String ExeName, String CmdLine)
{
  Int32 INFINITE;
  unchecked { INFINITE = (int)0xFFFFFFFF; }
  ProcessInfo pi = new ProcessInfo();
  if (CreateProcess(ExeName, CmdLine, IntPtr.Zero, IntPtr.Zero,
      0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, pi) == 0)
  {
    return false;
  }
  WaitForSingleObject(pi.hProcess, INFINITE);
  Int32 exitCode;
  if (GetExitCodeProcess(pi.hProcess, out exitCode) == 0)
  {
    MessageBox.Show("Falha no método GetExitCodeProcess");
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return false;
  }
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
  if (exitCode != 0)
    return false;
  else
    return true;
} 

Essa função é bastante interessante. Mantenha-a em sua biblioteca de funções preferidas..:)
Agora no evento OnLoad do formulário, nós iremos verificar se o MSMQ já está instalado no device e iremos iniciar a sua instalação caso seja necessário. Insira então o código abaixo:

string MSMQ_ADM = @"\windows\msmqadm.exe";
if (!CreateProcess(MSMQ_ADM, "status"))
{
  if (!File.Exists(MSMQ_ADM) ||
     !File.Exists(@"\windows\msmqd.dll") ||
     !File.Exists(@"\windows\msmqrt.dll"))
  {
    //install msmq
    string _path = 
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
    string docname = _path + "\\msmq.ARM.CAB";
    CreateProcess("wceload.exe", "/noui \"" + _path + "\\msmq.ARM.CAB\"");
  }
  //check again
  if (!File.Exists(@"\windows\msmqadm.exe"))
  {
    MessageBox.Show("falha na instalação do CAB msmq");
    Close();
  }
  else //register, start and activate service
  {
    CreateProcess(MSMQ_ADM, "register cleanup");
    if (CreateProcess(MSMQ_ADM, "register install")
      && CreateProcess(MSMQ_ADM, "register") 
      && CreateProcess(MSMQ_ADM, "enable binary"))
    {
      IntPtr handle = ActivateDevice(@"Drivers\BuiltIn\MSMQD", 0);//device registry key
      CloseHandle(handle);
      if (CreateProcess(MSMQ_ADM, "status")) return; //successo!
    }
    MessageBox.Show("Falha na incialização do msmq");
    Close();
  }
} 

Veja que ainda não trabalhamos diretamente com a fila. Até agora o código criado foi apenas para instalar e iniciar o MSMQ no device se necessário. Não se preocupe, usar a fila é infinitamente mais simples. Primeiro vamos enfileirar uma mensagem. Adicione o código abaixo no evento de Click do botão que envia a mensagem (btnSend):

if (txtSendMsg.Text.Trim() == "") return;
string strDestQ = @".\private$\testq"; //nome da fila 
try
{
  if (!MessageQueue.Exists(strDestQ))
    MessageQueue.Create(strDestQ);
  MessageQueue mq = new MessageQueue(strDestQ);
  mq.Send(txtSendMsg.Text);
  txtSendMsg.Text = "";
}
catch { }

Simples não? Apenas pegamos o texto que o usuário digitou no TextBox e o enfileiramos na fila "testq".

Finalmente, no evento do Clique do botão btnReceive nós iremos receber mensagem que estiver na frente da fila "testq". Note que estamos especificando um simples timeout de 1 segundo no caso de não haver nenhuma mensagem na fila. Então digite o código abaixo:

lblReceiveMsg.Text = ""; Refresh();
MessageQueue mq = new MessageQueue(@".\private$\testq");
//especifica o formatter para des-serializar a mensagem 
mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
try
{
  Message messageReceived = mq.Receive(new TimeSpan(0, 0, 1));//timeout em 1s
  lblReceiveMsg.Text = (string)messageReceived.Body;
}
catch { lblReceiveMsg.Text = "- timeout -"; } 

E pronto! Faça o deploy e execute. A primeira execução é um pouco mais lenta, por causa da inicialização do serviço do MSMQ, mas envie e receba algumas mensagens para testar. Envie algumas mensagens, feche a retorne a aplicação e tente recebê-las. Legal não? E se você der um Reset?

O uso de filas aumenta bastante a escalabilidade e pode ser a solução para a sincronização de tarefas transacionais complexas. Num próximo artigo irei explorar o uso de filas em rede, mas o leitor mais atento já percebeu que não é preciso mais nada além de dar uma boa olhada no help do MSMQ. Boa sorte!

José Antonio Leal de Farias

José Antonio Leal de Farias - Bacharel em ciências da computação pela UFCG e atualmente é Gerente de Pesquisa e Desenvolvimento da Light Infocon S.A. É programador profissional nas linguagens C++ e C#, líder do grupo de usuários CGSharp e adora Smartphones!
Veja seu blog em
http://thespoke.net/blogs/jalf/default.aspx.