&MCOP;: Modelo de Objeto e Fluxo
Visão Geral
O &MCOP; é o padrão que o &arts; usa para:
Comunicação entre objetos.
Transparência de rede.
Descrever interfaces de objetos.
Independência de linguagem.
Um aspecto importante do &MCOP; é a linguagem de descrição de interface, &IDL;, na qual muitas interfaces e APIs do &arts; são definidas de uma maneira independente de linguagem.
Para usar interfaces &IDL; a partir do C++, é pelo compilador &IDL; para código C++. Ao implementar uma interface, você deriva a partir da classe esqueleto que o compilador &IDL; gerou. Ao usar uma interface, você faz isso como se estivesse usando um envólucro. Desta maneira, o &MCOP; pode usar um protocolo se o objeto com que você está falando não é local - você obtém transparência de rede.
Este capítulo busca descrever os recursos básicos do modelo de objeto que resulta a partir do uso do &MCOP;, o protocolo, como usar o &MCOP; em C++ (ligação com a linguagem), e assim por diante.
Interfaces e &IDL;
Muitos dos serviços fornecidos pelo &arts;, como os módulos e o servidor de som, são definidos em termos de interfaces. Interfaces são especificadas em um formato independente de linguagem: &IDL;.
Isto permite que muitos dos detalhes da implementação como o formato de fluxos de dados multimídia, transparência de rede, e dependências de linguagem de programação, sejam ocultas a partir da especificação da interface. Uma ferramenta, o &mcopidl;, traduz a definição de interface para uma linguagem de programação específica (atualmente somente o C++ é suportado).
A ferramenta gera uma classe esqueleto com todo código correspondente e funcionalidade básica. Você deriva a partir desta classe para implementar os recursos que deseja.
O &IDL; usado pelo &arts; é semelhante ao usado pelo CORBA e DCOM.
Arquivos &IDL; podem conter:
Diretivas #include estilo-C para outros arquivos &IDL;.
Definições de enumerações e tipos de estrutura, como em C/C++.
Definições de interfaces.
Interfaces &IDL; são definidas de maneira bem semelhante às classes C++ ou estruturas C, apesar de com algumas restrições. Como no C++, interfaces pode ter subclasses de outras interfaces usando herança. Definições de interface podem incluir três coisas: fluxos, atributos e métodos.
Fluxos
Fluxos definem dados de multimídia, um dos mais importantes componentes de um módulo. Fluxos são definidos no seguinte formato:
[ async ] in|out [ multi ] type stream nome [ , nome ] ;
Fluxos possuem uma direção definida em referência ao módulo, como indicado pelos qualificadores obrigatórios in ou out. O argumento type define o tipo de dado, que pode ser qualquer um dos tipos descritos mais tarde pelos atributos (nem todos são atualmente suportados). Muitos módulos usam o tipo de fluxo audio, que é um apelido para flutuante uma vez que este é o formato de dados interno para fluxo de áudio. Fluxo múltiplos do mesmo tipo podem ser definidos na mesma definição usando nomes separados por vírgulas.
Fluxos são por padrão síncronos, o que significa que eles são contínuos e fornecem dados em uma taxa constante, como um áudio PCM. O qualificador async especifica um fluxo assíncrono, que é usado por fluxos de dados não contínuos. O exemplo mais comum de um fluxo assíncrono são mensagens &MIDI;.
A chave multi, somente válida para fluxos de entrada, indica que a interface suporta um número variável de entradas. Isto é útil para implementar dispositivos como mixadores que podem aceitar um grande número de fluxos de entrada.
Atributos
Atributos são dados associados com uma instância de uma interface. Eles são declarados como variáveis membro em C++, e podem usar qualquer um dos tipos primitivos booleano, byte, longo, string ou flutuante. Você pode também usar estruturas definidas pelo usuários ou tipos de enumeração bem como sequências delimitadas de variáveis usando a sintaxe sequência<tipo>. Atributos podem opcionalmente ser marcados como somente leitura.
Métodos
Como em C++, métodos podem ser definidos em interfaces. Os parâmetros do métodos são restritos aos mesmos tipos dos atributos. A chave oneway indica um método que retorna imediatamente e é executado de maneira assíncrona.
Interfaces Padrão
Diversas interfaces de módulos padrão já estão definidas para você no &arts;, como a StereoEffect, e SimpleSoundServer.
Exemplo
Um exemplo simples de um módulo obtido do &arts; é o módulo de retardo constante, encontrado no arquivo tdemultimedia/arts/modules/artsmodules.idl. A definição de interface é listada abaixo.
interface Synth_CDELAY : SynthModule {
attribute float time;
in audio stream invalue;
out audio stream outvalue;
};
Este módulo é herdeiro do SynthModule. Esta interface, definida em artsflow.idl, define métodos padrão implementados em todos os módulos de sintetização de música.
O efeito CDELAY retarda um fluxo de áudio estéreo de um valor de tempo especificado como um parâmetro de ponto flutuante. A definição de interface possui um atributo do tipo flutuante para armazenar o valor de retardo. Isto define dois fluxos de entrada de áudio e dois fluxos de saída de áudio (tipicamente para efeitos estéreo). Nenhum método é necessário além dos que ele herda.
Mais Sobre Fluxos
Esta seção aborda alguns tópicos adicionais relacionados com fluxos.
Tipos de Fluxo
Existem vários requisitos para como um módulo pode fazer um fluxo. Para ilustrar isto, considere estes exemplos:
Escalonar um sinal por um fator de dois.
Executar uma conversão de frequência de amostra.
Descomprimir um sinal codificado em tempo de execução.
Ler eventos &MIDI; de /dev/midi00 e inserí-los em um fluxo.
O primeiro caso é o mais simples: ao receber 200 amostras de entrada o módulo produz 200 amostras de saída. Ele somente produz saída quando ele recebe entrada.
O segundo caso produz número diferente de amostras de saída ao receber 200 amostras de entrada. Isto depende de que conversão é executada, mas o número é conhecido a seguir.
O terceiro caso é mais complicado. A partir dos dados recebidos você não pode adivinhar quanto dados os 200 bytes de entrada gerarão (provavelmente muito mais de 200 bytes, mas...).
O último caso é um módulo que se torna ativo por ele mesmo, e algumas vezes produz dados.
No &arts;-0.3.4, somente fluxos do primeiro tipo serão manipulados, e a maioria das coisas funciona bem. Este é provavelmente o que você precisa mais ao escrever módulos que processam áudio. O problema com os outros, e mais complexos tipos de fluxo, é que eles são difíceis de programar, e que você não precisa destes recursos na maioria das vezes. É por isto que nós fazemos isto com dois tipos de fluxo diferentes: síncronos e assíncronos.
Fluxos síncronos possuem estas características:
Os módulos devem ser capazes de calcular dados de qualquer tamanho, fornecida a entrada suficiente.
Todos os fluxos possuem a mesma taxa de amostragem.
A função calculateBlock() será chamada quando dados suficientes estiverem disponíveis, e o módulo pode confiar nos ponteiros que apontam para os dados.
Não existe alocação ou desalocação a ser feita.
Fluxos assíncronos, por outro lado, possuem este comportamento:
Módulos podem algumas vezes produzir dados, ou com taxa de amostragem variável, ou somente se eles tiverem entrada a partir de algum descritor arquivado. Eles não se limitam pela regra que deve ser capaz de satisfazer requisitos de qualquer tamanho
.
Fluxos assíncronos de um módulo podem ter taxas de amostragem completamente diferentes.
Fluxos de saída: existem funções explícitas para alocar pacotes, enviar pacotes - e um mecanismo de eleição adicional que lhe dirá quando você deve criar mais algum dado.
Fluxos de entrada: você obtém uma chamada quando recebe um novo pacote - você deve dizer quando você está processando todos os dados deste pacote, o que não deve acontecer de uma vez (você pode informar isto mais tarde, e se todos foram processados um pacoto, se será liberado/reusado)
Ao você declarar fluxos, você usa a chave async
para indicar que deseja criar um fluxo assíncrono. Assim, por exemplo, consideremos que você deseja converter um fluxo assíncrono de bytes para um fluxo síncrono de amostras. Sua interface pode parecer-se com isto:
interface ByteStreamToAudio : SynthModule {
async in byte stream indata; // o fluxo de amostra de entrada assíncrono
out audio stream left,right; // fluxos de amostra de saída síncronos
};
Usando Fluxos Assíncronos
Suponhamos que você decida escrever um módulo para produzir som de maneira assíncrona. Sua interface pode parecer-se com isto:
interface SomeModule : SynthModule
{
async out byte stream outdata;
};
Como enviar os dados? O primeiro método é chamado empurrar e entregar
. Com fluxos assíncronos você envia dados como pacotes. Ou seja, você envia pacotes individuais com bytes como no exemplo acima. O processo atual é: alocar um pacote, preenchê-lo, enviá-lo.
Aqui está em termos de código: Primeiro, nós alocamos um pacote:
DataPacket<mcopbyte> *packet = outdata.allocPacket(100);
Nós o preenchemos:
// molde de modo que fgets esteja satisfeito que ele seja um ponteiro (char *)
char *data = (char *)packet->contents;
// como pode ver, você pode restringir o tamanho do pacote após a alocação
// se desejar
if(fgets(data,100,stdin))
packet->size = strlen(data);
else
packet->size = 0;
Agora o enviamos:
packet->send();
Este é um exemplo bem simples, mas se você deseja enviar pacotes extamanete tão rápido quanto o recebedor possa processá-los, você precisa de outra abordagem, o método puxar e entregar
. Você pede para enviar pacotes tão rápido quanto o recebedor esteja pronto para processá-lo. Você inicia com uma determinada quantidade de pacotes que enviou. Assim que o recebedor processar um pacote após o outro, você começa a recarregar com novos dados, e enviá-los novamente.
Você inicia isto chamando o setPull. Por exemplo:
outdata.setPull(8, 1024);
Isto significa que você deseja enviar pacotes pelos dados de saída. Você deseja iniciar enviando 8 pacotes de uma vez, e assim que o recebedor processar alguns deles, você deseja receber mais.
Então, você precisa implementar um método que preencha os pacotes, que pode ser algo como isto:
void request_outdata(DataPacket<mcopbyte> *packet)
{
packet->size = 1024; // não deve ser mais de 1024
for(int i = 0;i < 1024; i++)
packet->contents[i] = (mcopbyte)'A';
packet->send();
}
É isto. Quando você não tiver mais qualquer dado, você pode começar a enviar pacotes com tamanho zero, que interromperá o processo de puxar.
Observe que é essencial fornecer ao método o nome exato request_nomedofluxo.
Nós apenas discutimos o envio de dados. O recebimento de dados é bem mais simples. Suponhamos que você tenha um filtro ToLower simples, que simplesmente converte todas as letras para minúsculas:
interface ToLower {
async in byte stream indata;
async out byte stream outdata;
};
Isto é realmente simples de implementar; aqui está toda a implementação:
class ToLower_impl : public ToLower_skel {
public:
void process_indata(DataPacket<mcopbyte> *inpacket)
{
DataPacket<mcopbyte> *outpacket = outdata.allocPacket(inpacket->size);
// converte para letras minúsculas
char *instring = (char *)inpacket->contents;
char *outstring = (char *)outpacket->contents;
for(int i=0;i<inpacket->size;i++)
outstring[i] = tolower(instring[i]);
inpacket->processed();
outpacket->send();
}
};
REGISTER_IMPLEMENTATION(ToLower_impl);
Novamente, é essencial nomear o método como process_nomedofluxo.
Como pode ver, para cada pacote de chegada você obtém uma chamada para uma função (a chamada process_indata em seu caso). Você precisa chamar o método processed() de um pacote para indicar que você já o processou.
Aqui está uma dica de implementação: se o processamento demorar muito 9&ie; se você deseja esperar pela saída da placa de som ou algo parecido). não chame o processo imediatamente, mas armazene todo pacote de dados e chame processed somente tão logo você realmente tenha processado o pacote. Deste modo, remetentes possuem a chance de saber quanto tempo ele realmente leva para fazer seu trabalho.
Como sincronização não é tão agradável com fluxos assíncronos, você deve usar fluxos síncronos sempre que possível, e fluxos assíncronos somente quando necessário.
Fluxos Padrão
Suponhamos que você tenha 2 objetos, por exemplo um AudioProducer e um AudioConsumer. O AudioProducer tem um fluxo de saída e o AudioConsumer tem um de entrada. Cada vez que você desejar conectá-los, você usará estes 2 fluxos. O primeiro uso dos padrões é para permitir que você crie conexões sem especificar as portas neste caso.
Agora suponhamos que os objetos acima possam manipular estéreo, e cada um tenha uma porta left
e right
. Você gostaria ainda de conectá-los tão facilmente quanto antes. Mas como pode o sistema de conexão saber que porta de saída conectar a qual porta de entrada? Não existe maneira de mapear corretamente os fluxos? Os padrões são então usados para especificar diversos fluxos, com uma ordem. Assim, quando você conectar um objeto com 2 fluxos de saída padrão a outro com 2 fluxos de entrada padrão, você não precisa especificar as portas, e o mapeamento será feito corretamente.
É claro, isto não se limita ao estéreo. Qualquer número de fluxos pode ser estabelecido como padrão se necessário, e a função de conexão verificará se o número de padrões de 2 objetos correspondem (na direção necessária) se você não especificar as portas a usar.
A sintaxe é a seguinte: no &IDL; você pode usar a chave default na declaração do fluxo, ou em uma linha simples. Por exemplo:
interface TwoToOneMixer {
default in audio stream input1, input2;
out audio stream output;
};
Neste exemplo, o objeto irá esperar que duas portas de entrada sejam conectadas por padrão. A ordem é a especificada na linha default, assim um objeto como este:
interface DualNoiseGenerator {
out audio stream bzzt, couic;
default couic, bzzt;
};
Criará conexões a partir de couic
para input1
, e bzzt
para input2
automaticamente. Observe que uma vez que existe somente uma saída para o mixador, ela será tornada padrão neste caso (veja abaixo). A sintaxe usada no gerador de ruído é útil para declarar uma ordem diferente da estabelecida na declaração, ou selecionar somente algumas portas como padrão. A direção das portas nesta linha serão descobertas pelo &mcopidl; assim não as especifique. Você pode misturar portas de entrada e saída na mesma linha, somente a ordem é importante.
Existem algumas regras que serão seguidas ao usar herança:
Se uma lista padrão é especificada no &IDL;, então a usa. Portas pai podem ser colocadas na lista também, sejam elas padrão no pai ou não.
Caso contrário, herda os padrões do pai. A ordem é parente1 padrão1, parente1 padrão2 ..., parente2 padrão 1... Se existir um ancestral comum usando 2 ramos pai, um lista pública virtual
mesclada é feita na qual o padrão é a primeira ocorrência da lista.
Se ainda não existir um padrão e um fluxo simples em uma direção, use-o como padrão para esta direção.
Notificações de mudança de atributos
Notificações de mudança de atributos são uma maneira de saber quando um atributo muda. Elas são um pouco semelhantes aos sinais e slots do &Qt; ou Gtk. Por exemplo, se você tiver um elemento &GUI;, um índice deslizante, que configura um número entre 0 e 100, você normalmente terá um objeto que faz algo com este número (por exemplo, ele pode estar controlando o volume de algum sinal de áudio). Assim você desejaria que sempre que o índice fosse movido, o objeto que escalona o volume fosse notificado. Uma conexão entre o remetente e o destinatário.
O &MCOP; lida com isto sendo capaz de fornecer notificações quando atributos mudam. O que quer que seja declarado como atributo
no &IDL; pode emitir estas notificações, e deve fazer isso, sempre que for modificado. O que quer que seja declarado como atributo
também pode receber estas notificações de mudaça. Assim, por exemplo, se você possui duas interfaces &IDL;, como estas:
interface Slider {
attribute long min,max;
attribute long position;
};
interface VolumeControl : Arts::StereoEffect {
attribute long volume; // 0..100
};
Você pode conectá-las usando notificações de mudanças. Isto funciona usando a operação de conexão do sistema de fluxo normal. Neste caso, o código C++ para conectar dois objetos se parecerá com isto:
#include <connect.h>
using namespace Arts;
[...]
connect(slider,"position_changed",volumeControl,"volume");
Como pode ver, cada atributo oferece dois fluxos diferentes, um para enviar as notificações de mudança, chamado nomeatributo_changed, e um para receber notificações de mudança, chamado nomeatributo.
É importante saber que as notificações de mudança e fluxos assíncronos são compatíveis. Eles também são transparentes à rede. Assim você pode conectar uma notificação de mudança de um atributo flutuante de um widget que a &GUI; possua com um fluxo assíncrono de um módulo sintetizador sendo executado em outro computador. Isto, é claro, implica que as notificações de mudança não são síncronas, ou seja, após você ter enviado a notificação de mudança, ela pode levar algum tempo até ela ser efetivamente recebida.
Enviando notificações de mudança
Ao implementar objetos que possuam atributos, você precisa enviar notificações de mudança sempre que um atributo muda. O código para fazer isto se parece com isto:
void KPoti_impl::value(float newValue)
{
if(newValue != _value)
{
_value = newValue;
value_changed(newValue); // <- envia notificação de mudança
}
}
É altamente recomendável usar um código como este para todos os objetos que você implementar, assim estas notificações de mudança podem ser usadas por outras pessoas. Você deve também no entanto esvaziar as notificação enviadas frequentemente, assim se você estiver realizando um processamento de sinal, é provavelmente melhor você manter um monitoramento ao enviar sua última notificação, de modo que você não envie uma com cada amostra que você processar.
Aplicações para notificações de mudança
Será especialmente útil o uso de notificações de mudança juntamente com escopos (coisas que visualizam dados de áudio por exemplo), elementos gui, widgets de controle, e monitoramento. Código usando isto existe em tdelibs/arts/tests, e na implementação experimental da artsgui, que você pode encontrar em tdemultimedia/arts/gui.
O arquivo .mcoprc
O arquivo .mcoprc (em cada pasta pessoal do usuário) pode ser usado para configurar o &MCOP; de diversas maneiras. Atualmente, os parâmetros possíveis são os seguintes:
GlobalComm
O nome de uma interface a ser usada para comunicação global. A comunicação global é usada para encontrar outros objetos e obter o cookie secreto. Múltiplos clientes/servidores &MCOP; que devem ser capazes de conversar um com os outros precisam ter um objeto GlobalComm que é capaz de compartilhar informações entre eles. Atualmente, os valores possíveis são Arts:TmpGlobalComm
para comunicar através da pasta /tmp/mcop-nomeusuário (que somente funcionará em um computador local) e Arts::X11GlobalComm
para comunicar através das propriedades da janela raiz no servidor X11.
TraderPath
Especifica onde procurar por informações de negociação. Você pode listar mais de uma pasta aqui, e separá-las com vírgulas, como
ExtensionPath
Especifica quais extensões de pasta (na forma de bibliotecas compartilhadas) são carregadas. Valores múltiplos podem ser especificados separados por vírgula.
Um exemplo que usa tudo acima é:
# $HOME/.mcoprc file
GlobalComm=Arts::X11GlobalComm
# se você for um desenvolvedor, isto deve ser acessado para adicionar uma pasta em
# seu diretório pessoal para o negociador/extensão de caminho seja capaz de adicionar
# componentes sem instalá-los
TraderPath="/opt/kde2/lib/mcop","/home/marcus/mcopdevel/mcop"
ExtensionPath="/opt/kde2/lib","/home/marcus/mcopdevel/lib"
&MCOP; para Usuários do CORBA
Se você já usou o CORBA antes, você verá que o &MCOP; é quase a mesma coisa. De fato, o &arts; antes da versão 0.4 usava o CORBA.
A idéia básica do CORBA é a mesma: você implementa objetos (componentes). Usando os recursos do &MCOP;, seus objetos não estão somente disponíveis como classes normais a partir do mesmo processo (através das técnicas padrão do C++) - eles também estão disponíveis para servidores remotos de maneira transparente. Para isto funcionar, a primeira coisa que você precisa fazer é especificar a interface de seus objetos em um arquivo &IDL; - da mesma maneira que o &IDL; do CORBA. Existem somente algumas poucas diferenças.
Recursos do CORBA Que Estão Faltando No &MCOP;
No &MCOP; não existe parâmetros in
e out
nas invocações de método. Parâmetros são sempre de entrada, o código de retorno é sempre de saída, o que significa que a interface:
// CORBA idl
interface Account {
void deposit( in long amount );
void withdraw( in long amount );
long balance();
};
é escrita como
// MCOP idl
interface Account {
void deposit( long amount );
void withdraw( long amount );
long balance();
};
no &MCOP;.
Não existe suporte à exceção. O &MCOP; não possui exceções - ele usa algo diferente para manipulação de erro.
Não existem tipos de união de typedefs. Eu não sei se isto é realmente uma fraqueza, uma vez que isto não é algo tão essencial assim.
Não existe suporte para passagem de interfaces ou referências de objetos
Recursos do CORBA Que São Diferentes No &MCOP;
Você declara sequências como tiposequence
no &MCOP;. Não existe necessidade de um typedef. Por exemplo, ao invés de:
// CORBA idl
struct Line {
long x1,y1,x2,y2;
};
typedef sequence<Line> LineSeq;
interface Plotter {
void draw(in LineSeq lines);
};
você escreverá
// MCOP idl
struct Line {
long x1,y1,x2,y2;
};
interface Plotter {
void draw(sequence<Line> lines);
};
Recursos do &MCOP; Que Não Existem No CORBA
Você pode declarar fluxos, que então serão avalidados pelo ambiente de trabalho do &arts;. Fluxos são declarados de maneira semelhante aos atributos. Por exemplo:
// MCOP idl
interface Synth_ADD : SynthModule {
in audio stream signal1,signal2;
out audio stream outvalue;
};
Isto diz que seu objeto aceitará dois fluxos de entrada síncrona de áudio chamados signal1 e signal2. Síncrona significa que estes fluxos enviarão x amostras por segunto (ou outro tempo), assim o agendador sempre garantirá que seja fornecido uma quantidade balanceada de dados de entrada (⪚ 200 amostras de signal1 existirão e 200 amostras de signal2 existirão). Você garantirá que se seu objeto for chamado com estas 200 amostras de signal1 + signal2, ele será capaz de produzir exatamente 200 amostras para o outvalue.
A Conexão de Linguagem com o C++ do &MCOP;
Isto difere do CORBA na maior parte:
Strings usam a classe sting do STL do C++. Quando armazenado em sequências, eles são armazenados como plano
, o que significa que eles são considerados um tipo primitivo. Assim, eles precisam de cópia.
longs são long de plano (esperado ser de 32 bits).
As sequências usam a classe vector do C++ do STL.
As estruturas são todas derivadas da classe Type do &MCOP;, e são geradas no compilador de &IDL; do &MCOP;. Quando forem armazenadas em sequências, elas não serão armazenadas planas
, mas sim como referências porque, caso contrário, iriam ocorrer várias cópias.
Implementando Objetos do &MCOP;
Após passar as interfaces pelo compilador de &IDL;, você precisa de derivar da classe _skel. Por exemplo, considere que definiu a sua interface da seguinte forma:
// IDL do MCOP: ola.idl
interface Ola {
void Ola(string s);
string concatenar(string s1, string s2);
long somar2(long a, long b);
};
Você pode passar isso pelo compilador de &IDL;, invocando o comando mcopidl ola.idl, o que por sua vez irá gerar o ola.cc e o ola.h. Para o implementar, você precisa de definir uma classe C++ que herde do esqueleto:
// Arquivo de inclusão de C++ - inclua o ola.h em algum lugar
class Ola_impl : virtual public Ola_skel {
public:
void ola(const string& s);
string concatenar(const string& s1, const string& s2);
long somar2(long a, long b);
};
Finalmente, você terá de implementar os métodos como C++ normal
// arquivo de implementação de C++
// como pode ver, as strings são passadas como referências de const string
void Ola_impl::ola(const string& s)
{
printf("Ola '%s'!\n",s.c_str());
}
// quando têm um valor a devolver são passadas como cadeias de caracteres normais
string Ola_impl::concatenar(const string& s1, const string& s2)
{
return s1+s2;
}
long Ola_impl::somar2(long a, long b)
{
return a+b;
}
Logo que faça isso, você terá um objeto que poderá comunicar-se usando o &MCOP;. Basta criar um (usando as funcionalidades normais do C++ para criar um objeto):
Servidor do Ola_impl;
E assim que forneça a alguém a referência
string referencia = servidor._toString();
printf("%s\n",referencia.c_str());
e vá para o ciclo de inatividade do &MCOP;
Dispatcher::the()->run();
As pessoas poderão acessar isso usando
// este código poderá rodar em qualquer lado - não necessariamente no mesmo
// processo (poderá também rodar em um computador/arquitetura diferentes)
Ola *h = Ola::_fromString([a referência do objeto impressa acima]);
e invocar os métodos:
if(h)
h->ola("teste");
else
printf("O acesso falhou?\n");
Considerações de Segurança do &MCOP;
Uma vez que os servidores de &MCOP; irão atender os pedidos numa porta TCP, potencialmente todos (se você estiver na Internet) poderão tentar ligar-se aos serviços do &MCOP;. Por isso, é importante autenticar os clientes. O &MCOP; usa o protocolo md5-auth.
O protocolo md5-auth faz o seguinte para garantir que só os clientes selecionados (confiáveis) poderão ligar-se a um servidor:
Ele considera que você fornece a todos os clientes um cookie secreto.
Toda vez que um cliente se conecta, ele verifica se o cliente conhece esse cookie secreto, sem o transferir de fato (de modo que nem quem esteja escutando o tráfego de rede possa descobrir).
Para fornecer a cada cliente esse cookie secreto, o &MCOP; irá (normalmente) colocá-lo na pasta mcop (em /tmp/mcop-USER/secret-cookie). Claro, você poderá copiá-lo para outros computadores. Contudo, se o fizer, use um mecanismo de transferência seguro, como o scp (do ssh).
A autenticação dos clientes usa os seguintes passos:
[SERVIDOR] gera um cookie novo (aleatório) R
[SERVIDOR] envia-o para o cliente
[CLIENTE] lê o "cookie secreto" S de um arquivo
[CLIENTE] embaralha os cookies R e S para um cookie embaralhado M usando o algoritmo MD5
[CLIENTE] envia o M para o servidor
[SERVIDOR] verifica que, ao embaralhar o R e o S obterá o mesmo resultado que o cookie M recebido do cliente. Em caso afirmativo, a autenticação foi bem-sucedida.
Este algoritmo deverá ser seguro, uma vez que
Os cookies secretos e os aleatórios são aleatórios o bastante
e
O algoritmo de dispersão do MD5 não permite descobrir o texto original
, que é o cookie secreto S e o cookie aleatório R (o qual é conhecido, de qualquer forma), a partir do cookie embaralhado M.
O protocolo &MCOP; irá iniciar todas as conexões novas com um processo de autenticação. Basicamente, assemelha-se ao seguinte:
O servidor envia uma mensagem ServerHello que descreve os protocolos de autenticação conhecidos.
O cliente envia uma mensagem ClientHello que inclui a informação de autenticação.
O servidor envia uma mensagem AuthAccept.
Para verificar se a segurança funciona de fato, nós deveremos ver como é que as mensagens se processam nas ligações não-autenticadas:
Antes de a autenticação ser bem sucedida, o servidor não irá receber outras mensagens da conexão. Em vez disso, se o servidor por exemplo estiver esperando uma mensagem ClientHello
e obtiver uma mensagem de mcopInvocation, irá interromper a conexão.
Se o cliente não enviar uma mensagem de &MCOP; totalmente válida (sem o código especial do &MCOP; no cabeçalho da mensagem) na fase de autenticação, mas sim outra coisa qualquer, a conexão será interrompida.
Se o cliente tentar enviar uma mensagem muito grande (> 4096 bytes na fase de autenticação, o tamanho da mensagem é truncado para 0 bytes, o que fará com que não seja aceita para a autenticação). Isto é para evitar que os clientes não-autenticados enviem mensagens de ⪚ 100 megabytes, o que faria com que fosse recebida e com que o servidor estourasse com falta de memória.
Se o cliente enviar uma mensagem ClientHello corrompida (uma, onde a decodificação falhe), a conexão é mais uma vez quebrada.
Se o cliente não enviar nada de nada, então ocorrerá a expiração de tempo-limite (ainda a ser implementado).
Especificação do Protocolo &MCOP;
Introdução
Existem semelhanças conceituais com o CORBA, mas pretende expandí-las de todas as formas necessárias para as operações multimídia em tempo-real.
Oferece um modelo de objetos multimídia, o qual poderá ser usado para: a comunicação entre os componentes num espaço de endereçamento (um processo) e entre componentes que existam em tarefas, processos ou mesmo máquinas diferentes.
Tudo junto, será desenhado para uma performance extremamente alta (de modo que tudo seja otimizado para ser extremamente rápido), o que é adequado para as aplicações multimídia bastante comunicativas. Por exemplo, a transmissão de vídeos é uma das aplicações do &MCOP;, onde a maioria das implementações de CORBA 'cairiam de joelhos'.
As definições das interfaces conseguem lidar com os seguintes aspectos nativamente:
Fluxos contínuos de dados (como os dados de áudio).
Eventos de fluxos de dados (como os eventos do &MIDI;).
Contagem de referências real.
e os truques mais importantes do CORBA, como
Invocações de métodos síncronas.
Invocações de métodos assíncronas.
Construção de tipos de dados definidos pelo usuário.
Herança múltipla.
Passagem de referências de objetos.
A Codificação das Mensagens do &MCOP;
Objetivos/idéias de desenho:
A codificação deverá ser simples de implementar.
A decodificação necessita que o destinatário saiba qual é o tipo que ele deseja decodificar.
O destinatário esperará usar toda a informação - por isso, só são ignorados os dados no protocolo ao nível em que:
Se você souber que vai receber um bloco de bytes, não precisa procurar em cada byte por um marcador de fim.
Se você souber que vai receber uma cadeia de caracteres, não precisa ler até encontrar o byte zero para descobrir o seu tamanho, contudo,
Se você souber que vai receber uma sequência de cadeias de caracteres, você precisa saber o tamanho de cada uma delas para descobrir o fim da sequência, uma vez que as cadeias de caracteres possuem tamanho variável. Mas se você usar as cadeias de caracteres para algo útil, você terá de fazer isso de qualquer jeito, por isso não se perde nada.
O mínimo de sobrecarga possível.
A codificação dos diferentes tipos é mostrada na tabela em baixo:
Tipo
Processo de Codificação
Resultado
void
Os tipos void são codificados, omitindo-os, de modo a que não seja nada escrito no canal.
long
é codificado como quatro bytes, em que o byte mais significativo vem primeiro, de modo a que o número 10001025 (correspondente à 0x989a81) será codificado como:
0x00 0x98 0x9a 0x81
enums
são codificados como longs
byte
é codificado como um único byte, de modo a que o byte 0x42 será codificado como:
0x42
string
é codificado como um long, contendo o tamanho da cadeia de caracteres seguinte, e pela sequência de caracteres propriamente dita, terminando com um byte zero (que está incluído na contagem do tamanho).
inclui o byte 0 final na contagem do tamanho!
O ola
seria codificado da seguinte forma:
0x00 0x00 0x00 0x04 0x6f 0x6c 0x60 0x00
boolean
é codificado como um byte, contendo 0 se for false (falso) ou 1 se for true (verdadeiro), de modo a que o valor booleano true é codificado como:
0x01
float
é codificado na representação de 4 bytes do IEEE754 - a documentação detalhada de como o IEEE funciona estão aqui: http://twister.ou.edu/workshop.docs/common-tools/numerical_comp_guide/ncg_math.doc.html e aqui: http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html. Deste modo, o valor 2,15 seria codificado como:
0x9a 0x99 0x09 0x40
struct
Uma estrutura é codificada com base no seu conteúdo. Não existem prefixos ou sufixos adicionais, por isso a estrutura
struct teste {
string nome; // que é igual a "ola"
long valor; // que é igual a 10001025 (0x989a81)
};
seria codificada como
0x00 0x00 0x00 0x04 0x6f 0x6c 0x60 0x00
0x00 0x98 0x9a 0x81
sequence
uma sequência é codificada através da listagem do número de elementos que se seguem e da descodificação dos elementos, um a um.
Por isso uma sequência de 3 longs 'a', with a[0] = 0x12345678, a[1] = 0x01 e a[2] = 0x42 seria codificada como:
0x00 0x00 0x00 0x03 0x12 0x34 0x56 0x78
0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x42
Se você precisar de fazer referência a um tipo, todos os tipos primitivos são referidos pelos nomes indicados acima. As estruturas e as enumerações possuem nomes próprios (como Header). As sequências são referidas como um *tipo normal, de modo que uma sequência de longs é um *long
e uma sequência de estruturas Header é um *Header
.
Mensagens
O formato do cabeçalho das mensagens do &MCOP; está definido pela seguinte estrutura:
struct Header {
long magic; // o valor 0x4d434f50, que é codificado como MCOP
long messageLength;
long messageType;
};
Os valores possíveis de messageTypes são atualmente
mcopServerHello = 1
mcopClientHello = 2
mcopAuthAccept = 3
mcopInvocation = 4
mcopReturn = 5
mcopOnewayInvocation = 6
Algumas notas sobre as mensagens do &MCOP;:
Todas as mensagens começam com uma estrutura Header.
Alguns dos tipos de mensagens deverão ser ignorados pelo servidor, enquanto a autenticação não estiver completa.
Depois de receber o cabeçalho, o tratamento do protocolo (conexão) poderá receber a mensagem por completo, sem olhar para o seu conteúdo.
O messageLength no cabeçalho é, obviamente em alguns casos, redundante, o que significa que esta aproximação não é minimalista no que respeita ao número de bytes.
Contudo, isto conduz a uma implementação simples (e rápida) do processamento não em bloqueante de mensagens. Com a ajuda do cabeçalho, as mensagens poderão ser recebidas pelas classes de tratamento do protocolo em segundo plano (não-bloqueante), se existirem demasiadas ligações ao servidor, todas elas poderão ser servidas em paralelo. Você não precisa de olhar para o conteúdo da mensagem para recebê-la (e para determinar que terminou), basta olhar para o cabeçalho, assim o código para isso é relativamente simples.
Logo que uma mensagem esteja lá, ela poderá ser descodificada e processada em um único passo, sem se preocupar com os casos em que nem todos os dados foram recebidos (porque o messageLength garante que tudo está lá).
Invocações
Para invocar um método remoto, você precisa enviar a seguinte estrutura no corpo de uma mensagem de &MCOP; com o messageType = 1 (mcopInvocation):
struct Invocation {
long objectID;
long methodID;
long requestID;
};
depois disso, você envia os parâmetros como uma estrutura, ⪚ se você invocar o método concatenar(string s1, string s2), você envia uma estrutura do tipo
struct InvocationBody {
string s1;
string s2;
};
se o método foi declarado como só de ida0 - o que significa que é assíncrono sem código devolvido - então isto é tudo. Caso contrário, você iria receber como resposta uma mensagem com o messageType = 2 (mcopReturn)
struct ReturnCode {
long requestID;
<tipo-a-devolver> result;
};
em que o <tipo-resultante> é o tipo do resultado. Como os tipos void são omitidos na codificação, você também poderá só escrever o requestID se regressar de um método void.
Por isso o nosso método string concatenar(string s1, string s2) iria originar um código de resultado do tipo
struct ReturnCode {
long requestID;
string result;
};
Inspecionando as Interfaces
Para fazer as invocações, você teria de conhecer os métodos que um objeto suporta. Para fazer isto, o methodID 0, 1, 2 e 3 estão pré-destinados a certas funcionalidades. Isto é
long _lookupMethod(MethodDef defMetodo); // methodID sempre a 0
string _interfaceName(); // methodID sempre a 1
InterfaceDef _queryInterface(string nome); // methodID sempre a 2
TypeDef _queryType(string nome); // methodID sempre a 3
para ler isso, você obviamente precisa de
struct MethodDef {
string methodName;
string type;
long flags; // posto a 0 por agora (será necessário na transmissão)
sequence<ParamDef> signature;
};
struct ParamDef {
string name;
long typeCode;
};
o campo parameters contém os componentes do tipo que indicam os tipos dos parâmetros. O tipo do valor devolvido é indicado no campo type do MethodDef.
Na forma restrita, só os métodos _lookupMethod() e _interfaceName() são diferentes de objeto para objeto, enquanto que o _queryInterface() e o _queryType() são sempre os mesmos.
O que são esses methodIDs? Se você fizer uma invocação de &MCOP;, irá ficar esperando para passar um número para o método que você está invocando. A razão para isso é que os números podem ser processados muito mais depressa do que as cadeias de caracteres ao executar um pedido ao &MCOP;.
Por isso, como é que obtém esses números? Se você souber a assinatura do método, existe um MethodDef que descreve o método (e que contém o nome, o tipo, os nomes e os tipos de parâmetros, entre outras coisas) e você poderá passar isso ao _lookupMethod do objeto onde deseja invocar um método. Dado que o _lookupMethod está pré-associado ao methodID 0, você não deverá ter problemas ao fazê-lo.
Por outro lado, se você não souber a assinatura do método, você poderá descobrir os métodos que são suportados, usando o _interfaceName, o _queryInterface e o _queryType.
Definições dos Tipos
Os tipos de dados definidos pelo usuário são descritos com a estrutura TypeDef:
struct TypeComponent {
string type;
string name;
};
struct TypeDef {
string name;
sequence<TypeComponent> contents;
};
Porquê o &arts; Não Usa o &DCOP;
Uma vez que o &kde; abandonou o CORBA por completo e está usando o &DCOP; em todo o sistema, naturalmente a questão a se levantar é porque o &arts; não faz isso também. Afinal de contas, o suporte do &DCOP; está no TDEApplication, é bem-mantido, supostamente integra-se bem com a libICE, entre outras coisas.
Uma vez que existirá (potencialmente) uma grande quantidade de pessoas a se perguntar se ter o &MCOP; além do &DCOP; é realmente necessário, aqui está a resposta. Não me levem a mal, porque eu não está dizendo que o o &DCOP; é ruim
. Eu estou simplesmente tentando dizer que o &DCOP; não é a solução adequada para o &arts;
(embora seja uma boa solução para outras coisas).
Primeiro, você precisa de compreender para que o &DCOP; foi criado. Desenvolvido em dois dias durante o encontro &kde;-TWO, pretendia ser o mais simples possível, um protocolo de comunicações realmente leve
. Especialmente, a implementação descartou tudo que pudesse envolver complexidade, como por exemplo um conceito completo de como os tipos de dados seriam codificados.
Ainda que o &DCOP; não se preocupe com certas coisas (por exemplo: como é que se envia uma cadeia de caracteres de forma transparente na rede?) - isso precisa de ser feito. Por isso, tudo o que o &DCOP; não faz, fica a cargo do &Qt; nos aplicativos do &kde; que usam o &DCOP; hoje em dia. Isto é em grande parte gestão de tipos (usando o operador de serialização do &Qt;).
Por isso, o &DCOP; é um protocolo mínimo que permite perfeitamente aos aplicativos do &kde; enviarem mensagens simples do tipo abrir uma janela a apontar para http://www.kde.org
ou os seus dados de configuração mudaram
. Contudo, dentro do &arts; o foco situa-se em outras coisas.
A idéia é que alguns pequenos plugins do &arts; irão comunicar-se, trocando algumas estruturas de dados como eventos MIDI
e ponteiros de posição na música
ou gráficos de fluxo
.
Estes são tipos de dados complexos, os quais deverão ser enviados entre objetos diferentes e passados como sequências ou parâmetros. O &MCOP; fornece um conceito de tipos para definir dados complexos a partir de dados mais simples (de forma semelhante às estruturas ou vetores no C++). O &DCOP; não se preocupa com os tipos como um todo, por isso este problema seria deixado para o programador - como: criar classes de C++ para os tipos, certificando-se que eles pudessem serializar-se corretamente (por exemplo: suportar o operador de transmissão do &Qt;).
Mas desta forma, eles seriam inacessíveis a tudo o que não fosse codificação direta de C++. Especificamente, você não poderia desenhar uma linguagem de scripting que conhecesse todos os plugins de tipo a que poderão estar expostos, uma vez que eles não se descrevem a si próprios.
O mesmo argumento se aplica também às interfaces. Os objetos do &DCOP; não expõem as suas relações, hierarquias de herança, etc. - se você fosse criar um navegador de objetos que lhe mostrasse que atributos tem este objeto
, seria mal-sucedido.
Embora o Matthias tenha dito que existe uma função especial functions
em cada objeto que lhe indica os métodos que um dado objeto suporta, isto deixa de fora coisas como os atributos (propriedades), as sequências e as relações de herança.
Isto quebra seriamente os aplicativos como o &arts-builder;. Mas lembre-se. o &DCOP; não pretendia ser um modelo de objetos (uma vez que o &Qt; já tem um com o moc e semelhantes), nem pretende ser algo semelhante ao CORBA, mas simplesmente uma forma de fornecer comunicação entre aplicativos.
Porque o &MCOP; ainda existe é: deverá funcionar perfeitamente com canais entre objetos. O &arts; faz um uso intensivo de pequenos plugins, que se interligam entre si com os fluxos. A versão em CORBA do &arts; tinha de introduzir uma divisão muito incômoda entre os objetos SynthModule
, os quais eram os módulos de trabalho internos que faziam a transmissão e a interface CORBA
, que era algo externo.
Muito do código preocupava-se em fazer a interação entre os objetos SynthModule
e a interface CORBA
parecer natural, mas não o era, porque o CORBA não sabia nada sobre fluxos. O &MCOP; sabe. Olhe para o código (algo como o simplesoundserver_impl.cc). Muito melhor! Fluxos podem ser declarados na interface dos módulos e implementados de forma natural.
Ninguém pode negar. Uma das razões pela qual o &MCOP; foi feito foi a velocidade. Aqui estão alguns argumentos de porque o &MCOP; será definitivamnete mais rápido que o &DCOP; (sem sequer mostrar imagens).
Uma invocação do &MCOP; terá um cabeçalho com seis long
s. Isto é:
o código do MCOP
o tipo da mensagem (invocação)
o tamanho do pedido em bytes
ID do pedido
ID do objeto destino
ID do método destino
Depois disso, seguem-se os parâmetros. Repare que a decodificação destes é extremamente rápida. Você poderá usar pesquisas em tabelas para encontrar o objeto e a função de decodificação do método, o que significa que a complexidade é O(1) (levará a mesma quantidade de tempo, independentemente de quantos objetos estão ativos, ou de quantas funções existem).
Comparando isto com o &DCOP;, verá que existem, pelo menos
uma cadeia de caracteres para o objeto destino - algo do tipo aMinhaCalculadora
um texto do tipo adicionarNumero(int,int)
para indicar qual o método
muita mais informação do protocolo adicionada pela libICE, e outros detalhes do DCOP que eu desconheço
Tudo isto é muito doloroso para decodificar, uma vez que você irá necessitar de processar o texto, procurar a função, &etc;.
No &DCOP;, todos os pedidos são passados através de um servidor (o DCOPServer). Isto significa que o processo de uma invocação síncrona se assemelha a:
O processo do cliente envia o pedido.
O DCOPserver (o intermediário) recebe a invocação e procura onde precisa ir, enviando para o servidor real
.
O processo do servidor recebe a invocação, efetua o pedido e envia o resultado.
O DCOPserver (o intermediário) recebe o resultado e ... envia-o para o cliente.
O cliente decodifica a resposta.
No &MCOP;, a mesma invocação assemelha-se ao seguinte:
O processo do cliente envia o pedido.
O processo do servidor recebe a invocação, efetua o pedido e envia o resultado.
O cliente decodifica a resposta.
Digamos que ambos foram implementados corretamente, a estratégia ponto-a-ponto do &MCOP; deverá ser mais rápida por um fator de 2, do que a estratégia de intermediário do &DCOP;. Repare contudo que existiam razões para escolher a estratégia do &DCOP;, principalmente: se você tiver 20 aplicativos rodando, se cada um estiver conversando com outro, você precisa de 20 conexões no &DCOP;, e de 200 com o &MCOP;. Contudo, no caso de multimídia, isto supostamente não será a configuração normal.
Tentou-se comparar o &MCOP; com o &DCOP;, fazendo uma invocação do tipo adicionar dois números. Alterou-se o testdcop para conseguir isto. Porém, o teste pode não ter sido confiável do lado do &DCOP;. Invocou-se o método no mesmo processo que fez a chamada para o &DCOP;, e não se soube como se ver livre de uma mensagem de depuração, pelo que foi feita redireção do resultado.
O teste só usava um objeto e uma função, esperando que os resultados do &DCOP; começassem a descer com mais objetos e funções, enquanto os resultados do &MCOP; deveriam se manter iguais. Também, o processo do dcopserver não estava conectado a nenhum dos outros aplicativos, uma vez que, se existissem outros aplicativos conectados, a performance do encaminhamento iria decrescer.
O resultado obtido foi que, enquanto o &DCOP; obteve cerca de 2000 invocações por segundo, o &MCOP; obteve um pouco mais de 8000 invocações por segundo. Isto resulta em um fator de 4. Sabe-se que o &MCOP; não está ajustado para o máximo possível, ainda. (Comparação: o CORBA, implementado no mico, faz algo entre 1000 e 1500 invocações por segundo).
Se você quiser dados brutos
, pense em fazer algum aplicativo de medida de performance para o &DCOP; e envie-o para mim.
O CORBA tinha uma funcionalidade interessante na qual você poderia usar os objetos que implementou uma vez, como processos de servidor separados
ou como bibliotecas
. Você poderia usar o mesmo código para tal, e o CORBA iria decidir transparentemente o que fazer. Com o &DCOP;, isto não é realmente pretendido, e tanto quanto se sabe não é possível realmente.
O &MCOP;, por outro lado, deverá suportá-lo desde o início. Por isso, você poderá rodar um efeito dentro do &artsd;. Mas se você for um editor de ondas, você poderá optar por rodar o mesmo efeito dentro do espaço do processo.
Embora o &DCOP; seja, em grande medida, uma forma de comunicação entre aplicativos, o &MCOP; também o é. Especialmente para a transmissão multimídia, o que é importante (uma vez que você poderá rodar vários objetos &MCOP; em paralelo, para resolver uma tarefa de multimídia no seu aplicativo).
Ainda que o o &MCOP; não o faça de momento, as possibilidades estão em aberto para implementar as funcionalidades de qualidade de serviço. Algo do gênero aquele evento &MIDI; é mesmo MUITO importante, em comparação com esta invocação
. Ou algo do tipo necessita de estar ali a tempo
.
Por outro lado, a transferência de fluxos poderá ser integrada no protocolo &MCOP; sem problemas e ser combinada com elementos de QoS. Uma vez que o protocolo poderá ser alterado, a transferência de fluxos do &MCOP; não deverá ser mais lenta do que uma transmissão convencional de TCP, mas: é mais fácil e mais consistente de usar.
Não existe necessidade de basear uma plataforma de multimídia no &Qt;. Ao decidir isso, e usando toda aquela serialização e outras funcionalidades de fluxo do &Qt;, iria conduzir facilmente a que a plataforma se tornasse apenas para o &Qt; (ou apenas para o &kde;). Quer dizer: tão logo se veja os GNOMEs começando a usar o &DCOP;, também, ou algo do gênero, provavelmente eu estarei errado.
Enquanto se sabe que o &DCOP; basicamente não sabe nada sobre os tipos de dados que envia, de modo que você poderia usar o &DCOP; sem usar o &Qt;, veja como é usado no uso do dia-a-dia do &kde;: as pessoas enviam tipos como o TQString, o QRect, o QPixmap, o QCString, ..., de um lado para o outro. Estes usam a serialização do &Qt;. Por isso, se alguém optar por suportar o &DCOP; em um programa do GNOME, ele teria de afirmar que usava os tipos TQString,... (ainda que não o faça, de fato) e emular a forma como o &Qt; faz a transmissão, ou então teria de enviar outros tipos de cadeias de caracteres, imagens e retângulos, o que deixaria de ser interoperável.
Bem, seja o que for, o &arts; pretendeu sempre funcionar com ou sem o &kde;, com ou sem o &Qt;, com ou sem o X11, e talvez com ou sem o &Linux; (e não há problema nenhum com as pessoas que o transpõem para um sistema operacional proprietário conhecido).
É a minha posição que os componentes não&GUI; deverão ser criados de forma independente da &GUI;, para possibilitar o compartilhamento destes pela maior quantidade de programadores (e usuários) possível.
É óbvio que o uso de dois protocolos de IPC pode causar inconveniências. Ainda mais, se ambos não forem normatizados. Contudo, pelas razões indicadas acima, a mudança para o &DCOP; não é uma opção. Se existir um interesse significativo em encontrar uma forma de unir os dois, ok, poderemos tentar. Até poderemos tentar fazer com que o &MCOP; fale IIOP, onde então poderemos passar a ter um ORB de CORBA.
Eu falei com o Matthias Ettrich um pouco sobre o futuro dos dois protocolos e encontramos diversas formas de como as coisas poderão seguir daqui para a frente. Por exemplo, o &MCOP; poderia tratar da comunicação de mensagens no &DCOP;, colocando os protocolos um pouco mais juntos.
Assim algumas soluções possíveis seriam:
Criar uma gateway de &MCOP; - &DCOP; (o qual deverá ser possível, e possibilitaria a interoperabilidade) - nota: existe um protótipo experimental, se você quiser ver algo sobre o assunto.
Integrar tudo o que os usuários do &DCOP; esperam no &MCOP;, e tentar apenas usar o &MCOP; - uma pessoa até poderia adicionar uma opção de intermediário
no &MCOP;, também ;)
Basear o &DCOP; no &MCOP; em vez da libICE, e começar a integrar lentamente as coisas em conjunto.
Contudo, poderá não ser a pior possibilidade para usar cada protocolo em tudo em que se pensou usar (existem algumas grandes diferenças nos objetivos de desenho), e não vale a pena tentar juntá-los num só.