Pergunta

Atualmente, estou trabalhando para transportar um aplicativo Delphi 5 existente para o Delphi 2010.

É uma DLL multithread (onde os threads são gerados pelo Outlook) que carregam no Outlook. Quando compilado através do Delphi 2010, sempre que fecho um formulário, encontro uma "operação de ponteiro inválida" dentro do tmonitor.Destroy ... aquele em System.Pas, isto é.

Como este é um aplicativo existente e meio complexo, eu tenho um muito de direções para serem analisadas, e a ajuda Delphi Nem sequer documenta Mal documenta essa aula de tmonitor em particular para começar (eu a traçava a algumas postagens de Allen Bauer com informações adicionais) ... então achei que primeiro perguntaria se alguém havia encontrado isso antes ou teve alguma sugestão sobre o que poderia causar esse problema . Para o registro: não estou usando a funcionalidade Tmonitor explicitamente no meu código, estamos falando de uma porta direta do código Delphi 5 aqui.

Editar CallStack no momento O problema ocorre:

System.TMonitor.Destroy
System.TObject.Free
Forms.TCustomForm.CMRelease(???)
Controls.TControl.WndProc(???)
Controls.TWinControl.WndProc((45089, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Forms.TCustomForm.WndProc(???)
Controls.TWinControl.MainWndProc(???)
Classes.StdWndProc(15992630,45089,0,0)
Forms.TApplication.ProcessMessage(???)
Foi útil?

Solução

O ponteiro para o System.Monitor A instância de cada objeto é armazenada após todos os campos de dados. Se você escrever muitos dados para o último campo de um objeto, isso pode acontecer, você escreve um valor falso no endereço do monitor, o que provavelmente levaria a um acidente quando o destruidor do objeto tenta destruir o monitor falso. Você pode verificar se este endereço é nil no BeforeDestruction Método de seus formulários, para uma porta Straight Delphi 5, não deve haver nenhum monitor atribuído. Algo como

procedure TForm1.BeforeDestruction;
var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  Assert(MonitorPtr^ = nil);
  inherited;
end;

Se isso for um problema no seu código original, você poderá detectá -lo na versão Delphi 5 da sua DLL usando o Fastmm4 Memory Manager com todas as verificações ativadas. OTOH isso também pode ser causado pelo aumento de tamanho dos dados de caracteres nas compilações Unicode e, nesse caso, só se manifestaria nas compilações da DLL usando o Delphi 2009 ou 2010. Ainda seria uma boa idéia usar o FastMM4 mais recente com todas as verificações.

Editar:

Do seu rastreamento de pilha, parece que o monitor está realmente atribuído. Para descobrir por que eu usaria um ponto de interrupção de dados. Não consegui fazê -los trabalhar com Delphi 2009, mas você pode fazê -lo facilmente com o Windbg.

No OnCreate Manipulador do seu formulário Coloque o seguinte:

var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  MessageDlg(Format('MonitorPtr: %p', [pointer(MonitorPtr)]), mtInformation,
    [mbOK], 0);
  DebugBreak;
  // ...

Agora carregue o Windbg e abra e execute o processo que chama sua DLL. Quando o formulário é criado, uma caixa de mensagem mostrará o endereço da instância do monitor. Anote o endereço e clique em OK. O depurador surgirá e você definirá um ponto de interrupção no acesso de gravação a esse ponteiro, como assim:

BA W4 A32D00

substituindo A32D00 com o endereço correto da caixa de mensagem. Continue a execução e o depurador deve atingir o ponto de interrupção quando o monitor for atribuído. Usando as várias visualizações do depurador (módulos, threads, Stack), você pode obter informações importantes sobre o código que grava nesse endereço.

Outras dicas

Uma operação de ponteiro inválido significa que seu programa tentou libertar um ponteiro, mas havia uma das três coisas erradas com ele:

  • Foi alocado por outro gerente de memória.
  • Já havia sido libertado uma vez antes.
  • Nunca foi alocado por nada.

É improvável que você tenha vários gerentes de memória alocando TMonitor Registros, então acho que podemos descartar a primeira possibilidade.

Quanto à segunda possibilidade, se houver uma aula em seu programa que não tenha um destruidor personalizado ou que não libere nenhuma memória em seu destruidor, então a primeira reação real de memória para esse objeto pode estar em tabago, onde ele libera o monitor do objeto. Se você tiver uma instância dessa classe e tentar libertá -la duas vezes, esse problema pode aparecer na forma de uma exceção no Tonitor. Procure erros duplos livres em seu programa. o Opções de depuração no Fastmm pode ajudá -lo com isso. Além disso, quando você tiver essa exceção, use o Ligue para a pilha Para descobrir como você chegou ao destruidor de Tonitor.

Se a terceira possibilidade for a causa, você terá corrupção de memória. Se você tiver código que faça suposições sobre o tamanho de um objeto, essa pode ser a causa. O TOBJECT é quatro bytes maiores a partir de Delphi 2009. Sempre use o InstanceSize método para obter o tamanho de um objeto; Não apenas adicione o tamanho de todos os seus campos ou use um número mágico.

Você diz que os tópicos são criados pelo Outlook. Você definiu o IsMultithread variável global? Seu programa normalmente o define como verdadeiro quando cria um tópico, mas se você não é quem cria threads, ele permanecerá em seu valor falso padrão, o que afeta se o gerente de memória se incomoda em proteger suas estruturas de dados globais durante a alocação e desalocação . Defina -o como true no bloco de programa principal do seu arquivo DPR.

Depois de muita escavação, acontece que eu estava fazendo um bom (leia -se: horrível, Mas tem feito seu trabalho corretamente em nossos aplicativos Delphi 5 há muito tempo)

PClass(TForm)^ := TMyOwnClass 

Em algum lugar no fundo das entranhas da nossa estrutura de aplicativos. Aparentemente, o Delphi 2010 tem alguma inicialização de classe para inicializar o "campo do monitor" que agora não aconteceu, fazendo com que o RTL tente e "libertar o syncobject" após a destruição do formulário, porque o GetFieldAddress retornou um valor não-NIL. Eca.

A razão Por quê Estávamos fazendo esse hack em primeiro lugar, porque eu queria alterar automaticamente os CreateParams em todas as instâncias de formulário, para obter um formulário redimensível sem ícone. Vou abrir uma nova pergunta sobre como fazer isso sem hacks de quebra de RTL (e por enquanto simplesmente adicionará um bom ícone brilhante aos formulários).

Marcarei a sugestão de Mghie como a resposta, porque ela me forneceu (e qualquer pessoa que leia este tópico) com uma quantidade muito grande de informações. Obrigado a todos por contribuir!

Existem dois Tonitor em Delphi:

  1. System.tmonitor; que é um registro e é usado para sincronização de threads.
  2. Forms.tmonitor; que é uma classe que representa um monitor anexado (dispositivo de exibição).

System.tmonitor é adicionado a Delphi desde Delphi 2009; Portanto, se você estiver portando um código do Delphi 5, o que seu código estava usando era o formulário.tmonitor, não system.tmonitor.

Eu acho que o nome da classe é referenciado sem o nome da unidade no seu código, e isso está fazendo a confusão.

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