QueryInterface: Navegando em um objeto

Depois de ter um ponteiro inicial para uma interface em um objeto, o COM tem um mecanismo muito simples para descobrir se o objeto oferece suporte a outra interface específica e, em caso afirmativo, obter um ponteiro para ele. (Para obter informações sobre como obter um ponteiro inicial para uma interface em um objeto, consulte Obtendo um ponteiro para um objeto.) Esse mecanismo é o método QueryInterface da interface IUnknown. Se o objeto oferecer suporte à interface solicitada, o método deverá retornar um ponteiro para essa interface. Isso permite que um objeto navegue livremente pelas interfaces que um objeto suporta. QueryInterface separa a solicitação "Você suporta um determinado contrato?" do uso de alto desempenho desse contrato uma vez que as negociações tenham sido bem-sucedidas.

Quando um cliente obtém inicialmente acesso a um objeto, esse cliente receberá, no mínimo, um ponteiro de interface IUnknown (a interface mais fundamental) por meio do qual ele pode controlar o tempo de vida do objeto — informando ao objeto quando ele é feito usando o objeto — e invocar QueryInterface. O cliente é programado para pedir a cada objeto que ele consegue executar algumas operações, mas a interface IUnknown não tem funções para essas operações. Em vez disso, essas operações são expressas por meio de outras interfaces. Assim, o cliente é programado para negociar com objetos para essas interfaces. Especificamente, o cliente chamará QueryInterface para solicitar a um objeto uma interface por meio da qual o cliente pode invocar as operações desejadas.

Como o objeto implementa QueryInterface, ele tem a capacidade de aceitar ou rejeitar a solicitação. Se o objeto aceitar a solicitação do cliente, QueryInterface retornará um novo ponteiro para a interface solicitada para o cliente. Através desse ponteiro de interface, o cliente tem acesso aos métodos dessa interface. Se, por outro lado, o objeto rejeitar a solicitação do cliente, QueryInterface retornará um ponteiro nulo — um erro — e o cliente não terá nenhum ponteiro por meio do qual chamar as funções desejadas. Nesse caso, o cliente deve lidar graciosamente com essa possibilidade. Por exemplo, suponha que um cliente tenha um ponteiro para a interface A em um objeto e solicite as interfaces B e C. Suponha também que o objeto ofereça suporte à interface B, mas não ofereça suporte à interface C. O resultado é que o objeto retorna um ponteiro para B e informa que C não é suportado.

Um ponto chave é que, quando um objeto rejeita uma chamada para QueryInterface, é impossível para o cliente pedir ao objeto para executar as operações expressas por meio da interface solicitada. Um cliente deve ter um ponteiro de interface para invocar métodos nessa interface. Se o objeto se recusar a fornecer o ponteiro solicitado, o cliente deve estar preparado para prescindir, seja não fazendo o que pretendia fazer com esse objeto ou tentando recorrer a outra interface, talvez menos poderosa. Esse recurso da funcionalidade COM funciona bem em comparação com outros sistemas orientados a objetos nos quais você não pode saber se uma função funcionará até que você chame essa função e, mesmo assim, lidar com a falha é incerto. QueryInterface fornece uma maneira confiável e consistente de saber se um objeto oferece suporte a uma interface antes de tentar chamar seus métodos.

O método QueryInterface também fornece uma maneira robusta e confiável para um objeto indicar que ele não oferece suporte a um determinado contrato. Ou seja, se em uma chamada para QueryInterface alguém perguntar a um objeto "antigo" se ele suporta uma interface "nova" (uma, por exemplo, que foi inventada depois que o objeto antigo foi enviado), o objeto antigo responderá "não" de forma confiável, sem causar um travamento. A tecnologia que suporta isso é o algoritmo pelo qual os IIDs são alocados. Embora isso possa parecer um ponto pequeno, é extremamente importante para a arquitetura geral do sistema, e a capacidade de perguntar elementos legados sobre novas funcionalidades é, surpreendentemente, um recurso não presente na maioria das outras arquiteturas de objetos.

Usando e implementando IUnknown