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 muitos motivos. Alterar uma versão de interface de acordo com as regras exige essencialmente que novos clientes não se comuniquem com servidores antigos. Isso é frequentemente impossível com o software comercial implantado no campo. À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 para a qual um novo cliente daria suporte às interfaces antigas e novas foi considerada indesejável.
Melhor prática
Esses são os métodos razoáveis de contornar o problema de incompatibilidade de transmissão quando o GUID e a versão da interface não podem ser alterados.
Faça com que o aplicativo esteja ciente dos recursos do outro lado.
O cliente e o servidor têm um protocolo que permite que cada (ou pelo menos o novo cliente) estabeleça a identidade do parceiro. Normalmente, é suficiente fazer com que o novo cliente esteja ciente dos recursos compatíveis com servidores antigos e novos. Isso pode ser feito facilmente quando um aplicativo se mantém em um contexto de conexão e tem suporte por meio de um tipo xxxGetInfo de chamada de função executado pelo cliente antes de executar qualquer operação RPC. Quando um aplicativo gerencia os recursos em uma base de versão por servidor, uma chamada com incompatibilidade para o servidor/cliente antigo nunca pode ocorrer, pois 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. Isso pode ser executado em conjunto com a segunda prática.
Introduza uma nova API remota.
Um novo método remoto não colidirá com métodos existentes se ele 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 sempre verifica o número do método para cada interface antes de uma expedição para garantir que o método esteja dentro de uma v-table apropriada. Para um método desconhecido para um servidor, o tempo de execução RPC gera a exceção RPC_S_PROCNUM_OUT_OF_RANGE. Essa exceção é gerada somente nessa situação específica. Portanto, um novo cliente pode watch para a exceção como um sinal de que a chamada foi para um servidor antigo e pode modificar seu comportamento normalmente.
Introduza novos parâmetros ou novos tipos de dados somente nos novos métodos.
Um motivo 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 deverá 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 essa regra é descrita no item quatro.
Mapeie novos parâmetros ou novos tipos de dados por meio de um wrapper.
Essa solução se aplica quando um novo parâmetro ou tipo de dados deve ser exposto a um usuário, mas, na verdade, não precisa ser remoto separadamente ou pode ser mapeado para os tipos de dados ou parâmetros antigos. Por exemplo, muitas APIs do sistema giram e executam 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, sempre vale a pena examinar se a alteração na interface do usuário precisa ser propagada como uma alteração 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 Linguagem de Definição de Interface (IDL) tem várias maneiras de facilitar esse remapeamento, ou seja, [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 de tipo direto. Para problemas de extensão, [call_as] é o mais usado e é mais fácil de entender e manipular sem armadilhas.
Modifique os tipos de dados por meio de uma união sem padrão.
A alteração de um atributo ou tipo de dados normalmente leva à incompatibilidade de transmissão. Consulte Exemplos de alterações incompatíveis para obter exemplos. No entanto, no caso de uma união sem uma cláusula padrão, a incompatibilidade pode ser gerenciada de maneira semelhante ao caso de um procedimento fora do intervalo, conforme descrito anteriormente. Esse esquema é prontamente aplicável aos tipos xxxINFO populares que usam uniões.
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 ramificações: 1, 2 e 3.
Use o atributo [range] para especificar o intervalo.
Você pode especificar o atributo [range] em um tipo de escala simples sem interromper a compatibilidade com versões anteriores. Esse atributo não afeta o formato de transmissão, mas durante o unmarshalling RPC verifica o valor em transmissão para confirmar que ele está dentro do intervalo especificado no arquivo .idl. Caso contrário, uma exceção RPC_X_INVALID_BOUND será gerada. Isso é especialmente útil se o servidor souber o tamanho máximo de uma matriz de tamanho.
Por exemplo:
HRESULT Method1( [in, range(0,100)] ULONG m, [size_is(m)] ULONG *plong);
O comportamento de 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, o RPC transmite um tipo indicado na cláusula padrão para qualquer coisa diferente dos rótulos de braço conhecidos (nesse caso, qualquer coisa diferente de 1, 2 ou 3). Para uma união sem padrão, o unmarshaler gera uma exceção porque, por definição, não há nenhum padrão para o qual fazer fallback. A exceção é RPC_S_INVALID_TAG.
Novamente, um novo cliente pode ajustar seu comportamento ao descobrir que ele chamou um servidor antigo.
O que se segue a partir dessas práticas recomendadas é que, se um tipo de dados remoto precisar ser projetado que possa ser estendido no futuro, use uma união sem padrão no arquivo IDL. Dada a escolha, uma união encapsulada é um pouco mais limpa.
Devido a peculiaridades da representação interna do protocolo de transmissão NDR64, a recomendação para adicionar armas fornecida anteriormente nesta seção precisa ser qualificada da seguinte maneira: O novo braço que está sendo adicionado não pode alterar o alinhamento da união e, em particular, o maior alinhamento dos braços não deve mudar. Normalmente, isso não é um problema, pois um ponteiro em um braço força o alinhamento a 8. Um design em que cada braço é um ponteiro para um tipo de braço é uma maneira limpo de atender ao requisito.