Arquivo binário em array de bytes

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.