Seu primeiro Webservice REST no ERP Protheus

Desmistificando o MVC no AdvPL

  – O que é REST?

A web é amplamente utilizada e reconhecida principalmente por sua arquitetura robusta, escalável e tolerante a falhas. Quem sustenta esses fatores e lhe dá todo este poder é o protocolo HTTP (o protocolo HTTP é utilizado, em regra, quando se deseja evitar que a informação transmitida entre o cliente e o servidor seja visualizada por terceiros, como, por exemplo, no caso de compras online.). Atualmente, muitas vezes necessitamos integrar aplicações em ambientes totalmente diferentes e os WebServices são uma das maneiras mais comuns e fáceis de integrar os diferentes sistemas. Este post mostrará um pouco de um modelo de WebService chamado REST.

Representational State Transfer ou somente REST, é cada vez mais usado como alternativa ao “já antigo” SOAP onde que a principal crítica a esta é a burocracia, algo que REST possui em uma escala muito menor.
REST é baseado no design do protocolo HTTP, que já possui diversos mecanismos…

Ver o post original 1.454 mais palavras

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 04

Introdução

No post anterior, preparamos um banco MySQL 5.5 e a UnixODBC na VM do Ubuntu 64. Agora, vamos instalar um DBAccess 64 bits no Linux e configurá-lo para usar o MySQL.

Instalação

Basicamente, precisamos de um arquivo contendo a distribuição 64 bits Linux do DBAccess. No exemplo de instalação, usei a versão disponível no portal da Totvs ( 16-03-15-DBACCESS_LINUX64_20141119.TAR.GZ )

Após subir a VM, usamos um WINSCP para criar dentro na estrutura de diretórios proposta para a aplicação (Post – Parte 01), uma pasta para a build do DBAccess que vamos instalar. A partir da pasta /totvs, criamos a pasta /dbaccess, e dentro dela a pasta 20141119 (referente a build em uso), e copiamos o arquivo mencionado para ela.

Usando um Putty, acessamos a VM pelo terminal e descompactamos o arquivo, usando os comandos:

cd /totvs/dbaccess/20141119/
tar zxvf 16-03-15-DBACCESS_LINUX64_20141119.TAR.GZ

Feito isso, vamos subir o DBAccess em modo “console”. A distribuição Linux 64 do DBAccess possui 2 executáveis : dbaccess64opt (Build Release) e dbaccess64dbg (Build Debug). A pasta “multi” significa que a versão de DBAccess dentro dela foi feita para conectar com múltiplos bancos de dados. Para subir esta versão em modo console, usamos os seguintes comandos :

cd multi
export LD_LIBRARY_PATH=./
./dbaccess64opt

DBAccess Linux64

Configurar conexão do MySQL no DBAccess

Você pode utilizar um DBAccess Monitor instalado no Windows, por exemplo, para conectar no DBAccess Server da máquina Linux, e proceder com a configuração da conexão com o MySQL.

Ao entrar na aba MySQL, e clicar em “Novo”, crie uma configuração com o mesmo nome de DSN no Linux que criamos no post anterior – no caso do exemplo, envp11mysql. Preencha os campos “usuário” e “senha” com o usuário e senha que criamos no MySQL durante a criação do banco de dados envp11mysql.

Testar a conexão

Usando o próprio DBAccess Monitor, na pasta “Assistentes”, usamos o recurso de Validação de Conexão, informamos o banco MySQL, depois informamos o nome da conexão (envp11mysql), e devemos receber a mensagem abaixo:

DBAccess Falha Conexão

Para ver o que houve, vamos olhar o console do DBAccess no terminal do Linux. Deve haver uma mensagem de erro parecida com a mensagem abaixo:

Begin TopClient Thread (3256297216,192.168.1.63,MYSQL/envp11mysql,DBMonitor,julio.wittwer,NOTE-AUTOMAN)
BEGINLOG]
Connection [MYSQL/envp11mysql] could not load database client library [libmyodbc3_r.so]
[ENDLOG]
[BEGINLOG]
libmyodbc3_r.so: cannot open shared object file: No such file or directory
[ENDLOG]
Exit TopClient Thread (3256297216) [ERROR -35]

O DBAccess está procurando uma lib client antiga do MySQL, para tentar conectar com ele, e não vai encontrá-la. Após instalar o DBAccess e configurar a conexão, precisamos editar o arquivo dbaccess.ini, e informar ao DBAccess que ele deve carregar a UnixODBC para acessar o MySQL. Para isso, usamos a chave clientlibrary na seção [MYSQL].

Primeiro, finalize o DBACcess, usando Control+C no console. Agora, vamos localizar o driver da UnixODBC. Normalmente ele está em alguma pasta da lib gnu Linux, em /usr/lib. Para verificar onde exatamente ela está, usamos o comando abaixo:

find /usr/lib -name 'libodbc.so*'

No meu ambiente, foram localizados os seguintes arquivos:

/usr/lib/x86_64-linux-gnu/libodbc.so.1.0.0
/usr/lib/x86_64-linux-gnu/libodbc.so.1

No dbaccess.ini, dentro da seção [mysql], colocamos a chave abaixo:

clientlibrary=/usr/lib/x86_64-linux-gnu/libodbc.so.1

DBACcess Ini

Após salvar o arquivo e subir o DBAccess novamente, vamos testar a conexão usando o assistente do DBAccess Monitor. E, se tudo estiver de acordo, devemos visualizar a mensagem abaixo:

DBAccess Conexão Ok

Subindo o DBAccess em modo “daemon”

Haja visto que o Banco de Dados MYSQL já está no ar, e o DBAccess está funcionando, podemos subir ele em modo “daemon”, sem deixar ele preso ao terminal. Para isso, usando o próprio terminal atual, finalizamos o DBAccess em modo console usando Control+C, e subimos ele em modo “daemon” usando o seguinte comando:

nohup ./dbaccess64opt -daemon

Para verificar se o processo do DBAccess está realmente no ar, podemos rodar uma instrução do Linux para listar os processos, filtrando o resultado por aqueles que contém a string “dbaccess”, usando o comando abaixo:

ps -ef | grep dbaccess

O retorno deste comando deve ser algo parecido com:

siga0984   1568      1  0 08:54 pts/0    00:00:00 ./dbaccess64opt daemon
siga0984   1591   1410  0 08:58 pts/0    00:00:00 grep --color=auto dbaccess

O que nos interessa é a primeira linha, pois informa o número do processo pelo qual o sistema operacional subiu o DBAccess64 – no nosso exemplo, 1568.

Para baixar o processo do DBAccess de forma controlada, usamos um terminal do Linux, e usamos o comando kill, informando como parâmetro o número do processo a ser finalizado. Por exemplo:

kill 1568

Para verificar se o processo está no ar, podemos usar novamente o comando “ps”. As mesmas regras se aplicam ao processo do Protheus Server. Ele pode ser colocado em modo “daemon” usando a mesma sintaxe, e verificado da mesma forma, usando o comando “ps”.

Conclusão

Com o DBAccess instalado e funcionando na própria máquina Linux, basta editar o arquivo appserver.ini da instalação do Protheus Server feita no segundo post desta sequência, para apontar para o banco MYSQL em localhost, para o alias “envp11mysql”, e agora temos um ambiente mínimo e operacional do Protheus 11 em uma máquina Linux 64.

Entre uma distribuição e outra de Linux, pode haver mudança nos comandos, a configuração de alguns itens pode ser manual e dar um pouco mais de trabalho, algumas distribuições vem com Firewall habilitado e requer mais passos de setup para abrir as exceções, alguns arquivos podem estar em outro lugar, ou ter um nome diferenciado, o gerenciador de pacotes é diferente, e assim por diante.

Estes primeiros posts foram apenas para “abrir o apetite”, temos muito a abordar ainda sobre isso nos próximos posts deste assunto 😀

Enquanto isso, agradeço novamente a audiência, e desejo a todos TERABYTES de SUCESSO 😉

 

Protheus no Linux – Parte 03

Introdução

No post anterior, instalamos na VM Ubuntu um Protheus Server, instância única, usando um c-Tree Server DLL (ou BoundServer). Agora, vamos instalar um banco de dados MYSQL e a UnixODBC nesta mesma VM.

Instalando

Após iniciar a VM do Ubuntu Linux — montada no primeiro post –, executamos os comandos abaixo, para instalar o MYSQL e a UNIXODBC, respectivamente:

sudo apt-get install mysql-server mysql-client
sudo apt-get install libmyodbc unixodbc-bin unixodbc

Com estes comandos, nesta versão do Sistema Operacional, o MySQL 5.5 será instalado. Durante a instalação, será perguntada uma senha do usuário “root” do MySQL. Insira uma senha e guarde-a, ela será necessária nas etapas posteriores.

Criando a base no MySQL

Após a instalação, o banco MYSQL já deve estar no ar, mas sem nenhuma base de dados. Utilize o comando “mysql” para acessar o interpretador de comandos do MySql, com a sintaxe abaixo:

mysql -u root -p

Uma vez dentro do interpretador de comandos do MySql, execute os comandos abaixo para criar a sua base de dados. Troque o conteúdo de ‘usuariodebanco’ para um nome de usuário que você queira criar no banco de dados para ter acesso a essa base. Você pode dar o mesmo nome do usuário que você criou para o sistema operacional. E, no lugar de ‘senha’, coloque a senha que você quer atribuir a este usuário, para ele acessar o Banco de Dados.

create database envp11mysql;
grant all on envp11mysql.* to 'usuariodebanco' identified by 'senha';

Mysql Create DB

Configurando a UnixODBC

Para não acessarmos diretamente a .so ( shared object library ) do banco de dados, vamos configurar a UnixODBC no Linux para o Mysql. Primeiro, vamos entrar em “root” mode no Linux, com o comando abaixo:

sudo su

Agora, vamos ver exatamente onde foram instaladas as libs ODBC do MySQL, usando o comando abaixo:

find / -name 'lib*odbc*.so'

O resultado esperado deve ser bem próximo de:

/usr/lib/x86_64-linux-gnu/odbc/libodbcdrvcfg2S.so
/usr/lib/x86_64-linux-gnu/odbc/libodbcnnS.so
/usr/lib/x86_64-linux-gnu/odbc/libodbcdrvcfg1S.so
/usr/lib/x86_64-linux-gnu/odbc/libodbctxtS.so
usr/lib/x86_64-linux-gnu/odbc/liboraodbcS.so
/usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so
/usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so
/usr/lib/x86_64-linux-gnu/odbc/liboplodbcS.so
/usr/lib/x86_64-linux-gnu/odbc/libodbcminiS.so

Os arquivos que nos interessam são os dois em destaque:

libmyodbc.so = MySQL Driver API 
libodbcmyS.so = MySQL Driver Setup

Agora, vamos criar a configuração de instalação da UnixODBC. Usando o editor de arquivos texto no Linux, crie o arquivo odbcinst.ini na pasta /etc/

sudo vi /etc/odbcinst.ini

O conteúdo do arquivo deve ser o seguinte:

[odbc_mysql]
Description     = ODBC for MySQL
Driver          = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
Setup           = /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so
UsageCount      = 1

Agora, vamos ver a onde está a configuração de Sockets do MySQL, usando o comando abaixo:

mysqladmin -u root -p version

O resultado deve ser parecido com este aqui:

mysqladmin  Ver 8.42 Distrib 5.5.49, for debian-linux-gnu on x86_64
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Server version          5.5.49-0ubuntu0.14.04.1
Protocol version        10
Connection              Localhost via UNIX socket
UNIX socket             /var/run/mysqld/mysqld.sock
Uptime:                 23 min 17 sec


Threads: 1  Questions: 583  Slow queries: 0  Opens: 189  
Flush tables: 1  Open tables: 41  Queries per second avg: 0.417

O que nos interessa é a informação em negrito: O caminho do Unix Socket em uso pelo Banco de Dados. Agora, baseado no que já temos até agora, vamos criar a configuração de ODBC efetivamente, apontando para os drivers.

vi /etc/odbc.ini

Podemos partir do seguinte conteúdo:

[envp11mysql]
Description = DSN para Banco P11 no MySQL
Driver      = odbc_mysql
Server      = localhost
Port        = 3306
Socket      = /var/run/mysqld/mysqld.sock
Database    = envp11mysql
Option      = 3
ReadOnly    = No

Na prática, os nomes das seções nos arquivos de configuração somos nós que definimos. No arquivo odbcinst.ini, cada seção configura um driver de banco diferente. No arquivo odbc.ini, cada seção corresponde a uma entrada de DSN da Odbc, que usa um determinado driver.

Após criar e editar os arquivos, vamos efetivar o registro das informações na UnixODBC, inicialmente instalando o Driver que configuramos, usando o comando abaixo.

odbcinst -i -d -f /etc/odbcinst.ini

Agora, vamos instalar a nossa configuração de DSN como “System DSN”, usando o comando abaixo:

odbcinst -i -s -l -f /etc/odbc.ini

Agora, usando o comando abaixo, podemos consultar quais DSN de sistemas estão instaladas:

odbcinst -s -q

E, finalmente, podemos testar a conexão com o MySQL via UnixODBC, usando o comando abaixo, trocando “MYSQLUSER” pelo usuário que nós criamos para o banco envp11mysql , e “MYSQLUSERPASSWORD” trocando pela senha utilizada.

isql -v myodbc_mysql_dsn MYSQLUSER MYSQLUSERPASSWORD

O resultado esperado é :

 

+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL>

 

Para sair do interpretador de comandos SQL, use a instrução “quit”. Com isso, já temos o MySQL instalado, um banco criado, e a UnixODBC devidamente configurada.

Referências

“Kafee Talk – How to setup and configure MySQL with unixODBC under Ubuntu 14.04”. Disponível em <http://www.kaffeetalk.de/how-to-setup-and-configure-mysql-with-unixodbc-under-ubuntu-14-04/>. Acesso em 16 de Julho de 2016.

Conclusão

Sim, foi mais simples e mais rápido do que imaginávamos. E, com mais um ou dois passos, você configura o MySQL no Linux para aceitar conexões TCP remotas, e pode instalar uma ODBC no Windows, e usar o MYSQL no Linux — Basta editar o arquivo /etc/mysql/my.cnf , localizar a configuração bind-address, e trocar ela para 0.0.0.0 😉 Mas, o foco é usarmos o MySQL no Linux, com o DBAccess também no Linux. E esta etapa será abordada no próximo post dese assunto !!!

Agradeço a todos pela audiência, e lhes desejo TERABYTES DE SUCESSO 😀 

Protheus no Linux – Parte 02

Introdução

No post anterior, vimos um passo a passo da montagem de um ambiente mínimo de um servidor Linux, mais precisamente um Ubuntu Server 14 – 64 bits, conde já executamos algumas etapas de preparação para ele receber um Protheus Server. Neste post, vamos colocar um Protheus Server 11 64 bits nele !

Instalando o Protheus Server

Após iniciar sua VM, verificar o IP, e abrir um terminal ( Putty ), crie uma pasta a partir da pasta raiz do Linux, para armazenar seus ambientes e aplicações. No lugar de <usuario>, use o usuário que você criou na instalação do Linux.

sudo mkdir /totvs
sudo chown usuario /totvs
cd /totvs

Com estes comandos, criamos a pasta, e damos as permissões de “owner” (dono) da pasta ao seu usuário.

Vale lembrar de um ponto importante: todos os arquivos do Protheus no Linux, e inclusive estruturas de diretórios, por uma questão de convenção e compatibilidade estabelecidas no porte da aplicação para Linux, devem estar em letras minúsculas.

No nosso exemplo, vamos instalar apenas um Protheus 11 Server 64 bits. Para isso, eu proponho executar os comandos abaixo, para criar a seguinte estrutura de diretório:

mkdir /totvs/protheus11
mkdir /totvs/protheus11/envp11
mkdir /totvs/protheus11/bin_131227a

Para o exemplo acima, eu baixei apenas o Protheus 11 Server 64 em Linux, disponibilizado na seção de Download do portal da Totvs:

Arquivo    15-12-01-P11_APPSERVER_LINUX_X64.ZIP
Descrição  APPSERVER X64 700131227A  
Tamanho    59.6 MB

Primeiro, usando o WinSCP (SFTP), copie para a pasta ‘/totvs/protheus11/bin_131327a’ o arquivo “15-12-01-P11_APPSERVER_LINUX_X64.ZIP”.Após terminada a cópia, descompacte o arquivo usando o comando abaixo:

unzip 15-12-01-P11_APPSERVER_LINUX_X64.ZIP

A descompactação deve criar duas pastas, uma para os arquivos do Protheus Server , outra para o Smartclient Linux. Renomeie as pastas geradas para “appserver64” e “smartclient”, respectivamente, usando o comando “mv”, a partir da pasta /totvs/bin_131227a

mv appserverLinux_x64 appserver64
mv smartclientLinux smartclient

Entre na pasta appserver64, e descompacte todos os arquivos da pasta “appserver64”, usando os comandos abaixo:

cd appserver64
tar zxvf appsrvlinux_x64.tar.gz
tar zxvf printer_x64.tar.gz
tar zxvf libctreetmp_x64.tar.gz

Após descompactar, você pode remover todos estes arquivos com o comando:

rm -f *.tar.gz

Agora, vamos ajustar as propriedades de execução dos arquivos *.so do binário, usando o comando:

sudo chmod 777 *.so

Para o ambiente do ERP, Protheus 11, podemos criar toda a estrutura de pastas necessárias a partir da pasta /totvs/envp11, usando os comandos abaixo:

cd /totvs/protheus11/envp11
mkdir apo
mkdir data
mkdir system
mkdir systemload

Você pode pegar um repositório de objetos do portal, por exemplo o arquivo “16-06-14-BRA-EUA-PAR-URU-TTTP110.RPO”, bem como os demais arquivos necessários para criar um ambiente “do zero”, ou pode separar os arquivos necessários de seu ambiente Windows para montá-lo no Linux. O importante é que a versão do repositório do ERP deve ser a mesma versão dos arquivos de sistema (pasta ‘systemload‘).

Para um ambiente mínimo, você precisa da pasta “systemload” do seu ambiente Windows copiado na íntegra para o Linux, e na pasta “system” você precisa ter os arquivos com extensão XNU com as definições dos módulos do ERP, e de um arquivo “sigaadv.pss” do CD de instalação do ERP ou de um ambiente Windows. Este arquivo é usado como base para a criação do arquivo de senhas do ambiente, e parte do usuário “Admin”, sem senha (senha em branco). Se você já tem um ambiente Windows montado, você pode copiar o arquivo “sigapss.spf” deste ambiente para a pasta “system” do Linux.

Configurando o AppServer

Após copiar todos os arquivos necessários, vamos criar um arquivo de configuração para este ambiente no Protheus 11. Usando um editor no Windows ou no Linux, crie um arquivo chamado appserver.ini, com o seguinte conteúdo:

[general]
consolelog=1

[drivers]
active=tcp

[tcp]
type=tcpip
port=11010

[envp11]
sourcepath=/totvs/protheus11/envp11/apo
rootpath=/totvs/protheus11/envp11
startpath=/system/
rpodb=top
rpolanguage=por
rpoversion=110
localfiles=ctree

[dbaccess]
database=mysql
alias=envp11
server=localhost
port=7890

Este arquivo deve ser criado e/ou copiado para a pasta ‘/totvs/protheus11/bin_131227a/appserver64‘. Caso você já tenha um DBAccess com um banco de dados vazio configurado em uma máquina Windows, você pode apontar para ele. Esta sugestão de configuração já prevê o próximo post da sequência, onde vamos instalar um mysql e um DBAccess 64 bits no Linux, com unixodbc e tudo.

Nos testes atuais, eu subi um MSSQL Express na minha máquina Windows, usando um DBACcess Windows, e usei as seguintes configurações:

[dbaccess]
database=mssql
alias=envp11sql
server=192.168.1.63
port=7890

Para subir o Protheus Server em modo console no terminal Linux, entre na pasta /totvs/bin_131227a/appserver64, e use o comando:

./appsrvlinux

APPServer Linux 64

Agora, com todos os arquivos corretamente copiados para as devidas pastas no ambiente Linux, use um SmartClient da mesma Build, instalado na sua máquina Windows, edite o arquivo smartclient.ini, para criar uma nova conexão TCP para o Protheus Server no Linux, por exemplo:

[tcpubuntu14]
server=192.168.1.50
port=11010

Inicie seu SmartClient no Windows, utilizando os seguintes parâmetros:

Smartclient Win

Ao confirmar o início da aplicação, se tudo der certo, você deve ver uma tela rapidinha com uma mensagem “convertendo arquivo de senhas” e logo após uma tela de escolha de tema, parecida com a tela abaixo:

Smartclient Temas

As primeiras etapas de instalação do ERP, como por exemplo a criação do arquivo de Helps, é bem lenta no Linux. Quando eu digo que é lento, acredite em mim…

Isto é bem mais rápido, mesmo no Linux, quando usamos um c-Tree Server para controle dos dicionários. Também teremos um post exclusivo dedicado a esta etapa, onde inclusive vamos instalar o c-Tree Server nesta VM, e fazer os ajustes necessários no appserver.ini para passar a usá-lo.

Por padrão, os dicionários e arquivos locais de dados no Protheus Server usam o c-Tree. Como nesta instalação não foi instalado e configurado um c-Tree Server, o Protheus assume a utilização do driver local do c-Tree, chamado de “c-Tree Local”, que já vem distribuída junto com o binário do Protheus Server (arquivo libctreestd.so).

Usando o Protheus com o c-Tree BoundServer

Quando temos uma instância única de Protheus Server, podemos também fazer uma alteração simples e muito eficaz: Usar o “c-tree Server DLL” (ou c-Tree BoundServer) neste ambiente … É extremamente simples, mas você somente pode ter um único serviço de Protheus Server configurado para acessar este ambiente: Você edita o appserver.ini (sem que o Protheus Server esteja em execução), e na seção [general], acrescenta a chave:

ctreemode=boundserver

Ao subir novamente o Protheus Server, ele vai usar uma versão do c-Tree Server “embarcada”, que já vem junto do pacote de distribuição do Protheus Server, para ser o gerenciador dos arquivos, ao invés do c-Tree Local.

No meu ambiente de testes, a criação dos arquivos de help já tinha consumido mais de 20 minutos, e ainda não tinha concluído nem a metade … Derrubei o Protheus Server, que rodava em modo console, usando Control+C, e fiz a alteração acima.

Embora no momento de subir o Protheus Server, o c-Tree BoundServer tenha reclamado que faltava um arquivo de “Callback”, o serviço subiu no ar certinho. Utilizando o WINSCP, apaguei da pasta “system” o arquivo “sigahlp.hlp”, que não tinha sido criado completo, e entrei no ERP novamente usando o SmartClient no Windows.

Utilizando o c-Tree BoundServer, a atualização do arquivo de Help e a tela de entrada do ERP apareceram em menos de 30 segundos.

Após digitar usuário e senha, e na segunda tela confirmar a data do dia, e entrar no módulo configurador, na empresa 99 – Teste (criada automaticamente na entrada do sistema), é perguntado qual país de instalação do ERP. Escolha “Brasil” e prossiga. O resto do procedimento é padrão da instalação do produto: Após criar um ou dois dicionários, o ERP pergunta qual é a pasta de dados. Esta pergunta foi mantida por compatibilidade no instalador. Por compatibilidade, escolha a pasta “data”.Em poucos minutos a carga inicial é finalizada, e o menu do módulo configurador do ERP é mostrado na tela.

 

Observações sobre o c-Tree BoundServer

  • Pode ser usado para gerenciador dos dicionários no lugar do c-Tree Server tradicional, desde que apenas uma instância do Protheus Server acesse os arquivos do ambiente.
  • Possui licença subsidiada para até 64 conexões.
  • Por default, o c-Tree BoundServer distribuído junto dos executáveis do Protheus Server está configurado para não aceitar conexões TCP.
  • Existem mais detalhes a respeito, que serão abordados em posts dedicados a este assunto 😀

Conclusão

Para um serviço “stand-alone”, sem o SGDB na máquina Linux, já temos bastante coisa funcionando. Nos próximos posts a respeito, vamos ver como colocar um MYSQL nesta máquina Linux, junto com um DBAccess 64 bits, e conectar o Protheus Server neste banco.

Ainda não chegamos no ponto de usar mais de um serviço, ou mesmo balanceamento de carga … mas não se preocupem, chegaremos lá aos poucos 😀  Agradeço de novo a audiência de todos, e lhes desejo sempre TERABYTES de sucesso 😉

Até o breve próximo post, pessoal !!!