Resta1 em AdvPL

Introdução

Seguindo a linha de jogos em AdvPL, hoje o post traz para vocês a contribuição do desenvolvedor e nobre colega Flávio Luiz Vicco, que fez um porte do jogo “Resta 1” para o AdvPL, usando orientação a objeto em AdvPL.

O Jogo

Resta 1 é um quebra-cabeças, cujo objetivo é retirar todas as peças do tabuleiro, até que reste somente uma. No início do jogo, há 32 peças no tabuleiro, deixando vazia a posição central. Um movimento consiste em pegar uma peça e fazê-la “saltar” sobre outra peça, sempre na horizontal ou na vertical, terminando em um espaço vazio. A peça que foi “saltada” é retirada do tabuleiro. O jogo termina quando não mais é possível fazer nenhum outro movimento. Nesta ocasião, o jogador ganha se restar apenas uma peça no tabuleiro.

resta1

Com apenas 250 linhas a classe com o “core” do jogo foi implementada, e usando menos de 50 linhas, foi implementada a função executora do Jogo. Internamente, o jogo usa a classe tPaintPanel, e a classe executora monta o jogo usando uma caixa de diálogo e um menu superior. Os objetos correspondentes às peças do quebra-cabeça são criados nas respectivas posições, disparando um bloco de código a cada clique do mouse em cada posição. Ao clicar em uma posição ocupada por uma pedra, a respectiva posição é selecionada. Ao clicar em uma nova pedra, esta passa ser a pedra selecionada. Uma vez havendo a seleção de uma pedra, caso você clique em uma posição livre localizada a 2 pedras de distância da pedra selecionada, e entre elas existe uma pedra, a pedra selecionada é movida para a nova posição selecionada, e a pedra entre elas é removida.

Compilando e Executando

Para compilar e executar o Resta Um, basta baixar os fontes e imagens do GitHub, no endereço “https://github.com/siga0984/Resta1-OO“, criar um projeto no IDE/TDS, acrescentar os dois fontes PRW no projeto, acrescentar todas as imagens no projeto como recursos do Projeto, compilar tudo, e chamar a função “U_RESTA1” através do SmartClient.

O Código

Um jogo de tabuleiro, sem adversário ou contagem de tempo, deve ter uma representação em memória do estado do tabuleiro, uma interface que represente graficamente esta representação, e permita ao jogador imputar um movimento, e ser capaz de criticar um movimento inválido, e aplicar um movimento válido na representação do tabuleiro em memória.

Para desempenhar estas tarefas, o jogo foi implementado como uma classe em AdvPL, chamada “TResta1”, onde o construtor “New()” recebe como parâmetro o “container” de interface do Jogo — no nosso caso o objeto da janela de diálogo — e inicia a execução do jogo através do método Activete() da classe “TResta1”.

Na classe TRESTA1, a propriedade “aShape” contem todas as peças do quebra-cabeças. Como o tabuleiro é basicamente a união de um array 3×7 e outro 7×3, compartilhando a área central ( 3×3 ), o array é criado baseado em um loop de 7×7 elementos, onde as posições onde não entraria nenhuma pedra nao são ocupadas não criam nenhum novo elemento. São 16 posicoes ignoradas, 4 de cada quadrante. Sabendo que uma matriz 7×7 tem 49 elementos, e 16 serão ignorados, serão criadas 33 posições no tabuleiro, onde para cada uma será atribuído um status ( vazio / ocupado / selecionado ) e uma imagem correspondente ao estado.

Partindo de um loop 7×7, numerando cada elemento sequencialmente, eu teria as seguintes posições numeradas:

01 02 03 04 05 06 07
08 09 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 32 33 34 35
36 37 38 39 40 41 42
43 44 45 46 47 48 49

Somente serão criados elementos para as posições onde podem haver pedras no quebra-cabeça. Logo, as 4 pedras de cada extremidade deste quadrado não serão usadas — repare que as posições a serem usadas estão em negrito. Cada pedra é criada como um “Shape” dentro do tPaintPanel, e cada uma delas recebe um identificador único, sendo criadas 33 pedras ( ou shapes ), no loop abaixo — vide método Activate() da classe tResta1.

For nX := 1 To 7
 For nY := 1 To 7
  nZ ++
  If !StrZero(nZ,2) $ "01|02|06|07|08|09|13|14|36|37|41|42|43|44|48|49"
   ::Create( ::oTPanel, nX, nY, "P"+StrZero(nZ,2), IIf(nZ==25,0,1) )
  EndIf
 Next nY
Next nX

O evento interessante tratado no jogo é o Click() … A classe tPaintPanel() tem uma propriedade que permite informar um bloco de código, que será disparado quando o usuário da aplicação clicar com o mouse dentro da área do tPaintPanel. Ao ver o fonte, reparamos que o evento em si não recebe nenhum parâmetro, mas isso não impede do método ‘descobrir’ se o evento de clique do mouse foi feito em cima de um determinado Shape. Ao ser criado, cada shape possui um “ID”, informado pela aplicação como um valor numérico. Quando um evento de clique do mouse é realizado sobre um shape, a propriedade ShapeAtu da instância do tPaintPanel é atualizada. Logo, o método somente precisa procurar no array de Shapes qual o shape que foi clicado. Se não foi clicado em nenhum shape, o evento é ignorado.

//-- Identifica obj. shape clicado.
nDestino := aScan(::aShape,{ |x|(x[1] == ::oTPanel:ShapeAtu)})

Ainda dentro do método Click(), existe todo o tratamento para avaliar como o clique deve ser tratado. Para realizar um “pulo”, você deve selecionar uma pedra, criando sobre ela, e depois clicando em um espaço em branco, indicando que você quer movê-la para aquela posição. Seu movimento somente será realizado se, a posição escolhida tiver a distancia de 2 casas, na horizontal ou vertical — diagonal não pode — , e entre a posição selecionada e a posição escolhida, exista uma pedra — que será removida.

E, da forma que o jogo foi idealizado, existe um programa “base”, ou “executor”, que é responsável por prover a interface externa do jogo — A janela de diálogo onde o jogo será montado e o menu de opções — Iniciar Novo Jogo, Ajuda, Sair, barra de status inferior, etc. Reparem que apenas uma instância do jogo é criada e armazenada na variável oResta1, e a partir deste ponto todas as ações externas que podem ser realizadas com o Jogo ( iniciar, reiniciar, mostrar ajuda) são feitas a partir dos métodos publicados.

#INCLUDE "PROTHEUS.CH"

/* -----------------------------------------------------
Fonte RESTA1.PRW
Programa Resta 1 - executor
Autor Flavio Luiz Vicco
Data 11/2016
----------------------------------------------------- */

User Function Resta1()
 Local oDlg
 Local oResta1
 DEFINE DIALOG oDlg TITLE "Resta1" From 180,180 TO 550,700 PIXEL COLOR CLR_BLACK,CLR_WHITE
 oDlg:lEscClose := .F.
 //-- Cria Resta1
 oResta1:= TResta1():New(oDlg)
 //-- Cria Menu superior
 CreateMenuBar(oDlg,oResta1)
 //-- Cria Barra de Status inferior
 CreateMsgBar(oDlg)
 // Na ativação do dialogo, ativa o jogo 
 ACTIVATE DIALOG oDlg CENTERED ON INIT oResta1:Activate()
Return Nil

//-- Cria Menu superior
Static Function CreateMenuBar(oDlg,oResta1)
 oTMenuBar:= TMenuBar():New(oDlg)
 oTMenuBar:SetCss("QMenuBar{background-color:#eeeddd;}")
 oTMenuBar:Align := CONTROL_ALIGN_TOP
 oTMenuBar:nClrPane := RGB(238,237,221)
 oTMenuBar:bRClicked := {||}
 oFile:= TMenu():New(0,0,0,0,.T.,,oDlg)
 oHelp:= TMenu():New(0,0,0,0,.T.,,oDlg)
 oTMenuBar:AddItem("&Arquivo",oFile,.T.)
 oTMenuBar:AddItem("Aj&uda" ,oHelp,.T.)
 oFile:Add(TMenuItem():New(oDlg,"&Novo Jogo",,,,{|| oResta1:NewGame()},,"",,,,,,,.T.))
 oFile:Add(TMenuItem():New(oDlg,"Sai&r",,,,{|| If(MsgYesNo("Deseja realmente sair do jogo?"),oDlg:End(),)},,"FINAL",,,,,,,.T.))
 oHelp:Add(TMenuItem():New(oDlg,"&Sobre... F1",,,,{|| oResta1:Help() },,"RPMPERG",,,,,,,.T.))
Return

//-- Cria Barra de Status inferior
Static Function CreateMsgBar(oDlg)
 oTMsgBar := TMsgBar():New(oDlg, "Resta1",.F.,.F.,.F.,.F., RGB(116,116,116),,,.F.) 
 oTMsgItem1 := TMsgItem():New( oTMsgBar,"2014", 100,,,,.T., {||} ) 
 oTMsgItem2 := TMsgItem():New( oTMsgBar,"V.1.00", 100,,,,.T., {||} )
Return


//----------------------------------------------------------------------------

E, segue abaixo o fonte da classe do jogo .

#INCLUDE "PROTHEUS.CH"

/* -----------------------------------------------------
Fonte TRESTA1.PRW
Programa Resta 1 - objetos
Autor Flavio Luiz Vicco
Data 11/2016
----------------------------------------------------- */

Class TResta1

Data nId AS INTEGER
 Data nOrigem AS INTEGER // Numero da posicao selecionada
 Data aShape AS ARRAY INIT {}
 DATA oTPanel AS OBJECT // tPaintPanel

Method New(oDlg) CONSTRUCTOR
 Method Activate()
 Method NewGame()
 Method Create()
 Method Click( x, y, oTPanel )
 Method Change( oTPanel, nItem, nStatus )
 Method SetId()
 Method ExportImage()
 Method Help()
EndClass

Method New(oDlg) Class TResta1
 ::nId := 0 
 ::nOrigem := 0 
 ::aShape := {} 
 ::oTPanel:= TPaintPanel():new(0,0,0,0,oDlg,.f.)
 ::oTPanel:Align := CONTROL_ALIGN_ALLCLIENT
 ::oTPanel:bLClicked := {|x,y| ::Click()}
 ::ExportImage()
Return Self

Method Activate() Class TResta1
 Local nX := 0
 Local nY := 0
 Local nZ := 0
 Local cImg := "backg.png"
 Local cId := ""
 //-- Tamanho da tabuleiro
 cTabLarg := cValToChar(400)
 cTabAlt := cValToChar(450)
 //-- Ajusta tela conforme tabuleiro
 ::oTPanel:oWnd:nHeight := Val(cTabAlt)
 ::oTPanel:oWnd:nWidth := Val(cTabLarg)
 //-- Altura largura do tabuleiro
 cAltura := '0'
 cLargura := '0'
 //-- Cria Container
 ::oTPanel:addShape( "id="+::SetId()+";type=1;left=0;top=0;width="+cValToChar(Val(cTabLarg))+;
   ";height="+cValToChar(Val(cTabAlt))+";"+;
   "gradient=1,0,0,0,0,0.0,#FFFFFF;pen-width=0;pen-color=#FFFFFF"+;
   ";can-move=0;can-mark=0;is-container=1;")
 //-- Cria shape com imagem do tabuleiro
 cId := ::SetId()
 ::oTPanel:addShape( "id="+cId+";type=8;left="+cLargura+";top="+cAltura+";width="+cTabLarg+;
   ";height="+cTabAlt+";image-file="+::GetTempPath()+cImg+";tooltip=Resta1"+;
   ";can-move=0;can-deform=0;can-mark=0;is-container=1")
 For nX := 1 To 7
  For nY := 1 To 7
   nZ ++
   If !StrZero(nZ,2) $ "01|02|06|07|08|09|13|14|36|37|41|42|43|44|48|49"
    ::Create( ::oTPanel, nX, nY, "P"+StrZero(nZ,2), IIf(nZ==25,0,1) )
   EndIf
  Next nY
 Next nX
Return

Method NewGame() Class TResta1
 Local nX := 0
 Local nY := 0
 Local nZ := 0
 For nZ := 1 To Len(::aShape)
  nX := ::aShape[nZ,3]
  nY := ::aShape[nZ,4]
  If ::aShape[nZ,5] <> IIf(nX==4.And.nY==4,0,1)
   ::Change( ::oTPanel, nZ, IIf(nX==4.And.nY==4,0,1 ) )
  EndIf
 Next nZ
Return

Method Create( oPanel, nImgX, nImgY, cCodigo, nStatus, nShape, cImgId ) Class TResta1
 Local cWidth := "30"
 Local cHeight := "30"
 Local cImg := ""
 Local cToolTip := AllTrim(cCodigo)+" X= "+AllTrim(Str(nImgX))+" Y= "+AllTrim(Str(nImgY))
 Default nShape := 0
 Default cImgId := ::SetId()

 //-- Define imagem para cada status
 // 0 = Nao há pedra - vazio
 // 1 - Espaço ocupado com uma pedra
 // 2 - Pedra atualmente selecionada
 Do Case
 Case nStatus == 0
   cImg := "empty.png"
 Case nStatus == 1
   cImg := "full.png"
 Case nStatus == 2
   cImg := "select.png"
 EndCase
 //-- criacao do obj
 If nShape == 0
  aAdd(::aShape,Array(5))
  nShape := Len(::aShape)
 EndIf
 //-- config. do obj
 ::aShape[nShape,1] := Val(cImgId) //CODIGO DO SHAPE
 ::aShape[nShape,2] := cCodigo //CODIGO
 ::aShape[nShape,3] := nImgX //POSICAO X
 ::aShape[nShape,4] := nImgY //POSICAO Y
 ::aShape[nShape,5] := nStatus //STATUS

oPanel:addShape("id="+cImgId+";type=8;left="+Str(nImgY*45)+;
 ";top="+Str(nImgX*45)+";width="+cWidth+";height="+cHeight+;
 ";image-file="+::GetTempPath()+cImg+";tooltip="+cToolTip+;
 ";can-move=0;can-deform=1;can-mark=0;is-container=0")
Return

Method Click() Class TResta1
 Local nDestino := 0
 Local nSalto := 0
 Local nIdImg := 0
 Local nX := 0
 Local nY := 0
 Local nIdClk := 0
 Local nStatus := 0
 Local lOk := .F.

//-- Identifica obj. shape clicado.
 nDestino := aScan(::aShape,{ |x|(x[1] == ::oTPanel:ShapeAtu)})
 If nDestino > 0
  nStatus := ::aShape[nDestino,5]
  Do Case
  Case nStatus == 0
  If ::nOrigem > 0
  nX0 := ::aShape[::nOrigem ,3]
  nY0 := ::aShape[::nOrigem ,4]
  nX1 := ::aShape[ nDestino,3]
  nY1 := ::aShape[ nDestino,4]
  //-- Verifica se movimento horizontal valido...
  If (nX0 == nX1 .And. Abs(nDif := nY0 - nY1) == 2)
   If nDif == 2
    nDif := -1
   Else
    nDif := 1
   EndIf
   lOk := (nSalto:=aScan(::aShape,{|x| x[3]==nX0 .And. x[4]==nY0+nDif .And. x[5]==1})) > 0
  EndIf
  //-- Verifica se movimento vertical valido...
  If (nY0 == nY1 .And. Abs(nDif := nX0 - nX1) == 2)
   If nDif == 2
    nDif := -1
   Else
    nDif := 1
   EndIf
   lOk := (nSalto:=aScan(::aShape,{|x| x[3]==nX0+nDif .And. x[4]==nY0 .And. x[5]==1})) > 0
  EndIf
  If lOk
   nStatus := 1
   //-- Retira da posicao saltada
   ::Change( ::oTPanel, nSalto, 0 )
   //-- Retira da posicao anterior
   ::Change( ::oTPanel, ::nOrigem, 0 )
   ::nOrigem := 0
  EndIf
 EndIf
 Case nStatus == 1
 If ::nOrigem > 0
  //-- Retira da posicao anterior
  ::Change( ::oTPanel, ::nOrigem, 1 )
 EndIf
 nStatus := 2
 ::nOrigem:= nDestino
 lOk := .T.
 Case nStatus == 2
  nStatus := 1
  ::nOrigem:= 0
  lOk := .T.
 EndCase
 //-- Troca figura da posicao atual
 If lOk
  ::Change( ::oTPanel, nDestino, nStatus )
 EndIf
 EndIf
Return

//-- Realiza uma mudança de status de um elemento ( pedra ) do jogo 
Method Change( oTPanel, nItem, nStatus ) Class TResta1
 Local nIdImg := 0
 Local cCodigo := ""
 Local nX := 0
 Local nY := 0
 nIdImg := ::aShape[nItem,1]
 cCodigo := ::aShape[nItem,2]
 nX := ::aShape[nItem,3]
 nY := ::aShape[nItem,4]
 //-- Excluir shape com status anterior
 ::oTPanel:DeleteItem(nIdImg)
 //-- Recriar shape com status atual
 ::Create( ::oTPanel, nX, nY, cCodigo, nStatus, nItem, Str(nIdImg) )
Return

//-- CRia identificador sequencial para objetos
Method SetId() Class TResta1
Return cValToChar(++::nId)

//-- Exporta as imagens do RPO para o temporario %TEMP%
Method ExportImage() Class TResta1
 Local aImage := { "backg.png" , "empty.png" , "full.png" , "select.png" }
 Local nImage, cImageTo
 For nImage := 1 To Len(aImage)
  cImageTo := ::GetTempPath()+aImage[nImage]
  If !Resource2File(aImage[nImage],cImageTo)
   MsgStop("Image not found: " + aImage[nImage])
   QUIT
  EndIf
 Next nImage
Return

Method Help() Class TResta1
 MsgInfo( "Resta1 em ADVPL.","Bem Vindo!")
Return

Conclusão

Agradeço novamente a colaboração do Fávio Vicco, neste momento em que eu estou enfrentando um breve “bloqueio criativo” ..risos.. e desejo a todos TERABYTES DE SUCESSO !!!

Referências

https://github.com/siga0984/Resta1-OO
http://tdn.totvs.com/display/tec/TPaintPanel
https://pt.wikipedia.org/wiki/Resta_um

Anúncios

Aplicações Externas no AdvPL – Parte 02

Introdução

No tópico anterior, vimos um exemplo de chamada de aplicação externa ao AdvPL via Smartclient, usando uma DLL. As mesmas regras da DLL valem para criar uma biblioteca de objetos compartilhados (Shared Object), para ser usada com o SmartClient Linux. Neste tópico, antes de ver alguns recursos mais avançados de DLL, vamos ver alguns comandos para chamar diretamente uma aplicação externa executável através do SmartClient.

Para a execução de aplicativos na máquina onde está sendo executado o SmartClient, o AdvPL disponibiliza três funções: WinExec(), WaitRun() e ShellExecute(). Cada uma delas possui parâmetros, atributos e comportamentos distintos, aplicáveis a diversas situações. Os detalhes de cada função estão documentados na TDN, nos links disponibilizados nas referências deste post. Neste tópico, o foco vai ser na utilidade, pontos comuns e diferenças de cada uma.

Função WinExec

Dispara a execução de uma aplicação externa ao SmartClient, sem aguardar por retorno ou finalização. Pode ser chamado qualquer aplicação na máquina, sem informar o PATH completo do arquivo, desde que a aplicação esteja em algum diretório especificado na variável de ambiente PATH da máquina onde está sendo executado o SmartClient.

Função WaitRun

Permite a execução de uma aplicação externa ao SmartClient, aguardando pelo término da aplicação. Esta particularidade pode não ser respeitada quando o SmartClient está sendo executado em uma máquina com Windows 10, por exemplo, devido a mudanças entre as versões do sistema operacional. Da mesma forma que a WinExec, podemos ou não informar o PATH completo da aplicação a ser executada, sendo possível omitir o caminho completo da aplicação caso ela esteja na variável de ambiente PATH da máquina onde o SmartClient está sendo executado. Adicionalmente, na função WaitRun(), pode ser especificado um parâmetro para a execução de uma aplicação sem que seja aberta uma janela de interface, ou pode ser iniciada a aplicação em uma janela minimizada.

Função ShellExecute

De modo similar a WinExec, ela apenas dispara uma aplicação. Porém, ela permite vários parâmetros adicionais, e inclusive permite outras operações relacionadas a arquivos, URLs, documentos e afins, onde o sistema operacional pode utilizar diretamente a aplicação default para uma determinada ação. Por exemplo, podemos solicitar a abertura de uma URL de um WebSite, informando o endereço HTTP, e o sistema operacional vai utilizar para esta ação o Browser de Internet default configurado para abrir endereços HTTP. A função ShellExecute não espera pelo término da aplicação iniciada.

Exemplo 01

Um cliente possui um Web Site de venda de seus produtos, onde existe uma página de consulta por código, que pode receber como parâmetro o código do produto desejado pela URL. E, no seu cadastro de produtos no ERP, ele têm o código do produto disponibilizado no site. Ele gostaria de criar uma função em AdvPL, que recebesse um código WEB do Produto, e abrisse uma página do navegador, na estação do usuário do Smartclient, trazendo na tela a consulta do produto no site.

A alternativa mais elegante é usar a função ShellExecute(), montando a URL de acesso usando o código fornecido, e deixando o sistema operacional abrir a página do site no Navegador de Internet padrão da máquina do usuário. Veja o exemplo abaixo:

#include 'shell.ch'

User Function ConsWeb(cCodWeb)
Local cUrl := 'http://meusite.com.br/"
Local cPage := 'consprod.php?cod='+cCodWeb

ShellExecute('open',cUrl+cPage,"","",SW_NORMAL)

Return

Exemplo 02

Vamos partir do primeiro exemplo, porém eu quero que a URL em questão sempre seja aberta pelo Google Chrome, mesmo que ele não seja o navegador default. Para isso, basta endereçarmos a execução do “Chrome.EXE”, e passamos como parâmetro a URL a ser aberta. nem preciso dizer que este é um exemplo didático, e que o sucesso dessa abordagem exige que a máquina onde está sendo executado o SmartClient tenha o Google Chrome instalado.

#include 'shell.ch'

User Function ConsWeb2(cCodWeb)
Local cUrl := 'http://meusite.com.br/"
Local cPage := 'consprod.php?cod='+cCodWeb

ShellExecute('open','chrome.exe',cUrl+cPage,"",SW_NORMAL)

Return

Exemplo 03

Precisamos executar um comando qualquer do sistema operacional, ou um aplicativo de linha de comando, onde não há passagem de parâmetros por interface, apenas por linha de comando, e o resultado deste comando é uma saída de texto em tela. Por exemplo, um comando “dir”, ou um “ifconfig” — para obter detalhes das interfaces de rede. Normalmente, quando executamos um comando assim pelo prompt de comando do sistema operacional, podemos direcionar a saída de tela para um arquivo em disco, colocando no final do comando um sinal de maior “>” , seguido do nome de um arquivo, que será criado em disco na pasta atual.

Porém, este direcionamento em arquivo é tratado apenas pelo interpretador de comandos, isto é, não funciona se você tentar usar em um Winexec(), WaitRun() ou ShellExecute(). Mas não precisa entrar em pânico, existe uma alternativa: Basta montar uma chamada direta para o interpretador de comandos (CMD.EXE), passando o comando original para ser executado através do interpretador de comandos.

Por exemplo, queremos executar um “ifconfig” na máquina onde está o SmartClient, para obter detalhes das interfaces de rede disponíveis. E, como não há como recuperar a string retornada direto pela chamada, fazemos uma saída em arquivo.

ifconfig > netconfig.txt

Porém, para executar esta instrução, e ela realmente gerar o arquivo em disco com o resultado, encapsulamos a chamada com o CMD.EXE, ficando assim:

cmd /c "ifconfig > netconfig.txt"

Agora, vamos ao exemplo prático, para recuperar as informações de configuração de rede da máquina do SmartClient. Caso a função não consiga recuperar as informações por qualquer razão, ela retorna uma string em branco. Assumimos que o arquivo “netconfig.txt” será gerado na pasta de trabalho do executável do SmartClient, então usamos a função GETREMOTEININAME() para descobrir o path completo do SmartClient, para copiar o arquivo gerado e copiar para uma pasta no APPServer, ou mesmo ler o arquivo diretamente usando o AdvPL, e posteriormente removendo o arquivo do disco.

User Function GetNetCfg()
Local cExec := 'cmd /c "ifconfig > netconfig.txt"'
Local cRet := ''
Local cRmtPath

// Identifica o PAth do SmartClient na maquina remota 
// a partir do path do arquivo smartclient.ini
cRmtPath := GETREMOTEININAME()
cRmtPath := left(cRmtPath,rat('\',cRmtPath))

// Executa o comando para chamar o ifconfig 
// e gerar o arquivo "netconfig.txt"
WaitRun(cExec,0)

If file(cRmtPath+'netconfig.txt')
 // O arquivo foi gerado, lê direto com MEMOREAD
 // E remove o arquivo do disco 
 cRet := memoread(cRmtPath+'netconfig.txt')
 ferase(cRmtPath+'netconfig.txt')
Endif

Return cRet

Outros Usos

É claro, existem limitações na passagem de parâmetros para uma aplicação executável ( Se eu não me engano a linha de comando total não pode ser maior que 255 bytes, e se a operação a ser executada é vital para garantir a continuidade do processo, garantir a execução com sucesso ou até descobrir onde houve falha na execução pode se tornar um problema. Para questões desta ordem, você pode fazer uma DLL para encapsular as chamadas e proteger com o que você achar necessário. Para execuções simples, que não precisam desse aparato todo, Shellexecute(), WaitRun() e WinExec() dão conta do recado.

Restrições

Quando voce usa WaitRun() ou WinExec(), se você fizer uma chamada direta a um comando de sistema operacional ou outro aplicativo de linha de comando, que tenha alguma parada de interface para solicitar entrada de dados ou confirmação de usuário, como não será aberta no terminal nenhuma janela de sistema operacional para dar um contexto de entrada para a aplicação, o prompt de comando vai ficar preso na memoria, até que o computador seja reiniciado ou o processo da aplicação seja derrubado da memória pelo Gerenciador de Tarefas do Windows.

Um exemplo clássico é criar um arquivo de lote (extensão “.bat”), e usar dentro dele uma instrução “pause”. Pronto, rodou isso com WaitRun(), seu SmartClient fica esperando pra sempre uma aplicação externa que não vai voltar nunca. Se você usar WinExec(), o programa AdvPL segue a vida, mas o processo iniciado na máquina remota para rodar o arquivo de lote fica preso na memória, até ser derrubado ou o usuário do computador fazer um logoff do sistema operacional ou reiniciar o equipamento. Se você executar isso pelo ShellExecute(), uma janela do interpretador de comandos será aberta, e você terá visão e interação com a aplicação em lote sendo executada.

Existem aplicações como o 7Zip, WinZip, WINSCP e afins, que embora sejam aplicações com interface gráfica, elas podem ser chamadas em modo ‘batch’, isto é, por linha de comando, sem interface. Com isso, por exemplo, você pode usar uma aplicação externa para transferir um arquivo para um servidor, gerar um pacote compactado de arquivos para enviar ao servidor, usar aplicações externas para fins específicos de integração com outras interfaces ou mesmo dispositivos.

Conclusão

Com estes três comandos, já dá pra fazer muita coisa. O modelo de chamada de aplicação externa é muito interessante, pois não compartilha a mesma área de memória do SmartClient, a aplicação é executada diretamente pelo sistema operacional, e nada impede de você criar a sua aplicação externa para atender a sua necessidade. Quando a integração envolve a chamada de diversas funções ou diversas vezes a mesma aplicação em curtos intervalos de tempo, pode ser mais vantajoso gastar um pouco mais de tempo e fazer a integração com DLL.

Espero que este post ajude a todos que desenvolvem em AdvPL a facilitar o processo de integração com outros sistemas, e desejo novamente a todos TERABYTES de SUCESSO 😀

Referências

http://tdn.totvs.com/display/tec/WinExec
http://tdn.totvs.com/display/tec/WaitRun
http://tdn.totvs.com/display/tec/ShellExecute

Aplicações Externas no AdvPL – Parte 01

Introdução

A linguagem AdvPL permite a execução de aplicações externas. E este recurso é muito interessante quando precisamos fazer automação de processos e integrações com sistemas legados ou aplicações externas ao TOTVS Application Server. Nesta série de tópicos, vamos abordar o que o AdvPL nos oferece para realizar estas tarefas, e para abrir esse assunto, este tópico têm um exemplo funcional de DLL para ser usada no SmartClient, que permite capturar a tela da máquina que está executando o SmartClient.

Visão Geral de Aplicações Externas

Podemos definir como “aplicações externas”, qualquer programa executável, com ou sem interface, criado para uma determinada finalidade, que não pertence ao conjunto de ferramentas do Protheus.

Existem inúmeras aplicações externas que o Protheus realiza algum tipo de integração. Por exemplo, um servidor de e-mail ou de FTP. É uma aplicação externa, que pode ser acessada através de uma conexão TCP, onde já existe uma função ou classe em AdvPL para encapsular as funcionalidades desta aplicação, como as classes de FTP e e-MAIL.

Normalmente muitas aplicações externas são sistemas complexos, com múltiplas funcionalidades. Algumas delas oferecem APIS de acesso via rede TCP/IP, mediante troca de mensagens em formato específico ou proprietário, ou usando XML-SOAP sobre HTTP, REST sobre HTTP, etc.

Aplicações Específicas ou Especialistas

Existem alguns tipos de aplicações, normalmente menores e criadas para tarefas especializadas, que não oferecem uma camada de acesso remoto, ou mesmo alguma API, mas sim apenas uma DLL ou um executável sem interface, onde devemos realizar a chamada da operação desejada através do endereço de uma função publicada na DLL, ou mesmo através da linha de comando do sistema operacional. Nesta abordagem de aplicações externas, vamos focar inicialmente nestes casos.

Recursos do AdvPL

Usando determinadas funções do AdvPL, podemos realizar a carga de uma DLL, e a chamada de uma função publicada nesta DLL, desde que a mesma atenda a especificação de parâmetros estabelecida, e podemos chamar programas externos (executáveis ou mesmo comandos do sistema operacional).

Porém, devido a uma (importante) questão de segurança e isolamento, ñenhum destes recursos está disponivel para carga de dlls ou execução de programas na instância do TOTVS | Application Server (serve-side). Elas foram feitas para trabalhar em conjunto com o TOTVS SmartClient.

Desse modo, a sua aplicação AdvPL precisa ser iniciada a partir de um SmarrtClient, para a partir dele carregar uma DLL ou chamar um programa executável disponivel na máquina onde o Smartclient está sendo executado. Deste modo, nenhum processo executado em JOB dentro do Protheus Server consegue carregar uma DLL no servidor, ou iniciar um aplicativo executável externo.

SmartClient – Carga de DLL

Existem 5 (cinco) funções no AdvPL que lidam com a carga e execução de funções em DLL, a seguir :

ExecInDLLOpen() – Carrega a DLL na área de memória do SmartClient
ExecInDLLRun() – Executa uma função pré-definida na DLL carregada
ExeDLLRun2() – Executa uma função pré-definida na DLL carregada
ExeDLLRun3() – Executa uma função pré-definida e diferenciada na DLL carregada
ExecInDLLClose()- Descarrega a DLL da memória do SmartClient

A DLL criada deve conter uma (e apenas uma) das prototipagens abaixo, para ser chamada pelo AdvPL:

extern “C” __declspec(dllexport) void ExecInClientDLL(int , char * , char * , int )
extern “C” __declspec(dllexport) int ExecInClientDLL(int , char * , int , char * , int )

Usamos a primeira prototipagem para realizar chamadas pelas funções AdvPL ExecInDLLRun() e ExeDLLRun2(), e a segunda pode ser chamada apenas pela ExeDLLRun3(). Devemos escolher apenas uma prototipagem a ser usada. Não é possível usar as duas na mesma DLL. Você pode criar uma DLL usando C ou C++, ou Delphi, ou outra linguagem que permita a declaração da função na prototipagem adequada. Quando precisamos encapsular um ou mais funcionalidades dentro de um projeto que dependem de DLLs de terceiros, podemos encapsular a DLL de terceiros, construindo um projeto de uma DLL que faça link dinâmico com a DLL de terceiros a ser encapsulada, fazendo a camada de chamadas dentro da função prototipada.

Uma aplicação externa em uma DLL pode abrir uma interface própria, sobre a interface do SmartClient, ou pode ser usada simplesmente para acessar um dispositivo externo ou realizar qualquer outra tarefa sem interface. Existem limitações sobre os parâmetros e retorno da chamada, estes limites variam de acordo com a função AdvPL utilizada para realizar a chamada.

Parâmetros e Retornos

Basicamente, todas as prototipagens permitem a passagem simultânea de um parâmetro numérico inteiro e uma string, e um retorno de uma string. A diferença entre elas está no tamanho dos parâmetros, e a última prototipagem permite também o retorno simultâneo para o AdvPL de um valor numérico e uma string. A documentação da TDN aborda em detalhes o funcionamento esperado de cada uma, vamos focar aqui em um exemplo prático.

DLL de Captura de Tela

Para mostrar como a carga e execução de uma DLL no TOTVS SmartClient funciona, vamos começar com um projeto em C++, que pode ser compilado a partir do Visual Studio 2005 e versões superiores. Basta criar um “Empty Project” em C++ no Visual Studio, definir que o projeto será uma DLL, e criar o fonte de cabeçalho “GetScreen.hpp” e “GetScreen.cpp”, com os conteúdos abaixo:

Arquivo [getscreen.hpp]

void DoCapture( char * file , char * result );
void ScreenCapture(int x, int y, int width, int height, char *filename, char * result );

Arquivo [getscreen.cpp]

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <windows.h>
#include <gdiplus.h>
#include <memory>

#include "GetScreen.hpp"

/* ------------------------------------------------------------
Programa GetScreen
Autor Júlio Wittwer
Data 07/09/2016
Descrição DLL Win32 para uso com o TOTVS SmartClient
 Permite a captura da tela do computador onde o SmartClient está sendo executado
 gerada como uma imagem .JPEG salva em disco, usando o nome do arquivo fornecido 
 como parâmetro.

Utilização 

 Utilizar a função ExecInClientDLL(), informando os seguintes parâmetros: 

 int ID => Número da operação desejada 
 0 = Obter a versão da API 
 1 = Capturar um ScreenShot em formato JPEG

 char * BufIn => array de char contendo um parâmetro para a opção.
 SE ID = 0, o parâmetro é ignorado e pode ser NULL
 SE ID = 1, deve ser informado o nome do arquivo JPEG no qual a 
 captura de tela deve ser gravada. 

 char * BufOut => Array de char contendo o resultado da chamada, no formato 
 NNN XXXXXXXX, onde NNN é um Código de Status , e XXXXX contém uma informação 
 adicional sobre o status retornado. Em caso de sucesso, o número retornado é "000"
 Qualquer outro número indica uma condição de falha. 

 "001 Encode size failed"
 "002 Memory allocation failed"
 "003 Image Codec not found"
 "004 Image Save Error (%d)"
 "005 Unexpected Error %d
 "010 Unknow Command"

------------------------------------------------------------ */


extern "C" __declspec(dllexport) void ExecInClientDLL( int ID, char * BufIn, char * BufOut, int nSizeOut )
{

 if( ID == 0 )
 {
 // Retorna a versão da DLL de captura
 strcpy(BufOut,"000 GetScreen V 0.160911");
 }
 else if (ID == 1)
 {
 // REaliza a captura da tela
 // Recebe em BuffIn o nome do arquivo a ser salvo 
 // Retona em buffOut o status da operação 
 // Em caso de sucesso, retorno "000 Ok"
 // Em caso de erro, retorno "NNN <error message>"
 DoCapture(BufIn,BufOut);
 }
 else
 {
 // ID não conhecido/inválido 
 strcpy(BufOut,"010 Unknow Command");
 }
}

using namespace Gdiplus;
using namespace std;

// Inicializa GDI para a captura de vídeo
// faz a captura, salva em disco, e finaliza GDI

void DoCapture( char * file , char * result ) 
{

 // Initialize GDI+.
 GdiplusStartupInput gdiplusStartupInput;
 ULONG_PTR gdiplusToken;

 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

 int x1 = 0;
 int y1 = 0;
 int x2 = GetSystemMetrics(SM_CXSCREEN);
 int y2 = GetSystemMetrics(SM_CYSCREEN);

 // Realiza a captura da tela e salva em arquivo 
 ScreenCapture(x1, y1, x2 - x1, y2 - y1, file , result );

 // Shutdown GDI+
 GdiplusShutdown(gdiplusToken);

}


// Retorna o ponteiro do encoder adequado para fazer
// a conversão do BITMAP em memória para o formato desejado


int GetEncoderClsid(const WCHAR* format, CLSID* pClsid )
{
 UINT num = 0; // number of image encoders
 UINT size = 0; // size of the image encoder array in bytes

 ImageCodecInfo* pImageCodecInfo = NULL;

 GetImageEncodersSize(&num, &size);

 if(size == 0)
 {
 // Encode Size Failure
 return -1; 
 }

 pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
 if(pImageCodecInfo == NULL)
 {
 // Memory allocation failure
 return -2; 
 }

 GetImageEncoders(num, size, pImageCodecInfo);

 for(UINT j = 0; j < num; ++j)
 {
 if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
 {
 *pClsid = pImageCodecInfo[j].Clsid;
 free(pImageCodecInfo);
 // Success
 return j; 
 } 
 }

 free(pImageCodecInfo);

 // Image Codec not found
 return -3; 
}

// Encapsula a gravação do BITMAP capturado da tela
// para o formato JPEG em disco 

void BitmapToJpg(HBITMAP hbmpImage, char *filename , char * result )
{

 Status eRC = Ok;

 Bitmap * p_bmp = Bitmap::FromHBITMAP(hbmpImage, NULL);

 CLSID pngClsid;

 int RC = GetEncoderClsid(L"image/jpeg", &pngClsid);

 if( RC >= 0 )
 {
 const size_t cSize = strlen(filename)+1;
 std::wstring wc( cSize, L'#' );
 mbstowcs( &wc[0], filename, cSize );
 eRC = p_bmp->Save(&wc[0], &pngClsid, NULL);
 if ( eRC != Ok)
 RC = -4;
 }

 delete p_bmp;

 if ( RC == -1 )
 sprintf_s(result,255,"001 Encode size failed");
 else if ( RC == -2 )
 sprintf_s(result,255,"002 Memory allocation failed");
 else if ( RC == -3 )
 sprintf_s(result,255,"003 Image Codec not found");
 else if ( RC == -4 )
 sprintf_s(result,255,"004 Image Save Error (%d)",eRC);
 else if ( RC < 0 )
 sprintf_s(result,255,"005 Unexpected Error %d",RC);
 else
 sprintf_s(result,255,"000 Ok");

}


// Funão de captura / snapshot de tela
// Requer o ambiente DGI previamente inicializado

void ScreenCapture(int x, int y, int width, int height, char *filename, char * result )
{
 HDC hDcMemory = CreateCompatibleDC(0);
 HDC hDcScreen = GetDC(0);
 HBITMAP hBmp = CreateCompatibleBitmap(hDcScreen, width, height);

 SelectObject(hDcMemory, hBmp);
 BitBlt(hDcMemory, 0, 0, width, height, hDcScreen, x, y, SRCCOPY);

 // Converte a tela capturada em JPEG e salva em disco 
 BitmapToJpg(hBmp, filename , result );

 // Cleanup
 DeleteObject(hBmp);
 DeleteObject(hDcMemory);
 ReleaseDC(NULL,hDcScreen);
}

Feito isso, basta fazer a “build do Projeto, gerando uma DLL 32 bits, e copiá-la para a pasta do TOTVS SmartClient.

Agora, crie um fonte AdvPL, chamado por exemplo de “GetScreen.PRW”, adicione-o a um projeto no IDE/TDS, com o seguinte conteúdo:

Arquivo [GetScreen.PRW]

/* ------------------------------------------------------------
Funcao U_GetSCR()
Autor Júlio Wittwer
Data 09/2016
Descrição Tira um ScreenShot da tela da máquina onde está 
 sendo executado o TOTVS | SmartClient, e copia 
 o JPEG com a imagem gerada para uma pasta 
 chamada "\images\" a partir do RootPath do 
 ambiente no Servidor
Observação A Pasta \images\ a partir do RootPath deve ser 
 criada antes de executar este programa

------------------------------------------------------------ */

User function GetSCR()

Local hHdl := 0
Local cRet
Local nCode
Local cRmtFile
Local cSrvFile 
Local cImgFile

// Abre a DlL
hHdl := ExecInDLLOpen( "GetScreen.dll" )

IF hHdl == -1 
 UserException("Failed to load [GetScreen.dll]")
Endif

// Pega a versao do GetScreen
cRet := ExecInDllRun( hHdl, 0, "" )
conout(cRet)

// identifica a pasta temporaria da maqina 
// que está rodando o smartclient 
// e usa ela pra criar a imagem do screenshot

cImgFile := 'img_'+cValTochar(Threadid())+'.jpeg'
cRmtFile := GETTEMPPATH() + cImgFile;

Msgrun( "Aguarde",;
 "Executando captura de tela",;
 {|| cRet := ExecInDllRun( hHdl, 1, cRmtFile )} )


If empty(cRet)
 UserException("Unexpected Empty result from [GetScreen.dll]")
Endif

// Verifica o codigo retornado
// 000 = Sucesso 
// <> 0 = Falha

nCode := val(left(cRet,3))
If nCode > 0 
 UserException(cRet)
Endif

// copia o arquivo do terminal para o servidor
CPYT2S(cRmtFile,"\images\")

// Apaga o arquivo na pasta temporária da estação 
Ferase(cRmtFile)

// Informa a operação realizada
MsgInfo("Imagem salva no servidor em \images\"+cImgFile)

// ----------------------------------------------------------------
// Fecha a DLL
ExecInDllClose( hHdl )

Return 

Execução

Após gerar a DLL 32 bits no Visual Studio, copiá-la para a pasta do SmartClient, e compilar o fonte AdvPL “GetScreen.PRW”, e criada uma pasta chamada “\images\” a partir do RootPath do seu ambiente do Protheus, basta iniciar um Totvs SmartClient, e executar o programa “U_GetSCR”. Ele mostrará rapidamente uma caixa de diálogo da função MsgRun(), a partir de onde será feita a captura da tela na estação onde está sendo executado o TOTVS SmartClient, e logo depois uma caixa de diálogo, informando o nome do arquivo que contém a imagem capturada na pasta \images\ no ambiente do Protheus Server.

O Fonte em AdvPL determina qual é o nome da pasta temporária da máquina onde o SmartClient está sendo executado, cria um nome para salvar a imagem, chama a DLL pra fazer a captura da imagem, informando o nome do arquivo onde a imagem deve ser salva, e depois apenas copia o arquivo gerado no disco da estação onde o SmartClient está sendo executado para a pasta “\images\” do Protheus Server, e apaga o arquivo temporário da estação.

Estendendo as funcionalidades

O modelo proposto permite que você implemente literalmente milhares de instruções dentro de uma DLL, mesmo tendo apenas uma função prototipada, pois podemos criar uma funcionalidade com tratamento diferenciado de parâmetros e retorno para cada nID utilizado. Usando o ID 0 (zero) para controlar a versão da API, quando você implementar uma funcionalidade nova nesta DLL, basta atualizar o número da versão, e no seu programa em Advpl, basta verificar qual é a versão em uso, para saber se ela já possui a funcionalidade desejada. Por exemplo, baseado neste programa, você pode implementar o ID=2 para pegar a quantidade de monitores que existe na máquina Client, ou a resolução atual de tela, ou qualquer outra coisa que lhe seja útil.

Fontes e DLL

Para você que quer compilar, testar e usar este recurso, ou estendê-lo, basta pegar os fontes no https://github.com/siga0984/Blog — todos os arquivos que começam com “GetScreen.*”. Inclusive, também disponibilizei o arquivo getscreen.dll, compilado para Windows 32 Bits. Basta copiar ele para a pasta do SmartClient, compilar o programa AdvPL GetScreen.PRW no repositório do ambiente Protheus e utilizá-lo. Segue abaixo links do GitHub para cada um deles.

https://github.com/siga0984/Blog/blob/master/GetScreen.hpp
https://github.com/siga0984/Blog/blob/master/GetScreen.cpp
https://github.com/siga0984/Blog/blob/master/GetScreen.prw
https://github.com/siga0984/Blog/blob/master/GetScreen.dll

Conclusão

Esse é um exemplo bem arroz-com-feijão, apenas pra dar um gostinho do que podemos fazer com esta integração, e ainda nem chegamos na camada de chamar aplicações externas — que também é uma “mão na roda”. No próximo post, vou entrar um pouco mais em alguns aspectos e propriedades desta integração com DLL, que abrem um amplo leque de possibilidades !!!

Agradeço a todos(as) pela audiência, e lhes desejo TERABYTES de SUCESSO 😀

Informações Adicionais

RELEASE 2016/09/14 23:32 – Meus agradecimentos ao meu chapa Pedro Scarapicchia, por localizar e me apontar 3 pontos de leak na função ScreenCapture() 😀 Post e GitHub atualizados 😉 Pedro, obrigado por me lembrar das boas práticas — a pressa de colocar o post no ar foi maior que a atenção no código ..rs…

Referências

http://tdn.totvs.com/display/tec/ExecInDLLOpen
http://tdn.totvs.com/display/tec/ExecInDLLRun
http://tdn.totvs.com/display/tec/ExeDLLRun2
http://tdn.totvs.com/display/tec/ExeDLLRun3
http://tdn.totvs.com/display/tec/ExecInDLLClose

 

Protheus no Linux – Parte 01

Introdução

O TOTVS Application Server ( ou Protheus Server ), bem como o DBAccess e outros produtos da TOTVS, também possuem versões para serem instaladas em Servidores e Desktops com sistema operacional Linux. Neste primeiro post, vamos ver como preparar uma VM com Ubuntu Server para instalar um TOTVS Application Server.

O Linux

Vou pular toda a parte histórica deste sistema operacional, você encontra muito material sobre isso na Wikipedia. Existem atualmente várias distribuições gratuitas e inclusive pagas de Linux, desenvolvidas para uso geral ou para cenários e ambientes específicos, versões “Server” — para servidores dedicados de aplicações — e versões “Desktop” — para uso diário de aplicativos com interface gráfica e janelas, entre outras para uso embarcado em dispositivos, etc.

Estreitando o leque de aplicações para os produtos TOTVS Application Server e TOTVS SmartClient, existem versões e distribuições de Linux homologadas para uso destas aplicações. Como cada distribuição possui versões diferentes e alguns comportamentos distintos, atualmente apenas versões de Linux “Server” são homologadas para o Protheus Server, e versões Desktop são homologadas para uso do SmartClient. A lista de distribuições e versões homologadas está na TDN, atualmente no link http://tdn.totvs.com/display/tec/Plataforma+homologada+dos+produtos

Ubuntu Server

O Ubuntu Linux é uma distribuição muito conhecida do Linux, derivada do Debian Linux, muito prática para instalação e atualização de softwares através do administrador de pacotes “apt-get”. Bem documentada, mantida por uma grande comunidade, possui versões Desktop e Server. Embora (até o momento) nenhuma distribuição Ubuntu esteja oficialmente homologada para uso do TOTVS Application Server, é muito simples criar um ambiente de testes ou desenvolvimento nesta plataforma.

Nada impede o uso desta plataforma em um ambiente de produção, porém como não foi realizado nenhum procedimento de homologação oficial da TOTVS neste sistema operacional, em caso de problema técnico de uma instalação nesta plataforma, não há suporte.

Criando a VM

Peguei uma ISO do Ubuntu Server no site oficial do Ubuntu, especificamente a versão “Ubuntu Server 14.04.4 64 Bits”. ( arquivo “ubuntu-14.04.4-server-amd64.iso”, disponível em http://releases.ubuntu.com/trusty/ ) . Criei uma VM usando um VMWare Player (versão gratuita para fins não-comerciais), com 32 GB de disco, 2 GB de RAM, 2 Cores, e uma interface de rede “Bridged — para a minha VM ter acesso a Internet.

VM Details

Instalando o Ubuntu Server 64

Inicialmente, você baixa a ISO de instalação no seu HD, e configura o Drive de CD da VM para iniciar com o disco montado. Ao iniciar a VM, aparece a tela de seleção de idioma de interface. O Ubuntu já tem a portabilidade para português do Brasil, eu particularmente gosto de usar o S.O. em Inglês mesmo.

Após escolher o idioma pressionando <enter>, será mostrada a tela inicial de setup. Para escolher a instalação mínima de sistema, pressione F4. Será mostrado um pop-up com algumas opções, selecione com as setas a opção “Install a minimal system”, pressione Enter para confirmar a escolha. Feito isso, pressione <Enter> novamente para iniciar a instalação do Ubuntu Server. Escolha agora o idioma de instalação (English, please…)

Na próxima tela, informe onde você está. Vamos informar que estamos no Brasil mesmo. Selecionamos “Other” na escolha de território. Na tela seguinte, escolhemos “South América” … E, finalmente, “Brazil”. Na configuração de “Locale”, pode deixar United States mesmo. Agora o instalador pergunta se você quer detectar o modelo do seu teclado mediante alguns testes de teclas, ou se você quer escolher o modelo por uma lista. Recomendo usar o assistente, é bem simples. Após escolher o teclado, será executada uma etapa preparatória rápida, e em seguida será perguntado o nome no host para esta máquina. Eu chamei a minha de “ubuntu14” mesmo.

Depois, é perguntado o nome completo do usuário da máquina. Na sequência, é perguntado o nome desse usuário para identificá-lo no sistema operacional. No meu caso, “siga0984”. E, na sequência, é solicitada uma senha e confirmação de senha para o usuário criado. Na próxima tela, o instalador pergunta se você quer criptografar a sua pasta “home”. Estamos fazendo uma VM de servidor … não precisa criptogtafar a “home”. Agora, o instalador tenta pegar a data e hora atuais na Internet, e determinar sua localização. Normalmente ele acerta, basta confirmar onde você está (Time Zone) . E, na última parte, vêm o setup do disco. Vamos pra opção mais simples, “Guided – Use entire disk”.

Na sequência, o instalador mostra as escolhas realizadas para a partição de disco. Como a nossa VM têm apenas um HD virtual, basta selecioná-lo. E, na sequência, na próxima tela, o Linux já sugere as configurações finais, basta selecionar <Ok> e prosseguir. Ao confirmar esta etapa, começa efetivamente a instalação do Ubuntu Server. Após a cópia de alguns arquivos, o Ubuntu pergunta se você está usando Proxy, para configurar o “Package Manager” – através dele podemos atualizar de forma muito fácil o sistema operacional. Se vão está sendo usado nenhum proxy, deixamos o campo em branco e apenas teclamos <enter> para continuar.

Após um ou dois minutos configurando os pacotes e instalando o sistema, o instalador pergunta se você quer manter a sua instância atualizada automaticamente. Normalmente eu escolho “não”. Eu atualizo minha VM quando eu precisar ou quiser uma atualização. E, finalmente, a tela que todos esperamos: O que nós queremos de Software na nossa instalação ? Bem, para usar o Protheus e ter um acesso legal na VM, não precisamos mais do que as duas primeiras opções. Selecione-as com as setas e pressione a barra de espaços para marcar ou desmarcar.

A última pergunta é se você quer instalar o gerenciador de boot ( GRUB loader ) na sua VM … bem, eu nunca deixei de instalar, mas não deve fazer diferença alguma para um ambiente de testes virtualizado. E, em poucos instantes, o instalador informa que está tudo pronto, e você pode reiniciar a máquina ! Basta pressionar <enter> para continuar. Após a VM reiniciada, o boot já foi feito pelo HD da VM, e o Ubuntu pergunta pelo seu usuário. Informe o usuário que você criou na instalação, pressione <enter>, depois a senha, enter novamente. Pronto ! Bem vindo ao seu Ubuntu Server 64 bits !

Ubuntu 14 Start

Preparação do Sistema Operacional

Agora, de posse da nossa VM, vamos aplicar as atualizações de sistema operacional desde a distribuição desta versão… Para isso, digite no prompt de comando:

sudo apt-get update

Logo na execução, será perguntado novamente a senha do usuário atual. Mesmo que você já esteja logado, o comando “sudo” serve para executar uma instrução com permissões de “root” ou Super User do Linux. Logo, no primeiro uso, ele vai te pedir novamente a senha do seu usuário … Este comando vai atualizar a lista de pacotes de updates disponíveis nos sites oficiais do Ubuntu. Feito isso, execute o comando abaixo, para instalar as últimas atualizações das aplicações instaladas no seu ambiente:

sudo apt-get upgrade

Caso exista alguma versão nova a atualizar, a aplicação pergunta se você quer atualizar ou não. Digite Y e pressione ENTER para confirmar a atualização. O processo de atualização via fazer download e instalação automática dos pacotes de aplicativos do sistema operacional instalados no seu ambiente, automaticamente.

Terminada esta atualização, vamos aproveitar e instalar alguns pacotes, que serão necessários para etapas posteriores. Inicialmente, vamos instalar o “unzip”, usando o comando abaixo:

sudo apt-get install unzip

Para permitir um acesso via rede para a VM, e inclusive para usar um SFTP ( Secure FTP ) para transferir nossos arquivos e binários do Protheus para dentro da VM, precisamos saber qual é o IP dela na rede. Para isso, usamos o comando abaixo:

ifconfig

Ao ser executado, ele mostra as interfaces de rede da máquina. Uma delas deve ser a eth0, que tem um endereço IPV4 e outro IPV6. Vamos usar o IPV4, destacado em vermelho na imagem abaixo:

Ubuntu 14 ifconfig

Recomendo que você instale na sua máquina Windows um “Putty” e um “WINSCP”, para acesso via Telnet/SSH (Terminal) e acesso ao sistema de arquivos (SFTP), respectivamente. O acesso via Putty, ao invés de acessar pela janela do Host da VM, permite operações como copiar e colar texto entre o terminal da VM e a máquina Windows, você pode abrir mais de um terminal do Putty ao mesmo tempo na mesma máquina, etc… E, finalmente, para a última etapa de preparação, vamos “esticar” um pouco os limites de handles por aplicação do sistema operacional. Isto será necessário posteriormente para subir o APPServer.

Use o comando abaixo para editar o arquivo /etc/security/limits.conf:

sudo vi /etc/security/limits.conf

O editor “vi” não é muito amigável para quem está acostumado com editores visuais, mas quando a tarefa é apenas inserir duas linhas no final da tabela, não é tão difícil. Basta usar as setas para chegar ao final do arquivo, apertar a tecla [ESC], seguido da tecla I (maiúsculo, para entrar no modo de inserção de texto), e acrescentar as linhas abaixo:

* soft nofile 32768
* hard nofile 32768

Com isso, aumentamos para 32768 os limites de handles para todas as aplicações desta instalação de sistema operacional. Para salvar o arquivo, pressione ESC para acessar o prompt interno do editor, então digite a string “wq!” ( sem as aspas ) e pressione <enter>. Isto fará com que o arquivo seja salvo, com o novo conteúdo agora editado, e o editor de textos seja finalizado.

Ubuntu 14 Linuxconf

Feito isso, vamos reiniciar a VM usando o comando abaixo:

sudo reboot now

Após reiniciar a VM, confirme se o IP da VM continua o mesmo ( usando o comando ifconfig ), e faça o setup de uma conexão de terminal para a sua VM usando o Putty, e uma conexão SFTP usando o WINSCP. Ambas as conexões são SSH.

Conclusão

Nesta primeira etapa, finalizamos a criação de uma VM 64 bits com Ubuntu Server, deixando ela praticamente pronta para receber um TOTVS Application Server, um DBAccess 64 bits, um c-Tree Server 64 bits, e afins. No próximo post da sequência do Protheus em Linux, vamos abordar os detalhes da instalação de um TOTVS Application Server nesta VM 😉

Saudações a todos, desejo a vocês TERABYTES de sucesso 😀 

 

Protheus – do AP5 ao P11

Introdução

Desde o surgimento do Advanced Protheus 5.07 em 1999, até o Protheus 11, o produto ganhou novos módulos, funcionalidades e recursos. Desde implementações e extensões da Linguagem AdvPL, até o FrameWork AdvPL com MVC. Porém, até o momento não ouvi falar de nenhuma análise técnica entre as versões. Então, após ressuscitar alguns CDs de instalação antigos, e fazer algumas medições, eu cheguei a alguns números aproximados muito, muito interessantes.

Do Protheus 5 ao Protheus 11

Informações por Versão Ap5 Ap6 Ap7 Mp8 P10 P11
Tabelas (SX2) 991 1394 1681 1677 3700 6368
Índices (SIX) 2190 3124 4107 4249 8770 14868
Campos (SX3) 16306 22042 27087 27545 59420 103951
Campos Virtuais 1281 1746 2261 2104 5284 8890
Maior numero de campos 168 169 176 163 200 267
Maior tamanho de Registro 1794 3761 3761 3761 4022 4909
Tabela com mais índices 14 15 18 26 20 28
Índice com mais Campos 12 12 13 14 14 14
Índice com maior chave 152 203 212 214 232 505
Tamanho do RPO (MB) 28 44 44 36 60 174
Funções no RPO 15104 21665 24601 26177 46026 79811
Classes no RPO 57 57 61 287 603 2451
Resources RPO 2445 3604 4107 4455 7975 15928
Entrada no ERP – SIGAFAT
Memória Consumida (MB) 10,63 10,47 12,48 12,06 15,74 49,22
Chamadas de funções básicas do AdvPl 34690 35682 44170 36615 63844 377300
Chamadas de funções do Repositório 8423 8598 9128 3944 6444 53066
Chamadas de Operadores 514403 576713 614246 363848 589606 4287337
Memoria para Carga de Fontes (MB) 1,8 2,3 2,41 2,91 4,8 14,9
Fontes Carregados 38 44 47 52 72 215

Analisando por cima os dados encontrados, podemos ver que do Protheus 10 para  Protheus 11 houve um salto bem maior em relação às versões anteriores. Houveram muitas implementações no ERP, módulos novos, MVC, ferramentas, integrações e controles adicionais.

 

Ao migrar do P10 para o P11, muitos clientes precisaram refazer o “sizing” de máquina. Na verdade, sempre ao migrar de versão um “sizing” de máquina deve ser avaliado, pois dependendo do tamanho do ambiente, um aumento de consumo de memoria ou CPU de 10 % pode representar um número grande em valores absolutos. No caso do P10 para o P11, o salto foi muito grande, mesmo quem usava o P10 com uma folga razoável de hardware precisou mexer no parque de máquinas para escalar o ambiente.

Os dados obtidos com esta pesquisa são meramente informativos, e foram obtidos em sua maioria com as versões de repositório original do CD, e dicionários sem customização. Foi um trabalho muito gostoso rever as telas de todas as versões de produto que eu trabalhei direta ou indiretamente nos últimos 17 anos, funcionando em um Windows 10 😀

Evolução da Interface

A interface da aplicação ERP foi evoluindo e ganhando novos layouts e funcionalidades ao longo das versões do ERP Microsiga. No TDN, têm um post muito interessante, com vários prints de interface, de todas as versões do ERP Microsiga, desde as primeiras versões Windows (antes do Protheus), até o P12 !! Vale a pena conferir, você pode acessar pelo link http://tdn.totvs.com/display/framework/Microsiga+Protheus

Conclusão

Com o crescimento da plataforma de negócios e de todo o parque de soluções rodando em AdvPl em cima da plataforma TotvsTec, o desafio constante de aprimorar a plataforma e implementar recursos para tornar viável a implementação e operacionalização de soluções cada vez maiores faz parte da rotina diária de todos os colaboradores da TOTVS 😀

Desejo a todos TERABYTES de sucesso !!!

Abraços e Saudações 😉 

 

 

Damas em AdvPL

Introdução

Em 2006, eu fiz um jogo de Damas em AdvPL, para jogar contra o Computador. Não cheguei a reforçar muito o algoritmo de decisão, mas ficou bom o suficiente pra dar um pouco de trabalho. O fonte ainda está cm alguns remendos, ainda não está pronto para um post de fins didáticos, MAS, atendendo a pedidos no FaceBook, estou disponibilizando um Patch da P11 (RPO TOP / Português) do Jogo, para degustação 😀

O Jogo

Após aplicar o patch, basta iniciar o SmartClient com a função “U_APGAMES”, e será mostrada a interface abaixo, para você entrar com o NickName do Jogador.

Checkers003

Após inserir o NickName e clicar em “Iniciar”, inicia-se o jogo de Damas. Você joga com as pedras amarelas, e o computador com as azuis. Você inicia a partida. Para jogar, primeiro você clica em cima de uma pedra sua, depois clica no lugar onde a sua pedra deve ser movimentada, vide sequência abaixo:

Checkers006a

Checkers006b

Checkers006c

Assim que você jogar, o computador jogará uma pedra dele. E já é a sua vez de novo.

Regras

  • As pedras normais somente movimentam-se para a frente.Em nenhuma hipótese uma pedra normal movimenta-se para trás.
  • Não existe “assopro”. Se você oferecer uma ou mais pedras ao computador, ele é obrigado a comer a sua pedra, e vice-versa.
  • Se mais de uma pedra for oferecida, o adversário escolhe como e qual pedra ele vai atacar.
  • Ao chegar do outro lado do tabuleiro com uma pedra normal, ela vira uma “Dama”.
  • A Dama pode mover-se para a frente e para trás, mas apenas UMA CASA por vez.
  • O mecanismo de navegação do Computador é “reativo”, então para você ganhar o jogo, você tem que encurralar o computador e fazer ele entregar as suas peças.
  • É mais fácil vencê-lo em jogo aberto, havendo troca de peças, e você abrindo caminho primeiro para fazer uma Dama. Mas cuidado com as “arapucas”, ele pode oferecer “inocentemente” uma pedra, e limpar duas ou três suas. 😉

Patch

O Patch do jogo está disponível para download no link “https://github.com/siga0984/Blog/blob/master/tttp110_APGames.zip” . Basta abrir a página, e clicar em “View RAW”, no final da página, para o Browse fazer o Dowload do ZIP contendo o Patch.

Conclusão

Existem diversas melhorias previstas no design do jogo, e inclusive a separação completa do core do jogo e da interface, além da utilização de orientação a objetos. Uma vez passado a limpo, o jogo será disponibilizado na íntegra, co os fontes 😀

Espero que vocês gostem do desafio, joguem um pouco contra o algoritmo, postem seus resultados no FaceBook 😉 E se você gostou, faça como eu: Compartilhe 😀

Desejo a todos TERABYTES de SUCESSO 😀 Abraços 😉 

 

RunTime do AdvPL

Introdução

No primeiro post do Blog, em apenas um parágrafo foi dada uma definição bem sintética do que é o AdvPL: Trata-se de uma uma linguagem de programação estruturada com suporte a orientação de objetos, que roda em uma máquina virtual com arquitetura client-server e multi-plataforma.

Hoje vamos entrar um pouco mais fundo em algumas características da execução de um código AdvPL, desde a pré-compilação até a execução do Código. Mas antes, vamos com algumas definições.

Código AdvPL

A sintaxe da linguagem AdvPL é uma extensão da sintaxe xBase, mais conhecida pelo seu uso na linguagem Clipper. Um código fonte em AdvPL passa por uma etapa de pré-compilação, onde vários comandos são transformados em chamadas de funções, e “açúcares sintáticos” podem ser implementados, através de #defines, #translates, #command(s), #ifdef(s) e afins. Os comandos da linguagem AdvPL que realmente são “comandos” são as instruções de decisão e iteração, como WHILE, FOR, IF, ELSEIF , ELSE, CASE, END … praticamente todo o resto dos comandos são implementações feitas por #command, #xcommand ou #translate, que permitem você usar por exemplo um “comando” de abertura de tabela, como o comando “USE <cTabela> ALIAS <cAlias> [ SHARED | EXCLUSIVE ] [ VIA <cRdd> ] [READONLY] [NEW]”, que na etapa de pré-compilação vai ser transformando internamente na chamada da função DbUseArea().

Pré-compilação

Tanto o IDE como o TDS (versão anterior e atual do Ambiente de Desenvolvimento de aplicações AdvPL, respectivamente) trabalham com um pré-compilador (appre.exe). A partir do fonte AdvPL e dos respectivos #include(s) utilizados no código, a pré-compilação gera um novo arquivo, chamado de PPO (Pre Processed Output), e este sim é o código enviado ao AppServer para ser compilado.

Compilação

A compilação deste código gera um “ByteCode” em formato proprietário, interpretável somente pela máquina virtual do AppServer. O ByteCode gerado é criptografado e armazenado em um arquivo de repositório de objetos, configurado no AppServer para o ambiente onde os fontes estão sendo compilados. Damos o nome de “ambiente” a um espaço virtual de execução, que engloba um repositório de objetos e um ponto de acesso a uma pasta no disco (local ou remota via rede) para acesso a arquivos sob este identificador de ambiente, entre outras configurações de acesso a dados relacionais e comportamentos desejados para a instância do ambiente.

Repositório de Objetos

Armazena as funções e classes compiladas a partir dos fontes AdvPL, é fornecido pela TOTVS contendo as compilações dos módulos do ERP Microsiga do produto padrão. Contém também as funções do Framework AdvPL, usadas pelos fontes dos demais módulos, e que também são usados em customizações do produto, onde o próprio cliente pode criar um código em AdvPL, usando-se das funções básicas e de Framework AdvPL para customizar o produto padrão através de pontos de entrada (compilação de funções de usuário — USER FUNCTION(s) — com nomes especificos, onde existem pontos de chamada realizados pelo produto padrão do ERP), ou mesmo criando novas funcionalidades.

O repositório possui mecanismos de verificação de integridade e proteção de código, para evitar a engenharia reversa dos fontes compilados. A reversão de código é possível, e às vezes necessária quando um cliente por exemplo “perde” os fontes das customizações da versão em uso do seu ERP, e precisa delas no momento de uma migração de versão. O procedimento de “descompilação” neste caso é solicitado para a TOTVS mediante abertura de chamado, onde cada caso é avaliado.

O repositório de objetos também guarda arquivos de imagem (BMP,PNG,JPG), que podem ser inseridos em um Projeto AdvPL através do IDE/TDS e recuperados posteriormente em tempo de execução e usados diretamente por algumas funções, classes e comandos da linguagem que montam a interface da aplicação.

O Protheus 12 utiliza uma nova engine da máquina virtual do AppServer, com um novo formato de ByteCode, com mais instruções de baixo nível. Isto tornou o ByteCode ligeiramente maior, porém com ganhos de desempenho em alguns processos e funcionalidades.

Execução do AdvPL

Partindo de uma execução de um programa AdvPL, iniciado a partir do SmartClient, o SmartClient faz um HandShake com o AppServer via conexão TCP (ou SSL caso configurado), e solicita a execução de uma função. Ela pode ser uma “Main Function” — Função de entrada de módulo ou ferramenta do ERP — ou uma “User Function” — aplicações ou customizações criadas em AdvPL pelo próprio cliente.

Como o AdvPL é essencialmente dinâmico, os fontes do repositório são carregados na memória sob demanda, conforme as funções vão sendo chamadas. Quando o Menu do ERP é apresentado, todos os programas já carregados e executados até então, essencialmente fontes do Framework do AdvPL são mantidos na memória. Ao chamar uma opção de menu, o fonte e suas dependências são carregados sob demanda e executados, e quando o usuário encerra a opção e volta ao Menu do ERP, todos os fontes carregados desde o início daquela opção são descarregados da memória.

Quando a excecução é a chamada de menu da interface MDI (SIGAMDI), cada opção de menu cria um contexto e uma conexão separada com o AppServer, permitindo a execução de múltiplos programas sob a mesma interface, onde cada execução possui seu contexto e processo independentes. Cada opção de menu finalizada encerra o seu próprio conexto e descarrega todos os prograas da memória. Ao usar o mecanismo de Balanceamento de Carga do AdvPL, cada nova opção de Menu do MDI pode ser direcionada para o AppServer com menos processos em execução.

Para jobs, não existe balanceamento de carga nativo. Cada Job é executado na instância do serviço onde o AppServer está sendo executado. Hoje podemos emular um balanceamento realizando um RPC para um determinado serviço e subindo o job via RPC ( Nativo do AdvPL).

Atualmente o contexto de uma execução em AdvPL está implicitamente amarrado com uma Thread do Sistema Operacional. A arquitetura multi-thread do AppServer não estabelece afinidade entre as CPUS físicas do equipamento, o Scheduler de tarefas do sistema operacional e encarregado de distribuir as Threads e realocá-las por CPU a seu critério. Devido ao cenário extremamente variável de peso de execução das Threads, tentar estabelecer uma afinidade tende a ser pior do que deixar o sistema operacional escolher a CPU em uso.

AdvPL com interface – SmartClient

A execução de processos de interface AdvPL com o SmartClient é síncrona, a aplicação SmartClient é a resposável por renderizar a interface (Janela ou diálogo) construída pela aplicação AdvPL, e uma vez que a interface seja “ativada”, o AppServer passa a esperar por uma interação do usuário no SmartClient, como pressionar um botão ou preencher um campo. Entre ambos existe um mecanismo de verificação de conexão, chamado “pulso”. Quando o AppServer está aguardando por uma interação do usuário com a interface, mesmo que o usuário não dispare nenhum evento, a cada 60 segundos o SmartClient dispara um evento interno de “pulso” para o AppServer , para indicar que o SmartClient ainda está aberto. Caso o AppServer nao receba nenhum evento ou nenhum pulso do SmartClient em 3 minutos, ele assume que o SmartClient não está mais conseguindo se comunicar com o AppServer , e derruba/finaliza o processo em execução no AppServer , liberando os recursos utilizados ( arquivos e registros bloqueados, licenças, conexão com DBAccess,etc ).

Depuração de Código AdvPL

Através do IDE e/ou TDS, podemos depurar (ou “debugar”) um código AdvPL, colocando pontos de parada, inspecionando variáveis de memória, e determinando as etapas de execução (step into, step over, step out, run…). Deve-se tomar uma atenção especial no uso dos “Watches” de execução, pois a chamada de funções dentro de um “Watch” podem mudar o comportamento da aplicação, como por exemplo reposicionar o ponteiro de registro atual de uma tabela de dados, mudando o comportamento da aplicação enquanto ela está sendo depurada. Normalmente usamos os Watches para variáveis de memória. Através da Janela de Comandos do IDE/TDS, também podemos executar expressões, e até mudar conteúdos de variáveis durante a depuração através de instruções de atribuição. Isto pode ser útil em muitas situações.

Uso de memória

Normalmente a memória alocada por um programa é limpa quando o programa é finalizado. Durante a execução de código, existem algumas limpezas realizadas nos destrutores de componentes de interface, e no retorno ao stack (pilha) anterior após a execução de funções do Repositório de Objetos. Normalmente objetos são limpos quando não são mais referenciados, porém caso seja feita uma referência circular entre objetos, a limpeza somente ocorre quando a aplicação termina, ou quando for realizada explicitamente por uma função chamada “FreeObj()”.

Existem diversos recursos de monitoramento de memória do AppServer, um dos mais utilizados é a configuração DebugThreadUsedMemory, que pode ser habilitada no appserver.ini, na seção [GENERAL]. Esta configuração faz com que o AppServer mostre no Monitor de Processos do AppServer a quantidade de memória aproximada em uso por cada processo em execução.

Conclusão

Boa parte dos recursos mencionados neste tópico estão documentados no site oficial de documentação da TOTVS, o TDN (Totvs Development Network), que pode ser acessado no link http://tdn.totvs.com. O Post de hoje é só pra “molhar” o bico, os próximos posts em momento oportuno vão entrar em mais detalhes de cada uma destas operações 😀

Agradeço a audiência de todos, e desejo a vocês TERABYTES de sucesso !!! 

Abraços 😉