Dicas valiosas de programação – Parte 02

Introdução

Continuando na linha de boas práticas e afins, vamos abordar nesse post mais uma dica de ouro, válida para qualquer aplicação que pretende ser escalável, porém com foco no AdvPL

Procure fazer transações curtas

Parece simples, e se olharmos bem, não é assim tão complicado. Primeiro, vamos conceituar uma transação. Na linguagem AdvPL, temos os comandos BEGIN TRANSACTION e END TRANSACTION, que por baixo utilizam recursos do Framework do ERP Microsiga, junto com recursos da linguagem AdvPL, para identificarmos e protegermos uma parte do código responsável por realizar inserção ou atualização de dados em uma tabela.

Quando utilizamos um Banco de Dados relacional no AdvPL, através do DBAccess, proteger a parte do fonte AdvPL que faz alterações no banco de dados é importante, pois evita que, caso ocorra um erro (de qualquer origem)  durante a execução deste trecho, e ele não chegue ao final da transação, nenhuma das operações realizadas com os dados desde o BEGIN TRANSACTION são realmente efetivadas no Banco de Dados, mas sim descartadas. Isso evita por exemplo dados órfãos na base de dados, ou registros incompletos ou inconsistentes.

Por que transações curtas ?

Cada alteração de um registro de qualquer tabela no AdvPL, exige que seja feito um LOCK (ou bloqueio) explícito no registro, para que o mesmo possa ser alterado, e este bloqueio, uma vez adquirido, somente pode ser solto no final da transação. Quanto maior (mais longa) for a duração da transação, por mais tempo este registro permanecerá bloqueado para os demais processos do sistema.

Erros comuns que devem ser evitados

  • “Não abrirás interface com o usuário no meio de uma transação”

Antes de abrir uma transação, a aplicação deve realizar todas as validações possíveis nos conteúdos que estão sendo manipulados. Se qualquer um deles não passar na validação, o programa nem abre a transação. O objetivo da transação é pegar os dados em memória e transferi-los para as tabelas pertinentes, sem interrupções. Ao abrir uma tela ao usuário no meio de uma transação, á transação somente continua depois que o usuário interagir com a interface. Se o operador do sistema submeteu uma operação, virou as costas e foi tomar café, os registros bloqueados pelo processo dele assim permanecerão até que ele volte. Isso pode prejudicar quaisquer outros processos que precisem também atualizar estes mesmos registros.

  •  “Evitarás processamentos desnecessários dentro da transação”

Eu adoro o exemplo do envio de e-mail. Imagine que, durante a gravação de um pedido, exista a necessidade de emissão de um e-mail para inciar um WorkFlow ou um aviso ao responsável pelo setor de compras que um item vendido atingiu um estoque mínimo. Como já visto anteriormente, a solução mais elegante é delegar o envio desse e-mail para um outro processo, preferencialmente notificado de forma assíncrona. Se, em último caso, você precisa que esse email seja disparado na hora, e isso impacta seu processo, não dispare esse email com a transação aberta, lembre-se do tempo que os registros da sua transação vão ficar bloqueados … Grave em um dos campos de STATUS que o envio do e-mail está pendente, e finalize a transação. Então, tente enviar o e-mail, e ao obter sucesso, bloqueie novamente o registro e atualize apenas o campo de STATUS.

  • “Evitarás DEADLOCK”

Ao realizar atualizações concorrentes (mesmo registro tentando ser atualizado por mais de um processo), procure estabelecer uma ordem de obtenção dos bloqueios dos registros, para não fazer a sua aplicação entrar em deadlock — duas transações, que vamos chamar de A e B, já tem cada uma um registro bloqueado de uma determinada tabela. A transação A têm o lock do registro 1, e a transação B têm o lock do registro 2, porém a transação A precisa do lock do registro 2, e a transação B precisa do lock do registro 1, e enquanto nenhuma delas desistir, uma fica esperando a outra e as duas transações “empacam” (efeito conhecido por DEADLOCK).

Se as duas transações ordenassem os registros por uma chave única em ordem crescente, e tentasse obter os locks nesta ordem , o primeiro processo a falhar ainda não teria conseguido pegar o bloqueio de nenhum registro de valor superior ao que o outro processo já tenha obtido, ele apenas esperaria um pouco e tentaria pegar o lock novamente, o que será possível assim que a transação que já tem o lock seja finalizada.

Conclusão

O arroz com feijão é apenas isso. Mas apenas isso já tira um monte de dor de cabeça de DBAs e Administradores de Sistemas, por que mesmo com todo o ferramental disponível, rastrear quem está segurando o lock de quem, em ambientes que passam de centenas de conexões, tome abrir monitor do DBAccess, do Protheus, do SGDB, e caçar a thread que está esperando uma liberação de registro, e a outra que está segurando o lock, não é uma tarefa fácil …

Desejo a todos TERABYTES de SUCESSO 😀

Referências

Entendendo e minimizando deadlocks

DEADLOCK. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Deadlock&oldid=51816852>. Acesso em: 15 abr. 2018.

 

 

 

 

Anúncios

Orientação a Objetos em AdvPL – Parte 01

Introdução

Para quem ainda não sabe o que é ou porquê a Orientação a Objetos, também conhecida por OOP (Object Oriented Programming), nesse post vamos dar uma passada mais a fundo neste tema, cobrindo algumas lacunas dos posts anteriores, para depois entrar nas recentes melhorias e implementações a serem disponibilizadas na próxima Build Oficial do TOTVS Application Server, chamada de “Lobo Guará”.

Programação Estruturada

A programação tradicional em AdvPL é conhecida por ser estruturada. Na prática, o sistema inteiro é criado a partir de funções ou sub-rotinas, usando estruturas de decisão (IF/CASE) e repetição (FOR/WHILE).

Normalmente as funções criadas declaram e usam variáveis de escopo local — vistas apenas dentro do corpo da função — e podem usar escopos mais abrangentes para compartilhar valores entre funções do mesmo fonte (STATIC), funções na pilha de chamadas (PRIVATE) e ainda entre qualquer função do sistema (PUBLIC), sem a necessidade de passagem de parâmetros.

A restrição de acesso à variáveis e funções normalmente é feita pelo escopo. As funções declaradas em um fone AdvPL por default são públicas — qualquer fonte pode chamar qualquer função — e podemos declarar a função como STATIC FUNCTION, restringindo sua visibilidade apenas para as demais funções presentes no mesmo código fonte onde a função foi declarada.

Programação Orientada a Objetos

Também presente no AdvPL, a orientação a objetos consiste em criar um agrupamento de dados e funções, chamada de “CLASSE”, onde cada variável da classe é chamada de “propriedade”, e cada função da classe é chamada de “método”. Basicamente, os métodos são funções aplicáveis apenas para a classe na qual foram declarados, eles podem ter parâmetros e retornos, e eles normalmente têm acesso às propriedades da classe. Em AdvPL, dentro de um método, você tem acesso às propriedades daquela instância da classe usando a variável especial “self”.

Como a classe em si é apenas uma definição, você precisa criar um contexto daquela classe para utilizá-la. Para isso, chamamos um método do tipo “construtor”, que pode ou não receber argumentos, e seu retorno é chamado de “instância” da classe, e essa instância é do tipo AdvPL “O” Objeto.

A classe por si só já representa um “container” de informação e manutenção de informação, mas ela permite ainda um recurso de reaproveitamento e organização de funcionalidades chamada HERANÇA. Isto permite que você crie uma classe que tenha acesso aos métodos e propriedades da classe pai, e permite que você implemente propriedades e métodos que atendam a sua necessidade, usando ou substituindo as implementações da classe pai, conforme a necessidade da sua especializacao.

Escopo de propriedades, métodos e heranças

Por hora, desde as primeiras versões do Protheus Server, a orientação a objetos do AdvPL não tinha restrição de escopo, isto é, tudo é público — propriedades e métodos da classe atual, e em caso de herança, também temos acesso a propriedades e métodos da classe pai informada na herança. Logo, ao criar uma instância de uma classe, qualquer propriedade ou método desta classe pode ser visto e acessado a partir da variável que armazena a instância da classe.

Classes em AdvPL na TDN

Existe uma árvore de documentos na TDN dedicada às classes AdvPL nativas do TOTVS Application Server, incluindo classes visuais e não-visuais, disponível no link Advpl – Classes, e na seção “Como Fazer – Classes AdvPL”, a documentação de como fazer classes em AdvPL sem e com herança.

Classes em AdvPL no Blog

Complementando a documentação da TDN, no próprio BLOG já tem quatro posts sobre Classes em AdvPL, com detalhes e exemplos, vide links abaixo:

https://siga0984.wordpress.com/2014/12/01/classes-em-advpl-parte-01/
https://siga0984.wordpress.com/2014/12/02/classes-em-advpl-parte-02/
https://siga0984.wordpress.com/2014/12/03/classes-em-advpl-parte-03/
https://siga0984.wordpress.com/2014/12/06/classes-em-advpl-parte-04/

TL++

Batizada de TL++ (Totvs Language Plus Plus), a criação de classes em AdvPL passa a ter os recursos de uma orientação a objetos tradicional, como modificadores de acesso, escopo de propriedades e métodos, até mesmo declaração de tipagem. Para não conflitar com as declarações já existentes, foi adotada uma nova extensão de arquivo fonte (.tlpp) para utilizar estes recursos, que estarão disponíveis apenas na liberação oficial na nova Build do TOTVS Application Server, chamada de “Lobo Guará”. Para ver esta documentação na TDN, acesse o link A Linguagem TL++

F.A.Q.

Qual a diferença entre Classe, Instância e Objeto ?

Classe = Definição da estrutura, dividida em propriedades e métodos.
Instância = Corresponde a uma área de memória alocada para uso da classe
Objeto = Tipo da variável que contêm uma instância de uma classe

Eu posso criar uma classe sem métodos ?

Sim, inclusive isso é conhecido por “estrutura”, isto é, apenas um agrupamento de dados em propriedades. Porém, no AdvPL, você precisa criar pelo menos um método New(), para servir de construtor da estrutura. Existe uma classe diferenciada, chamada WSSTRUCT, usada em WebServices. Esta classe não precisa de construtor, porém a criação de uma nova instância de uma estrutura de WebServices é criada pela função WSCLASSNEW(), que faz o papel do construtor, porém sem receber parâmetros — e neste caso todas as propriedades da estrutura são inicializados com “NIL”.

Eu posso passar uma instancia de uma classe como parâmetro para um JOB ou RPC ?

Não, não pode. Uma instância da classe está internamente amarrada ao processo que criou a instância. Os tipos AdvPL “O” Objeto e “B” CodeBlock têm esta característica restritiva. O que você pode fazer é, por exemplo, criar uma nova instancia da classe, passar as propriedades da instância encapsulado em uma string ou array, e reatribuir as propriedades na nova instância. Neste caso, você terá uma instância “clone” da instância do processo original, e não uma “referência” dela.

Existe certo ou errado entre criar funções ou classes ?

Cada paradigma foi criado para atender a uma necessidade. É mais simples e intuitivo programar de forma estruturada, funções genéricas que não dependem diretamente de contexto ou não precisam ter o nível de controle oferecido pela Orientação a Objetos não precisam necessariamente serem feitos usando OOP.

Quando optamos por usar Orientação a Objetos, precisamos ter em mente as responsavilidades de cada parte do código, tempo de vida da instância, necessidade ou não da existência de propriedades, necessidade ou não de uso de herança.

Normalmente os dois paradigmas usam-se mutuamente. Isto é, voce cria uma função com estruturas e controles, que cria por sua vez instâncias de classes, e as manipula ou chama sua execução para obter um resultado, onde os próprios métodos são funções estruturadas. Existem várias formas de se fazer alguma coisa, a melhor é aquela que atende a sua necessidade hoje, e pode ser expandida facilmente para atender uma necessidade maior amanhã.

Para não criarmos monstros, seja programando estruturalmente, ou orientado a objeto, é importante deixar claro o papel de cada função, classe ou método. A coisa começa a ficar confusa e sem sentido quando uma classe resolve fazer mais coisas do que ela foi originalmente projetada, ou você começa a engordar a classe com métodos especialistas desnecessários.

Conclusão

Pra assimilar o conceito da utiliação de Orientação a Objetos, o melhor a fazer é colocar a mão na massa e implementar uma funcionalidade que assim a utilize, e procurar basear-se em bons exemplos de uso.

Desejo a todos TERABYTES de sucesso !!!

Referências

TOTVS – Central de Atendimento – AppServer Lobo Guará

PROGRAMAÇÃO ESTRUTURADA. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Programa%C3%A7%C3%A3o_estruturada&oldid=52590881>. Acesso em: 6 jul. 2018.

ORIENTAÇÃO A OBJETOS. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Orienta%C3%A7%C3%A3o_a_objetos&oldid=52638985>. Acesso em: 11 jul. 2018.

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

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 😉