Sobre ponteiros de métodos

Delphi

06/04/2005

pessoal como faço para criar um ponteiro de método (função) sem deixar definido seus parâmetros, ou seja posso passar qualquer parâmetro para esse ponteiro indiferente de seus métodos?


Bruno Belchior

Bruno Belchior

Curtidas 0

Respostas

Massuda

Massuda

06/04/2005

Não tem como fazer isso... Pascal (e consequentemente, Delphi) é uma linguagem fortemente tipada, de modo que os parametros que você passa para uma function/procedure tem do mesmo tipo dos argumentos que você declarou.


GOSTEI 0
Bruno Belchior

Bruno Belchior

06/04/2005

obrigado massuda... era o que eu imaginei... pois é o mesmo que acontece com os eventos...


GOSTEI 0
Michael

Michael

06/04/2005

Olá colega!

Uma solução seria vc criar um parâmetro do tipo array do tipo Variant. Veja:

function MinhaFuncao(Matriz : array of Variant) : boolean;
(...)

O problema das Variants é que elas consomem mais recursos do que se uma variável tipada.

[]´s


GOSTEI 0
Bruno Belchior

Bruno Belchior

06/04/2005

Olá colega! Uma solução seria vc criar um parâmetro do tipo array do tipo Variant. Veja: function MinhaFuncao(Matriz : array of Variant) : boolean; (...) O problema das Variants é que elas consomem mais recursos do que se uma variável tipada. []´s
obrigado colega... porém o problema vai um pouquinho além disso, não quero utilizar um parâmetro que não tem um tipo (no caso array of variants) mas sim um [b:064db646bd]ponteiro[/b:064db646bd] para um método, assim como um evento faz internamente, porém isso só é possível quando temos um tipo (classe) de ponteiro de método previamente implementado, na realidade a intenção era criar uma classe de ponteiro que suportasse receber qualquer tipo de método, independente da quantidade de parâmetros...


GOSTEI 0
Beppe

Beppe

06/04/2005

Vc pode tratar uma rotina ou método como ponteiro:
procedure Oi(A, B: Integer);
procedure Ola(A, B: String);
...
P: Pointer;
...
P := @Oi;
...
P := @Ola;

O problema que se vc não souber exatamente a assinatura(parâmetros e seus tipos) da rotina, não poderá chamar via ponteiro.


GOSTEI 0
Yallebr

Yallebr

06/04/2005

O problema que se vc não souber exatamente a assinatura(parâmetros e seus tipos) da rotina, não poderá chamar via ponteiro.
Correto mas como eu iria [b:541be4a84b]chamá-la em outra classe[/b:541be4a84b] por exemplo?
Ou seja o método que passarei como ponteiro esta em uma classe, porém será outra classe quem vai executá-lo...


GOSTEI 0
Beppe

Beppe

06/04/2005

mas como eu iria [b:8ce7017788]chamá-la em outra classe[/b:8ce7017788] por exemplo?

Isto é simples, dado que um método é apenas uma rotina com um parâmetro implícito. Por exemplo, os dois métodos são exatamente iguais quanto à síntese:
procedure Button1Click(Self: TForm1; Sender: TObject);
begin
  ShowClass(Self);
  ShowClass(Sender);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowClass(Self);
  ShowClass(Sender);
end;

Mas para chamar um método vc precisa mais do que apenas um ponteiro, precisa também do objeto sobre o qual o método agirá. Para isso, use TMethod.
var
  M: TMethod;
begin
  M.Code := @TForm1.Button1Click;
  M.Data := Form1;
  TNotifyEvent(M)(Sender);
end;

Ou ainda
type
  T2P = procedure(Self: TForm1; Sender: TObject);
var
  P: Pointer;
begin
  P := @TForm1.Button1Click;
  T2P(P)(Form1, Sender);
end;

As duas formas se equivalem, a diferença está no trato de Self(como método ou simples subrotina).


GOSTEI 0
Nildo

Nildo

06/04/2005

Olha... você pode criar uma função sem parametros, e dentro dela você pode ler quantos itens você quiser da Stack, que serão PUSHados antes de você chamar a função. Mas deve ter um auxilio do Assembly InLine. Aqui vai um exemplo onde eu passo 2 parâmetros para uma função declarada sem parametros, e os leio dentro dela

function SuaFuncao: Integer; stdcall;
var
   _ESP: Pointer;

   Param1: Integer;
   Param2: Integer;
begin
   asm
      mov _ESP, ESP
   end;

   Param1 := PInteger( Integer( _ESP ) + ( 12 * 4 ) )^;
   Param2 := PInteger( Integer( _ESP ) + ( 11 * 4 ) )^;

   ShowMessage( IntToStr( Param1 ) );
   ShowMessage( IntToStr( Param2 ) );
end;


E para testar, chame-a da seguinte maneira:

procedure TForm1.Button1Click(Sender: TObject);
begin
   asm
      push eax

      push 1
      push 2
      call SuaFuncao
      pop eax
      pop eax

      pop eax
   end;

end;


Partindo deste princípio você pode criar um parametro que seria a contagem dos parametros, daí você faz um FOR e sai lendo os itens da Stack. É complicado isso, nem é muito legal usar, mas foi só para mostrar que é possível :wink:


GOSTEI 0
Beppe

Beppe

06/04/2005

nildo, uma correção quanto aos offsets: os argumentos começam a partir de 3 * 4. Isto se deve as 3 locais da função. Mas desta forma vc deve se privar de usar tratamento de exceções e strings(try/finally implícito envolvendo seu código). E possíveis variáveis temporárias que o Delphi criar para a função, complica enormemente, na minha opinião.


GOSTEI 0
Massuda

Massuda

06/04/2005

...É complicado isso, nem é muito legal usar, mas foi só para mostrar que é possível
Acho que esse é o ponto mais importante deste tópico... só quem já teve que procurar a causa de um stack corrompido sabe o pesadelo que são esses truques relacionados com manipulação de stack e conversão de ponteiros de método para ponteiros de procedure/função.

Se for para programar dessa forma, estaríamos retrocedendo para os tempos onde todas as variáveis tinham que ser globais e subrotinas não podiam receber parametros (algo como retornar aos anos 60?).


GOSTEI 0
Nildo

Nildo

06/04/2005

nildo, uma correção quanto aos offsets: os argumentos começam a partir de 3 * 4. Isto se deve as 3 locais da função. Mas desta forma vc deve se privar de usar tratamento de exceções e strings(try/finally implícito envolvendo seu código). E possíveis variáveis temporárias que o Delphi criar para a função, complica enormemente, na minha opinião.


Por isso eu disse que nem vale a pena. Então, eu também achei que seria 3*4, mas quando eu testei deu erro, dai eu debugei pela CPU e ví que na Stack, o Delphi acrescentou uns 8 itens.. Dai pra funcionar (é sempre especifico de função para função) eu peguei o 11º e 12º item da Stack. Pode testar e você vai ver que funciona.

mas eu estranhei o Delphi ter PUSHado um monte de coisa numa função simples... mas fazer o que né! rs


GOSTEI 0
Beppe

Beppe

06/04/2005

Me parecou apenas curiosidade do brunovicenteb. Se alguém tiver intenção de fazer isto na prática, acho que dá pra chegar numa solução eficiente, dada algumas restrições. A princípio, a solução do NerdeX(ou ainda usar array of const) é a mais geral, e ao menos eu nunca tive o que reclamar dela.


GOSTEI 0
Nildo

Nildo

06/04/2005

só quem já teve que procurar a causa de um stack corrompido sabe o pesadelo que são esses truques relacionados com manipulação de stack e conversão de ponteiros de método para ponteiros de procedure/função.


É realmente um pesadelo, principalmente quando você tem que aplicar um patch na Stack por conta de um erro estranho na compilação. Mas enfim, depois que você começa a estudar a estrutura do processador você tira isso de letra

Se for para programar dessa forma, estaríamos retrocedendo para os tempos onde todas as variáveis tinham que ser globais e subrotinas não podiam receber parametros (algo como retornar aos anos 60?).


Isso sim eu chamo de [b:022f8d5df1]Programar[/b:022f8d5df1]! Eu gosto disso porque acabo aprendendo a base da coisa.. Como ela funciona internamente.. O que o processador faz quando executa nossos códigos, etc..


GOSTEI 0
Beppe

Beppe

06/04/2005

Por isso eu disse que nem vale a pena. Então, eu também achei que seria 3*4, mas quando eu testei deu erro, dai eu debugei pela CPU e ví que na Stack, o Delphi acrescentou uns 8 itens.. Dai pra funcionar (é sempre especifico de função para função) eu peguei o 11º e 12º item da Stack. Pode testar e você vai ver que funciona.

Nem precisa testar...estou vendo agora o IntToStr, que cria strings. Se não fossem por eles, neste caso, os offsets seriam os óbvios.


GOSTEI 0
Thiago Vidal

Thiago Vidal

06/04/2005

Já utilizei muitas vezes este método pra chamada de DLLs, principalmente para fazer uma DLL compartilhar de uma TSQLConnection que está no executável, e sempre funcionou sem problemas...

O motivo de o Delphi PUSHar tantos itens no stack, é pq o Delphi passa os parâmetros padrão ´register´ via registradores; para passar via stack, vc deve declarar o método como ´stdcall´. Como por Exemplo:

na Dll:
type
  PSQLConnection = ^TSQLConnection;

function Abrir(cnn: PSQLConnection): LongBool; stdcall;
begin
  with TForm1.Create(Application) do
  try
    SQLDataSet1.Connection := cnn;
    Result := (ShowModal = mrOk);
  finally
    Free;
  end;
end;


no Executável:
procedure TForm1.Button1Click(Sender: TObject);
type
  PSQLConnection = ^TSQLConnection;
var
  DLLHandle: THandle;
  cnn: PSQLConnection;
  Metodo: Pointer;
  Resultado: LongBool;
begin
  DLLHandle := LoadLibrary(´minhadll.dll´);
  if (DLLHandle <> 0) then
  try
    cnn := @SQLConnection1;
    Metodo := GetProcAddress(DLLHandle, ´Abrir´);
    asm
      push cnn
      call Metodo
      mov [Resultado], eax
    end;

    if (Resultado) then
      ShowMessage(´Sucesso!´);
  finally
    FreeLibrary(DLLHandle);
  end;
end;


Realmente não é a forma mais recomendada de se fazer as coisas, mas funciona, e para um sistema como o meu, em que um executável enxuto carrega diversas DLLs, é importante manter apenas 1 conexão com o banco ativa... desde que se faça um rigoroso controle, para evitar que as DLLs tentem acessar a conexão simultaneamente.


GOSTEI 0
Beppe

Beppe

06/04/2005

thiago_vidal,
você pode declarar Metodo como ´function Abrir(cnn: PSQLConnection): LongBool; stdcall;´

e chamar com ´Metodo(cnn);´


GOSTEI 0
Bruno Belchior

Bruno Belchior

06/04/2005

Me parecou apenas curiosidade do brunovicenteb. Se alguém tiver intenção de fazer isto na prática, acho que dá pra chegar numa solução eficiente, dada algumas restrições. A princípio, a solução do NerdeX(ou ainda usar array of const) é a mais geral, e ao menos eu nunca tive o que reclamar dela.
é acho que não vou utilizar isso não, na verdade o que eu queria é o seguinte, nos meus testes de classa com DUnit qdo tenho que verificar testes que esperam uma exceção tenho que fazer
var
  Aux: Boolean;
begin
  Aux := True;
  try
    CodigoQueGeraExcecao;
  except
    Aux := False;
  end;
  CheckFalse(Aux,´Deveria ter retornado false´);
end;
a idéia era no método [b:cdca8f2682]CodigoQueGeraExcecao[/b:cdca8f2682] colocar um ponteiro para métodos poque ai poderia implementar uma função que execute esse código, mas como vi não é tão simples assim passar o [b:cdca8f2682]CodigoQueGeraExcecao[/b:cdca8f2682] de modo genérico


GOSTEI 0
POSTAR