Sobre ponteiros de métodos
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
Curtidas 0
Respostas
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
06/04/2005
obrigado massuda... era o que eu imaginei... pois é o mesmo que acontece com os eventos...
GOSTEI 0
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
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
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
06/04/2005
Vc pode tratar uma rotina ou método como ponteiro:
O problema que se vc não souber exatamente a assinatura(parâmetros e seus tipos) da rotina, não poderá chamar via 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
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
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
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
E para testar, chame-a da seguinte maneira:
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:
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
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
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
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
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
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
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
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:
no Executável:
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.
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
06/04/2005
thiago_vidal,
você pode declarar Metodo como ´function Abrir(cnn: PSQLConnection): LongBool; stdcall;´
e chamar com ´Metodo(cnn);´
você pode declarar Metodo como ´function Abrir(cnn: PSQLConnection): LongBool; stdcall;´
e chamar com ´Metodo(cnn);´
GOSTEI 0
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 fazervar Aux: Boolean; begin Aux := True; try CodigoQueGeraExcecao; except Aux := False; end; CheckFalse(Aux,´Deveria ter retornado false´); end;
GOSTEI 0