Layout de vtable incorreto para classe exportada por DLL:pedido de esclarecimento sobre cabeçalhos e construção de vtable

StackOverflow https://stackoverflow.com/questions/3453402

Pergunta

Embora o problema em questão seja resolvido, fiquei um pouco confuso sobre quais dados são usados ​​para construir as vtables para uma classe e onde o layout da vtable é armazenado.Se alguém puder me esclarecer ou indicar alguma informação que possa saciar minha curiosidade, eu agradeceria muito.

Fundo

  1. Dois projetos VC6.0 separados:um para um exe, um para uma dll.
  2. O aplicativo contém arquivos .lib, .dll e .h de versões de projetos dll.
  3. Quando uma nova versão estiver pronta, os arquivos .lib, .dll e .h serão copiado no projeto exe do projeto dll.
  4. Eu herdei esse esquema.

Problema:

Recentemente fiz alterações na DLL e copiei .lib e .dll, mas esqueci de copiar os arquivos de cabeçalho.Houve algumas mudanças na hierarquia e, como resultado, a vtable para uma classe específica (chame-a InternalClass) havia mudado.

O exe faz não invocar diretamente nenhum método em InternalClass.Em vez disso, ele cria uma instância de uma classe diferente (chame-a InterfaceClass) que encapsula um ponteiro para um InternalClass objeto e invoca vários métodos contra esse ponteiro.

Em tempo de execução, as chamadas feitas de dentro InterfaceClass métodos para InternalClass métodos estavam na verdade invocando o métodos errados (ou seja, InterfaceClass invocaria InternalClass::A e InternalClass::B realmente funcionaria).Olhando para o asm, acontece que a correção ou conversão (desculpe se este é o jargão errado!) estava usando o correto desvio na tabela v. No entanto, a própria vtable continha a lista de ponteiros que você esperaria do velho arquivos de cabeçalho.

Pergunta real:

Percebi meu erro, copiei os arquivos de cabeçalho, recompilei e tudo ficou bem. No entanto, fico com uma pergunta incômoda:

Quando o layout das vtables é determinado e quais informações são usadas para construir a vtable para essas classes em tempo de execução?Ou seja, parece-me que o apropriado vtable deve ter sido montado quando a dll foi compilada para que os deslocamentos, etc. pudessem ser usados ​​para as chamadas de InterfaceClass para InternalClass.Por que, então, uma vtable desatualizada foi usada em tempo de execução?É o layout também determinado separadamente quando o exe é compilado usando os cabeçalhos que possui?

Não tenho certeza se isso está claro. Deixe-me saber se o que estou perguntando é muito complicado.Obrigado!

Foi útil?

Solução

Hmm, detalhes de implementação pesados ​​de um compilador de 12 anos.Quando você usa __declspec(dllexport) em uma classe, o vinculador exporta os membros da classe.Não a tabela v.Isso é reconstruído no cliente da classe quando o compilador analisa a declaração da classe no arquivo de cabeçalho.O vinculador preenche essa vtable local com os endereços dos membros exportados.

Funcionaria melhor se o compilador simplesmente exportasse a tabela v da DLL?Talvez, mas exportar esses detalhes de implementação é muito frágil.O medo de se colocar em uma situação da qual você não consegue sair por causa da compatibilidade com versões anteriores é um forte motivador, imagino.

Outras dicas

A maneira como você descreve a interação dessas duas classes não é totalmente clara em um detalhe muito importante:É o seu chamado InternalClass sendo exportado da dll ou não?Ou, colocando a questão de uma maneira diferente:O código nesses arquivos de cabeçalho copiados usa ponteiros para InternalClass ou é todos conhecimento dessa classe escondida apenas na dll?

Se a classe em questão estiver completamente oculta na DLL, não tenho certeza de como esse problema pode acontecer.Mas pelo que você descreve, realmente parece que InternalClass está sendo exposto pela dll.

Se os cabeçalhos copiados contiverem código semelhante em conceito a este:

class InterfaceClass
{
    InternalClass* Internal;

    void DoSomething()
    {
        Internal->DoSomething();
    }
};

Então o InternalClass deve ser exposto, caso contrário, o projeto do exe não conseguirá compilar o cabeçalho.(Então, afinal, não é realmente interno.)

E se for esse o caso, isso explica facilmente o seu problema.O exe está sendo construído com base em uma declaração diferente de InternalClass do que a dll foi construída.Isso significa que cada um compila sua própria versão da tabela virtual e essas versões são diferentes.

Então, em tempo de execução, o código exe recebe um objeto criado a partir da dll e, portanto, com seu ponteiro vtable apontando para a versão da dll da vtable.O código exe então tenta trabalhar com esse ponteiro vtable como se apontasse para a versão exe da vtable.E obviamente isso causa problemas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top