Technical Article

Preservar macros VBA e ligações externas quando o código Delphi reescreve um livro de cálculo

Considere uma tarefa que quase nada faz: abrir um livro mensal, escrever a data de hoje numa célula e guardá-lo de volta. Execute isso através de um serviço vezes suficientes e surgirá uma reclamação de qualquer forma. As macros desapareceram, ou as taxas de câmbio associadas apresentam agora #REF!, e a equipa de operações está convicta de que o seu código as apagou. Não apagou nada. O que normalmente aconteceu foi que um livro com macros ativas foi guardado sob um nome simples com extensão .xlsx, e o Excel obedeceu às regras de content-type da norma ECMA-376: um pacote cujo content-type declare a ausência de VBA não pode carregar um projeto VBA, independentemente de os bytes estarem lá presentes. O ficheiro não se corrompeu. Foi sim renomeado para um estado no qual o Excel é obrigado a ignorar parte dele.

As macros e as ligações a livros de cálculo externos são os dois elementos que a automação perde com maior regularidade, pela mesma razão subjacente. Ambos residem fora da grelha de células com que o código de edição realmente contacta, pelo que um código que raciocine em termos de linhas e colunas irá descartá-los sem nunca emitir uma eliminação. O HotXLS é uma biblioteca nativa em Delphi e C++Builder que lê e escreve XLS e XLSX sem o Excel instalado, tratando ambos os recursos como payloads que transporta deliberadamente, e não como dados que simplesmente copia por acaso. Apresentamos a seguir o que cada um deles necessita do seu caminho de gravação e onde as garantias terminam.

Por que razão estes dois recursos se comportam de forma diferente sob uma reescrita

Um projeto VBA é um binário opaco único. Num pacote OOXML, é o ficheiro vbaProject.bin; num ficheiro legacy BIFF, é um armazenamento OLE. Existem exatamente duas formas de o perder: o escritor nunca o copiar para o output, ou o output receber um tipo de ficheiro que o proíba. Qualquer uma das falhas é total e silenciosa. O projeto está presente ou não está.

Uma ligação externa não é um blob. É sim um pequeno grafo de relações: um caminho ou URL de destino a apontar para outro livro de cálculo, a lista de nomes de folhas que esse destino expõe e um cache opcional dos valores vistos pela última vez nessas folhas, para que o Excel possa apresentar algo quando o destino estiver offline. Estas três partes têm ciclos de vida diferentes sob uma reescrita, e uma biblioteca pode preservar fielmente algumas enquanto descarta silenciosamente outras. Essa assimetria é o aspeto que vale a pena detalhar com precisão, porque nada no código de edição de células o revelará.

Transportar um projeto VBA através de uma reescrita XLSX

No lado do XLSX, o TXLSXWorkbook mantém o payload das macros intacto. A propriedade VbaProject retém os bytes brutos de vbaProject.bin dentro de uma AnsiString, sendo uma string vazia a forma de o modelo indicar que não existem macros. Em seu redor posicionam-se três operações: HasVbaProject responde se um projeto está presente, ClearVbaProject remove-o intencionalmente e LoadVbaProjectFromFile injeta um projeto extraído de um modelo. Esta última chamada vale mais do que parece. Permite que os livros gerados recebam um projeto de macros padrão sem arrastar um ficheiro de modelo completo pelo pipeline.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 'Refreshed ' + DateTimeToStr(Now);

    Book.LoadVbaProjectFromFile('macros\vbaProject.bin');
    if not Book.HasVbaProject then
      raise Exception.Create('VBA payload failed to load');

    // The .xlsm extension is not cosmetic: it selects the
    // macro-enabled content type inside the package.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

A linha de gravação é onde reside todo o problema. Um livro que contenha um projeto VBA tem de ser escrito com semântica de macros ativas, e o HotXLS aplica-as quando o nome de destino termina em .xlsm. Forneça-lhe em alternativa a extensão .xlsx e o Excel recusará as macros, mesmo que os bytes estejam fisicamente presentes no pacote e pudessem ser desserializados normalmente. A extensão não é um elemento decorativo; ela seleciona o content-type que indica ao Excel que é permitida a existência de um projeto VBA. Na maioria das vezes, apenas necessita de transportar o payload. Quando precisar de ler o seu conteúdo, por exemplo para listar nomes de módulos para um relatório de auditoria, o ParsedVBAProject expõe um modelo de módulo analisado, enquanto o VbaProject se mantém como os bytes originais intocados.

Reutilizar macros de livros legados XLS

A fachada BIFF espelha esse conjunto de ferramentas com um passo adicional. O HasVBAProject examina um ficheiro carregado, o SaveVBAProjectToFile escreve o armazenamento do projeto no disco e o LoadVBAProjectFromFile lê um de volta para outro livro. O desvio através de um ficheiro torna simples uma tarefa de modernização comum: extrair as macros de um modelo da era de 2003 e inseri-las num output XLS recém-gerado, sem a necessidade de um modelo original em tempo de execução.

var
  Src, Dst: IXLSWorkbook;   // interface references: no manual Free
begin
  Src := TXLSWorkbook.Create;
  if Src.Open('legacy-model.xls') <= 0 then
    raise Exception.Create('Cannot open legacy model');
  if Src.HasVBAProject then
    Src.SaveVBAProjectToFile('extracted-vba.bin');

  Dst := TXLSWorkbook.Create;
  Dst.Sheets.Add.Name := 'Report2026';
  Dst.LoadVBAProjectFromFile('extracted-vba.bin');
  Dst.SaveAs('report-with-macros.xls');
end;

O modelo de memória é a armadilha neste caso, e funciona de forma oposta à classe XLSX. O TXLSWorkbook é mantido através da interface IXLSWorkbook controlada por contagem de referências, pelo que nunca o liberta manualmente; já o TXLSXWorkbook do XLSX é um objeto simples que deve envolver em try..finally e libertar. Misture as duas convenções numa única unit e seguir-se-ão falhas por libertação dupla (double-free). Outro limite que vale a pena respeitar: mantenha a extração e a injeção dentro de um único formato de ficheiro. O armazenamento do projeto BIFF e o vbaProject.bin do OOXML são primos, não o mesmo contentor, e um pipeline que tenha de emitir macros em ambos os formatos deve manter um modelo de macros separado para cada um.

Ligações externas: o mapa sobrevive, os valores em cache não

Para livros XLSX, o HotXLS expõe ligações externas através da coleção ExternalLinks. Cada TXLSXExternalLink contém um Target, o caminho ou URL do livro remoto, e uma lista SheetNames com o nome das folhas que referencia. Ambos sobrevivem intactos a um ciclo de abertura e gravação, e também pode construir uma ligação de raiz:

var
  Link: TXLSXExternalLink;
begin
  Link := Book.ExternalLinks.Add('\\fileserver\finance\fx-rates-2026.xlsx');
  Link.SheetNames.Add('FX');

  if Book.ExternalLinks.Count > 0 then
    Writeln(Format('%d external link(s): delivery requires reachable targets',
      [Book.ExternalLinks.Count]));
end;

O limite situa-se um nível mais profundo do que a lista de destinos. O HotXLS realiza o round-trip do mapa de ligações, ou seja, do destino e dos nomes das folhas, mas não analisa nem reescreve os valores de células em cache que o OOXML guarda no elemento sheetDataSet da ligação. Essa cache é o que permite ao Excel apresentar um número conhecido quando o ficheiro de origem está offline, e um livro gerado é entregue sem ela. A consequência recai sobre o destinatário, não sobre si. Abra um ficheiro onde o destino é inacessível, um portátil fora da VPN ou uma partilha de rede renomeada, e as fórmulas que dependem da ligação resultarão em #REF! ou ficarão bloqueadas sob um aviso de atualização. Daqui decorrem duas regras: não prometa que um livro gerado exibirá os seus valores ligados externamente em modo offline; e interprete um ExternalLinks.Count diferente de zero como uma condição prévia de entrega e não como uma funcionalidade: cada destino tem de estar acessível a partir do local onde o ficheiro será efetivamente aberto.

O que o leitor XLS preserva byte a byte

Para estruturas que não modela, o lado do BIFF tem uma resposta diferente: deixa-as exatamente como foram encontradas. Pools de pivot e vistas pivot (a família de registos SX*), definições de QueryTable, ligações de dados externos, vistas personalizadas, imagens de cabeçalho e registos de temas passam todos por um ciclo de abertura e gravação como blocos de registos brutos, não analisados e inalterados. As próprias referências externas efetuam o round-trip através dos registos EXTERNSHEET e SupBook subjacentes. Não existe uma API de criação tipada para as mesmas no lado XLS, mas uma ligação existente sobrevive à edição sem ser alterada.

A preservação byte a byte é uma garantia real com um limite estrito. Uma vez que nada lê uma estrutura preservada, as suas edições não a conseguem corromper. Pela mesma razão, nada a atualiza. Insira linhas numa região para a qual uma cache pivot preservada ou uma query table aponta, e a estrutura manterá as suas coordenadas originais enquanto os dados por baixo se deslocam. O ficheiro continua a ser um XML ou BIFF válido; no entanto, o sentido desfasou-se silenciosamente, e nenhum erro é acionado para o alertar. O layout defensivo consiste em manter as edições geradas em folhas que não contenham estruturas preservadas, que é a mesma disciplina que protege folhas bloqueadas e configuradas para impressão no nosso artigo sobre proteção de folhas de cálculo e configuração de página.

Verificar o ficheiro que realmente escreveu

Ambos os modos de falha são silenciosos no momento da escrita, pelo que a validação que realmente importa é feita ao reabrir o output, e não ao confiar no código que o produziu. Três verificações cobrem quase tudo. Reabra o ficheiro e confirme se o HasVbaProject continua a devolver verdadeiro sempre que eras esperadas macros, o que deteta um payload descartado e uma extensão errada num único teste. Leia o ExternalLinks.Count e compare-o com a contagem anterior à reescrita. Em seguida, abra o ficheiro uma vez no Excel com as macros desativadas, uma vez que a validação de content-type do Excel é mais rigorosa do que a de qualquer biblioteca, e o Excel é o programa pelo qual os seus clientes irão avaliar o ficheiro.

Nada disto exige uma análise completa na entrada. Quando os livros chegam em grande volume e apenas necessita de triar quais transportam conteúdo regulado, a sondagem leve apresentada no nosso artigo sobre listagem de folhas e inspeção leve de livros de cálculo permite encaminhar ficheiros com macros e ligações para um pipeline mais rigoroso antes de correr a primeira reescrita.

Algumas questões surgem com frequência suficiente para justificar uma resposta direta. O HotXLS nunca executa as macros que preserva: não existe um runtime VBA na biblioteca, mas apenas os mecanismos para armazenar, copiar, extrair e injetar o projeto como dados. Num servidor, esta é uma propriedade de segurança digna de menção, uma vez que uma macro hostil que passe pelo pipeline permanece inerte até que um Excel de desktop abra o ficheiro e um utilizador ative o conteúdo. Converter um .xlsm para .xlsx preservando as macros não é possível, e essa é uma regra do formato e não uma limitação da biblioteca: o tipo de conteúdo .xlsx declara um livro livre de macros, pelo que as únicas opções honestas são manter a extensão .xlsm ou chamar ClearVbaProject e entregar um ficheiro que genuinamente não tenha nenhuma. A mudança silenciosa de nome é a opção que não satisfaz ninguém. E quando as células ligadas apresentam #REF! após uma reescrita, a causa é a ausência da cache de valores discutida acima: o novo ficheiro transporta o destino mas não os números em cache, pelo que o Excel tem de resolver a origem no momento da abertura, e um caminho inacessível ou relativo ao ambiente inviabiliza-o. Garanta que o destino está acessível ou escreva os valores calculados nas células antes da entrega, eliminando totalmente a dependência.

Editar livros de cálculo de terceiros é maioritariamente um trabalho de preservação de elementos que não escreveu e não compreende totalmente. Os recursos de round-trip de VBA e ligações externas descritos aqui acompanham o Componente HotXLS para Delphi e C++Builder, juntamente com as propriedades de auditoria que permitem detetar conteúdos regulados no instante em que um ficheiro chega.