Partilhar via


Alterando interfaces de maneira compatível com versões anteriores

Os métodos explicados em The Versioning Theory for RPC and COM podem ser inaceitáveis por muitas razões. Alterar uma versão da interface de acordo com as regras requer essencialmente que os novos clientes não se comuniquem com servidores antigos. Isto é frequentemente impossível com software comercial implantado no terreno. Às vezes, o Windows introduziu alterações de interface ausentes de GUIDs ou versões alteradas. Isso foi resultado da necessidade de novos clientes se comunicarem com servidores herdados e porque a solução que um novo cliente suportaria as interfaces antigas e novas foi considerada indesejável.

Melhores práticas

Estes são os métodos razoáveis de contornar o problema de incompatibilidade de fio quando o GUID da interface e a versão não podem ser alterados.

  1. Faça com que o aplicativo esteja ciente das capacidades do outro lado.

    O cliente e o servidor têm um protocolo que permite que cada um (ou pelo menos o novo cliente) estabeleça a identidade do parceiro. Normalmente, é suficiente que o novo cliente esteja ciente dos recursos suportados por servidores antigos e novos. Isso pode ser feito facilmente quando um aplicativo mantém um contexto de conexão e é suportado por uma chamada de função do tipo XxxGetInfo, executada pelo cliente antes de realizar qualquer operação RPC. Quando um aplicativo gerencia os recursos em uma base de liberação por servidor, uma chamada com uma incompatibilidade com o servidor/cliente antigo nunca pode ocorrer, uma vez que o aplicativo controla quais chamadas são emitidas para qual servidor. A conclusão é que o aplicativo é proativo para evitar que uma incompatibilidade aconteça. Isto pode ser realizado em conjunto com a segunda prática.

  2. Introduza uma nova API remota.

    Um novo método remoto não colide com métodos existentes se for adicionado no final da interface. Clientes antigos podem chamar novos servidores como sempre fizeram. O novo cliente pode chamar o novo método sem saber a identidade do servidor, desde que observe os erros provenientes do servidor que está sendo chamado. O tempo de execução do RPC verifica sempre o número do método para cada interface antes de um despacho para garantir que o método esteja dentro de uma tabela de métodos virtuais (v-table) apropriada. Para um método que é desconhecido para um servidor, o tempo de execução RPC gera a exceção RPC_S_PROCNUM_OUT_OF_RANGE. Esta exceção só é suscitada nesta situação específica. Portanto, um novo cliente pode observar a exceção como um sinal de que a chamada foi para um servidor antigo e pode modificar seu comportamento graciosamente.

  3. Introduza novos parâmetros ou novos tipos de dados apenas nos novos métodos.

    Uma razão para introduzir um novo método é evitar a incompatibilidade de dados. Se um novo tipo de dados for introduzido ou simplesmente modificado, em princípio ele deve ser usado apenas em um novo método (ou métodos). Consulte Exemplos de alterações incompatíveis para obter exemplos de alterações de tipo de dados incompatíveis. A única exceção notável a esta regra está descrita no item quatro.

  4. Mapeie novos parâmetros ou novos tipos de dados através de um wrapper.

    Esta solução aplica-se quando um novo parâmetro ou tipo de dados tem de ser exposto a um utilizador, mas na verdade não tem de ser remoto separadamente ou pode ser mapeado para os tipos de dados ou parâmetros antigos. Por exemplo, muitas APIs do sistema acabam por executar uma chamada remota. Eles podem ou não estar fazendo algum tipo de mapeamento dos tipos de dados conhecidos do usuário para os tipos de dados realmente usados na chamada RPC subjacente. Portanto, vale sempre a pena examinar se a mudança na interface do usuário precisa se propagar como uma mudança para uma interface remota.

    Uma situação semelhante pode acontecer quando o usuário chama uma API remota diretamente, mas um wrapper pode ser introduzido para fazer um novo mapeamento de tipo ou algumas outras ações adicionais que se tornaram necessárias. A Interface Definition Language (IDL) tem várias formas de facilitar esse remapeamento, nomeadamente [call_as], [transmit_as] e [wire_marshal]. O atributo [call_as] introduz um wrapper de função no cliente e no servidor. Ambos são colocados entre o código do usuário e o marshaler. Os outros atributos lidam com o mapeamento direto de tipos. Para problemas de extensão, [call_as] é o mais frequentemente usado, e é mais fácil de entender e manipular sem armadilhas.

  5. Modifique tipos de dados por meio de uma união sem valor predefinido.

    Alterar um atributo ou tipo de dados normalmente leva à incompatibilidade de fios. Consulte Exemplos de alterações incompatíveis para obter exemplos. No entanto, no caso de uma união contratual sem uma cláusula de incumprimento, esta incompatibilidade pode ser gerida de forma semelhante ao caso de um procedimento fora do alcance, como descrito anteriormente. Este esquema é prontamente aplicável aos populares XxxINFO tipos que usam sindicatos.

    Por exemplo, uma chamada como esta

    XxxGetInfo( [in] level, [out] XxxINFO  * pInfo );
    

    poderia retornar informações no nível 1, 2 ou 3, com XxxINFO sendo uma união com três ramos: 1, 2 e 3.

  6. Use o atributo [range] para especificar o intervalo.

    Você pode especificar o atributo [range] em um tipo de escala simples sem quebrar a compatibilidade com versões anteriores. Esse atributo não afeta o formato do fio, mas durante o desempacotamento o RPC verifica o valor no fio para confirmar se ele está dentro do intervalo especificado no arquivo .idl. Caso contrário, uma exceção RPC_X_INVALID_BOUND é gerada. Isso é especialmente útil se o servidor souber do tamanho máximo de um array dimensionado.

    Por exemplo:

    HRESULT Method1( [in, range(0,100)] ULONG m, [size_is(m)] ULONG *plong); 
    

O comportamento do RPC quando o nível indicado é 4 e o braço está ausente, depende da definição da união. Para uma união com a cláusula padrão definida, RPC transmite um tipo indicado na cláusula padrão para qualquer coisa diferente dos rótulos de braço conhecidos (neste caso, qualquer coisa diferente de 1, 2 ou 3). Para uma união sem padrão, o unmarshaler levanta uma exceção porque, por definição, não há um valor padrão para recorrer. A exceção é RPC_S_INVALID_TAG.

Novamente, um novo cliente pode ajustar seu comportamento ao descobrir que chamou um servidor antigo.

O que resulta dessas práticas recomendadas é que, se um tipo de dados remotável deve ser projetado para poder ser estendido no futuro, use uma união sem valor padrão no arquivo IDL. Dada uma escolha, uma união encapsulada é ligeiramente mais limpa.

Devido a peculiaridades da representação interna do protocolo de fio NDR64, a recomendação para adicionar armas fornecida anteriormente nesta seção precisa ser qualificada da seguinte forma: O novo braço que está sendo adicionado não pode alterar o alinhamento da união e, em particular, o maior alinhamento das armas não deve mudar. Isso normalmente não é um problema, pois um ponteiro em um braço força o alinhamento para 8. Um design em que cada braço é um ponteiro para um tipo de braço é uma maneira clara de satisfazer o requisito.