BlackTDN Search

quinta-feira, 20 de janeiro de 2011

Protheus :: Recuperando Variáveis da Pilha de Chamadas

Dia desses um conhecido disse-me: "иαldσ, preciso implementar o Ponto de Entrada 'MT360GRV' que será executado logo após a gravação da Condição de Pagamento, Programa 'MATA360'. Só que preciso saber o conteúdo do parâmetro 'nOpcao' que é de escopo 'Local' na função de gravação A360Grava e perguntou-me: Existe alguma forma de eu obter o conteúdo desse parâmetro pra uso na Implementação do Ponto de Entrada?"

Minha resposta costumeira, foi: Hummmm, deixe-me pensar...... e disse-lhe: Existem duas formas de se obter o conteúdo do parâmetro: 

A primeira é entrando em contato com a Totvs e solicitando a melhoria na chamada do Ponto de Entrada, passando como parâmetro o valor de 'nOpcao' de forma a permitir recuperá-lo através de 'ParamIxb' e, a segunda, é tentar obter o conteúdo do parâmetro 'nOpcao'  da 'Pilha de Chamadas'. Como desconheço a existência de alguma função que retorne a 'Pilha de Chamadas', recomendo que utilize a rotina de tratamento de erros pra esse fim. Ou seja: "Force um erro no sistema e, no 'exception', obtenha da propriedade ErrorEnv, do objeto de erro, o conteúdo do parâmetro 'nOpcao'. Obviamente que essa segunda solução é  'paliativa' até que a primeira seja, de fato, implementada. 

E, considerando que não dou 'Ponto sem Nó', implementei as funções abaixo para, se alguém mais passar por uma situação parecida como essa tenha como contornar. A solução é geral mas, infelizmente, incompleta. ErrorEnv é uma string e informações de 'Arrays', 'Blocos de Códigos' e  'Objetos' não estarão disponíveis. Perfeita seria se o objeto de erro contivesse um array com a pilha de chamadas, variáveis, seus tipos e seus valores. Mas, para o caso em questão, que é recuperar um valor numérico, a solução vai atender em 100%.

Vamos ao que interessa, ao código:


#INCLUDE "TRYEXCEPTION.CH"

#DEFINE STACK_INDEX_PARAMETER 1
#DEFINE STACK_INDEX_SCOPE  2
#DEFINE STACK_INDEX_TYPE  3
#DEFINE STACK_INDEX_VALUE  4

#DEFINE STACK_INDEX_ELEMENTS 4 

/*/
 Funcao:  ReadStackParameters
 Autor:  Marinaldo de Jesus
 Data:  19/01/2011
 Uso:  Retornar informacoes de Variaveis da Pilha de Chamadas
 Sintaxe: StaticCall(U_STACKPUSH,ReadStackParameters,cStack,cParameter,cScope,cModule)
/*/
Static Function ReadStackParameters( cStack , cParameter , cScope , cModule )

 Local aStackParameters

 Local bAscan

 Local lScope
 Local lModule

 Local nStack
 Local nParameter
 
 Local uValue

 BEGIN SEQUENCE

  aStackParameters := GetStackParameters()
  
  IF Empty( aStackParameters )
   BREAK
  EndIF
  
  lModule  := !Empty( cModule )

  IF ( lModule )
   nStack := aScan( aStackParameters , { |x| ( x[ 1 ] == cStack ) .and. ( cModule $ x[ 2 ] ) } )
  Else
   nStack := aScan( aStackParameters , { |x| x[ 1 ] == cStack } )
  EndIF 
  
  IF ( nStack == 0 )
   BREAK
  EndIF

  lScope  := !Empty( cScope )

  IF ( lScope )
   nParameter := aScan( aStackParameters[ nStack ][ 3 ] , { |x| ( x[ STACK_INDEX_PARAMETER ] == cParameter ) .and. ( x[ STACK_INDEX_SCOPE ] == cScope ) } )
  Else
   nParameter := aScan( aStackParameters[ nStack ][ 3 ] , { |x| ( x[ STACK_INDEX_PARAMETER ] == cParameter ) } )
  EndIF

  IF ( nParameter == 0 )
   BREAK
  EndIF

  uValue := aStackParameters[ nStack ][ 3 ][ nParameter ][ STACK_INDEX_VALUE ]

 END SEQUENCE

Return( uValue )

/*/
 Funcao:  GetStackParameters
 Autor:  Marinaldo de Jesus
 Data:  19/01/2011
 Uso:  Obtem Array com a Pilha de Chamadas que sera usado pela ReadStackParameters
 Sintaxe: StaticCall(U_STACKPUSH,GetStackParameters)
/*/
Static Function GetStackParameters()

 Local aStackEnv
 Local aStackParameters := {}
 
 Local cStack
 Local cModule
 Local cStackEnv
 
 Local nStack
 Local nIndexEnv
 Local nStackEnv

 Local oException
 
 TRYEXCEPTION

  UserException( "IGetStackParameters" )

 CATCHEXCEPTION USING oException

     cStackEnv := oException:ErrorEnv
     cStackEnv := StrTran( cStackEnv , "  " , CRLF )
     cStackEnv := StrTran( cStackEnv , "STACK " , CRLF + "STACK " )
     aStackEnv := StrTokArr( cStackEnv , CRLF )

     cStackEnv := NIL

     nIndexEnv := 0
     nStackEnv := Len( aStackEnv )

     While ( ( ++nIndexEnv ) <= nStackEnv )

      IF ( "Public" $ aStackEnv[ nIndexEnv ] )

       IF ( "Publicas" $ aStackEnv[ nIndexEnv ] )
        Loop
       EndIF

       nStack := aScan( aStackParameters , { |x| ( x[1] == "PUBLIC" ) } ) 
      
       IF ( nStack == 0 )
        aAdd( aStackParameters , { "PUBLIC" , "" , Array(0) } )
        nStack := Len( aStackParameters )
       EndIF

       cStackEnv := aStackEnv[ nIndexEnv ] 
       AddStackParameters( @aStackParameters , @nStack ,  @cStackEnv  )

            ElseIF ( "STACK" == SubStr( aStackEnv[ nIndexEnv ] , 1 , 5 ) )
             
             cStackEnv := AllTrim( StrTran( aStackEnv[ nIndexEnv ] , "STACK" , "" ) )
             cStack  := SubStr( cStackEnv , 1 , AT( "(" , cStackEnv ) - 1 )
             cModule  := StrTran( cStackEnv , cStack , "" )

       nStack   := aScan( aStackParameters , { |x| ( x[1] == cStack ) } ) 

       IF ( nStack == 0 )
        aAdd( aStackParameters , { cStack , cModule , Array(0) } )
        nStack := Len( aStackParameters )
       EndIF

             While (;
                ( ( ++nIndexEnv ) <= nStackEnv );
                .and.;
                !( "STACK" == SubStr( aStackEnv[ nIndexEnv ] , 1 , 5 ) );
                .and.;
                !( "FILES" == Upper( SubStr( aStackEnv[ nIndexEnv ] , 1 , 5 ) ) );
               ) 

        cStackEnv := aStackEnv[ nIndexEnv ] 
        AddStackParameters( @aStackParameters , @nStack ,  @cStackEnv  )

             End While

             --nIndexEnv

   ElseIF ( "FILES" == Upper( SubStr( aStackEnv[ nIndexEnv ] , 1 , 5 ) ) )
   
    Exit

            EndIF
       
     End While

 ENDEXCEPTION

Return( aStackParameters )

Static Function AddStackParameters( aStackParameters , nStack ,  cStackEnv  )

 Local aToken  := StrTokArr( cStackEnv , ":" )
 
 Local cType
 Local cScope
 Local cParameter
 
    Local nToken  := Len( aToken )
    Local nParameter

 Local uValue
 
 IF ( nToken >= 1 )
  cScope := Upper( AllTrim( StrTokArr( aToken[ 1 ] , " " )[1] ) )
 Else
  cScope := "UNDEFINED"
 EndIF 

 IF ( nToken >= 2 )
  cStackEnv := aToken[ 2 ]
     cParameter := AllTrim( SubStr( cStackEnv , 1 , AT( "(" , cStackEnv ) - 1 ) )
     cType  := SubStr( cStackEnv , AT( "(" , cStackEnv ) + 1 , 1 )
    Else
     cParameter := "NULL"
     cType  := "U"
    EndIF 

 IF ( nToken >= 3 )
  uValue  := aToken[ 3 ]
 Else
  uValue  := NIL
 EndIF 

 TRYEXCEPTION

  Do Case
   Case ( cType == "N"  )
    uValue := Val( uValue )
   Case ( cType == "D"  )
    uValue := Ctod( uValue )
   Case ( cType == "L"  )
    uValue := &( uValue )
   Case ( cType == "B"  )
    uValue := &( uValue )
   Case ( cType == "A"  )
    uValue := {}
   Case ( cType $ "U/O" )
    uValue := NIL
  End Case 
  
 CATCHEXCEPTION

  uValue := NIL
 
 ENDEXCEPTION     

 aAdd( aStackParameters[ nStack ][3] , Array( STACK_INDEX_ELEMENTS ) )
 
 nParameter := Len( aStackParameters[ nStack ][3] )

 aStackParameters[ nStack ][3][ nParameter ][ STACK_INDEX_PARAMETER ] := cParameter
 aStackParameters[ nStack ][3][ nParameter ][ STACK_INDEX_SCOPE  ] := cScope
 aStackParameters[ nStack ][3][ nParameter ][ STACK_INDEX_TYPE  ] := cType
 aStackParameters[ nStack ][3][ nParameter ][ STACK_INDEX_VALUE  ] := uValue

Return( NIL )    
       


Com as funções acima, conseguiremos recuperar o conteúdo de uma variável da 'Pilha de Chamadas' através do 'Tratamento de Erros'.

Um exemplo de uso seria:


/*/
 Funcao:  U_MT360GRV()
 Autor:  Marinaldo de Jesus
 Data:  20/01/2011
 Uso:  Implementacao do Ponto de Entrada MT360GRV que sera executado apos a Gravacao das Condicoes de pagamento
    Demonstra o Uso das funcoes pra recuperar conteudo de variaveis da Pilha de Chamadas
/*/
User Function MT360GRV()

 /*/
  Chamo Static Function ReadStackParameters que se encontra compilada no 'Modulo/Programa' U_STACKPUSH para obter o
  conteudo do Parametro nOpcao. ReadStackParameters espera que todos os parametros sejam passados em   "Upper Case"
  em funcao da forma de armazenamento. Os parametros obrigatorios sao o 'Funcao' a partir de  onde deseja-se  obter 
  o conteudo do Parametro e o parametro em si. Os parametros cScope e cModule sao opcionais, serao usados para  uma
  busca mais refinada. Ira retornar o conteudo do parametro se seu tipo for Numerico, Caractere, Logico, Data  e/ou
  Bloco de Codigo, lembrando que esse ultimo nao podera ser avalidado pois seu 'ponteiro' nao se encontra na declara
  cao original.
 /*/
 
 Local nOpcao := StaticCall(U_STACKPUSH,ReadStackParameters,"A360GRAVA","NOPCAO")
 
 IF ( nOpcao == 3 )
  //Implementacao especifica para o Ponto de Entrada
 EndIF

Return( NIL )
       
Que poderá ser baixado ao clicar aqui.

Se alguém souber uma forma de obter a "pilha de chamadas e parâmetros" sem ser via tratamento de erro, ficaria imensamente feliz em aprender.

E, como todo conhecimento não compartilhado torna-se nulo, sempre que aprender algo novo compartilhe para que seja perpetuado.

Esse é o meu lema.

[]s
иαldσ dj

9 comentários:

  1. Como sempre, o Naldo "mata o problema e mostra o código!". Esta solução (mesmo que temporária) atendeu perfeitamente no meu caso.

    ResponderExcluir
  2. Naldo,
    Fantastico...
    Dificil acreditar que era possível uma solução assim....

    ResponderExcluir
  3. Ferne$ deixou um novo comentário sobre a sua postagem "Protheus :: Recuperando Variáveis da Pilha de Cham...":

    Que show Naldo, a algum tempo atras precisei de algo parecido no template ACD, precisava obter um valor da tela em um ponto de entrada, abri um chamado para Totvs solicitando que a variavel fosse enviada via ParamIXB e minha solucao paleativa foi usar a funcao (VTSave) responsavel por montar um Array com as informacoes da tela.
    Muito util sua solucao

    ResponderExcluir
  4. Cara,
    já fiz cada gambi pra conseguir recuperar valores de escopo LOCAL em um determinado PE.

    Sempre pensei em uma forma de recuperar esses valores, apesar de ser um dado volatil, sera que seria possivel obte-lo da memoria.

    ¬¬

    ResponderExcluir
  5. Deve ter uma forma mas eu a desconheço. As variáveis estão lá, na Pilha... só esperando serem encontradas. A unica forma que encontrei de recuperá-las foi através do Tratamento de Erros. Então se deseja obter uma variável de escopo Local do Tipo Numérica, Data, Caractere ou Lógica, poderá abrir mão do código que disponibilizei para "Download". Esse código serve para recuperár os valores de Static, Private e Public também. Para as variáveis de escopo Local e Static não obteremos Arrays, Blocos de Código e Objetos, Limitação essa que não existe para as de escopo Private e Public.


    []s
    иαldσ dj

    ResponderExcluir
  6. Tenho uns amigos malucos de ciência da computação,
    vou ver com eles esse sistema de endereçamento de memoria, se existe alguma assinatura do protheus que as identifique.
    Nem que tenhamos que buscar esses valores descendo ao nível mais baixo e criando uma DLL ou EXE para ser executado externamente ao Protheus.

    Vou questiona-los!
    Abs

    Amonimo!
    ¬¬

    ResponderExcluir
  7. Naldo,
    Muito boa sua solução porém, quando fui compilar ocorreu um erro de sintaxe na linha 30 do rdmake U_StackPush.prg, com a instrução DEFAULT aStackParameters := GetStackParameters().
    Não estaria faltando o include do PROTHEUS.CH ?

    ResponderExcluir
  8. Garoto experto... Falha minha não ter incluido a chamada ao #include "protheus.ch", vou corrigir no original e disponibilizar para "Download".

    Uma observação. NÃO É RDMAKE. RDMAKE era um "Pseudo Compilador" que convertia o código, escrito em CodBase, de forma que o "SIGA ADVANCED" pudesse interpretá-lo já em advpl, criamos verdadeiras "Functions" que são executadas exatamente igual às "Functions" criadas pela equipe de IP.

    []s
    иαldσ dj

    ResponderExcluir