Arquivo binário em array de bytes

Bom dia,

preciso gerar arrays de bytes de arquivos pdf para envio para outros serviços, alguém já fez?

Até o momento estou usando memoread para pegar o arquivo e tentando converter os caracteres para byte sem sucesso.

cData = memoread("/DANFES_DCP/" + "FILE.pdf")

For nX:= 1 to Len(cData)

aadd(aBytes, asc(SubStr(cData, nX, 1)))

Next

Dessa forma ele faz o trabalho, porém o resultado não é o correto pois convertendo em outra linguagem o arquivo sai diferente do original.

Att.

Bom dia Vinicius, o serviço que vai receber o arquivo, também é seu ou é de terceiros?

Temos os 2 casos Daniel

Recomendo você fazer isso, transformando o arquivo para base64 com a função Encode64, enviando no header essa informação de encoded, esse tipo de leitura com ASC não vai funcionar.

Base64 funciona tranquilo mesmo, agora do outro jeito não tem como mesmo? tentei com memoread e fread tenho o mesmo resultado errado.

Arquivos PDF tem vários tipos de caracteres, porém caso exista um caractere \0 (NUL), você terá problemas, pois o binário do Protheus é feito em C++, logo ele encara esse caractere como final de string.

Vinicius,

Como eu falei, o binário acaba tendo problemas com certos textos, veja esse exemplo:


#include "protheus.ch"
#include "fileio.ch"

//-------------------------------------------------------------------
/*/{Protheus.doc} u_LenStr
Função de exemplo do funcionamento do MemoRead e Len em comparação
a leitura do arquivo, byte a byte

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
function u_LenStr()
local cPDF as char
local cData as char
local nLength as numeric
local nHandle as numeric

cPDF := "mypdf.pdf"

cData := MemoRead(cPDF)

nHandle := FOpen(cPDF)
nLength := FSeek(nHandle, 0, FS_END)

FClose(nHandle)

//Veja que a diferença pode ser bem grande, o teste foi feito com um PDF de cerca de 580Kb
ConOut("Tamanho lento a string: " + cValToChar(Len(cData)))
ConOut("Tamanho lento o arquivo: " + cValToChar(nLength))

return

Esse teste foi executado com um arquivo que tem mais de 580Kb e mais que 600000 caracteres, porém o appserver me retorna que o tamanho da string é de 65535 na função Len, gerando diversas inconsistências.


Após muitos testes, consegui chegar a um resultado equivalente ao Python.

Vamos, lá no Python eu criei o seguinte código:


with open("mypdf.pdf", "rb") as f, open("python-bytes.txt", "w") as ob, open("python-bytes-zero.txt", "w") as obz:
    numbers = [int(w) for line in f.readlines() for w in line]
    ob.write("".join([str(n) for n in numbers]))
    obz.write("".join([str(n).zfill(3) for n in numbers]))

Então, eu abro o arquivo PDF e gero o array de bytes, depois escrevo os mesmo em um arquivo para futura comparação com os resultados do ADVPL.

Como eu citei, a função Len acaba não tendo o retorno correto, então o que podemos fazer é ler o arquivo byte a byte com a FRead:


//-------------------------------------------------------------------
/*/{Protheus.doc} u_testBytesFile
Função para testes das demais funções de conversão de arquivo para
array (byte e binário)

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
function u_testBytesFile()
local cFile as char
local aBytes as array
local aBinary as array
local aBytesZeros as array

cFile := "mypdf.pdf"

aBytes := u_file2ArraryByte(cFile)
aBinary := u_file2BinaryArray(cFile)
aBytesZeros := u_file2ByteArray(cFile)

writeFile("advpl-bytes.txt", aBytes)
writeFile("advpl-binary.txt", aBinary)
writeFile("advpl-bytes-zero.txt", aBytesZeros)

cleanArray(@aBytes)
cleanArray(@aBinary)
cleanArray(@aBytesZeros)

return

//-------------------------------------------------------------------
/*/{Protheus.doc} writeFile
Cria um arquivo com base no nome e array de valores recebido

@param cFile Arquivo que será criado
@param aValues Array contendo os dados para a escrita

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
static function writeFile(cFile as char, aValues as array)
local nHandle as numeric

nHandle := FCreate(cFile)
AEval(aValues,{|cValue| FWrite(nHandle, cValue)})
FClose(nHandle)

return

//-------------------------------------------------------------------
/*/{Protheus.doc} cleanArray
Limpa o conteúdo do array recebido

@param aArray Array que será limpo

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
static function cleanArray(aArray as array)
aSize(aArray, 0)
aArray := nil
return

//-------------------------------------------------------------------
/*/{Protheus.doc} u_file2ArraryByte
Converte o arquivo recebido para um array de bytes

@param cFile Arquivo que será convertido para o array de bytes

@param aBytes Array contendo os dados em bytes

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
function u_file2ArraryByte(cFile as char) as array
local cByte as char
local aBytes as array
local nLoop as numeric
local nLength as numeric
local nHandle as numeric

nHandle := openFileSize(cFile, @nLength)
aBytes := {}

for nLoop := 1 to nLength
    cByte := FReadStr(nHandle, 1)
    cByte := cValToChar(Asc(cByte))

    aAdd(aBytes, cByte)
next

FClose(nHandle)

return aBytes

//-------------------------------------------------------------------
/*/{Protheus.doc} u_file2ByteArray
Converte o arquivo recebido para um array de bytes

@param cFile Arquivo que será convertido para o array de bytes com zeros

@param aBytes Array contendo os dados em bytes, com zeros a esquerda

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
function u_file2ByteArray(cFile as char) as array
local cByte as char
local aBytes as array
local nLoop as numeric
local nLength as numeric
local nHandle as numeric

nHandle := openFileSize(cFile, @nLength)
aBytes := {}

for nLoop := 1 to nLength
    cByte := FReadStr(nHandle, 1)
    cByte := StrZero(Asc(cByte), 3)

    aAdd(aBytes, cByte)
next

FClose(nHandle)

return aBytes

//-------------------------------------------------------------------
/*/{Protheus.doc} u_file2BinaryArray
Converte o arquivo recebido para um array de binários

@param cFile Arquivo que será convertido para o array de binários

@return aBynary Array contendo os dados em valores binários

@author Daniel Mendes
@since 21/07/2020
@version 1.0
/*/
//-------------------------------------------------------------------
function u_file2BinaryArray(cFile as char) as array
local cBinary as char
local aBynary as array
local nLoop as numeric
local nLength as numeric
local nHandle as numeric

nHandle := openFileSize(cFile, @nLength)
aBynary := {}

for nLoop := 1 to nLength
    cBinary := FReadStr(nHandle, 1)
    cBinary := Bin2Str(cBinary)
    cBinary := StrTran(cBinary, " ", "0")
    cBinary := StrTran(cBinary, "x", "1")

    aAdd(aBynary, cBinary)
next

FClose(nHandle)

return aBynary

//-------------------------------------------------------------------
/*{Protheus.doc} openFileSize
Função auxiliar para abrir o arquivo e descobrir o seu tamanho

@param cFile Arquivo que será aberto
@param nLength Tamanho do arquivo [Referência]

@param nHandle Handle do arquivo aberto

@author Daniel Mendes
@since 21/07/2020
@version 1.0
*/
//-------------------------------------------------------------------
static function openFileSize(cFile as char, nLength as numeric) as numeric
local nHandle as numeric

nHandle := FOpen(cFile, FO_READ+FO_SHARED)
nLength := FSeek(nHandle, 0, FS_END)

FSeek(nHandle, 0)
return nHandle

As principais funções do exemplo são:

  • u_file2ArraryByte - Converte o arquivo para bytes sem qualquer tratamento
    • u_file2ByteArray - Converte o arquivo para bytes tendo sempre três caracteres, criei esse exemplo por conta de vários exemplos e conversores online que trabalham assim
    • u_file2BinaryArray - Converte o arquivo para binários, apenas um outro exemplo de como pode ser tratado

Como comentei, fiz os testes me baseando em Python e tiveram resultados satisfatórios, mas... A performance ficou baixíssima, apesar dos exemplos, recomendo que você trabalhe com base64 que é uma função em C++ com uma performance muito superior, já tendo sido homologada e utilizada em diversos projetos.

Beleza Daniel, vou tentar mudar o requisito do projeto, acho mais seguro.