Packages e o .fxd
package, o arquivo .fxd e a RTL como DLL compartilhada — o equivalente ao .bpl + .dcp do Delphi.
A ideia#
Um package é a forma de um projeto que gera uma DLL e um arquivo .fxd do lado. A DLL tem o código, e só. O .fxd leva tudo o que o compilador precisa saber para compilar um cliente e que não cabe num binário ligável.
| Forma | Sai | Exporta | O que o cliente vê |
|---|---|---|---|
program | .exe | — | — |
library | .dll | só o que está no exports; o resto o otimizador poda | nada: escreve-se external ... name ... à mão |
package | .dll + .fxd | tudo o que é public: rotinas, classes, records, interfaces, constantes, genéricos | o .fxd; o compilador resolve tudo sozinho |
O que o .fxd carrega#
- o layout dos tipos:
SizeOf, alinhamento, deslocamento de cada campo, o que é gerenciado, a cadeia de ancestrais; - o slot da VMT de cada método virtual: sem ele o cliente não herda nem chama um virtual;
- as assinaturas: tipos dos parâmetros e do retorno, a convenção e as marcas que mudam a chamada (
pure,impure,vital,noreturn,discardable,abi_win/abi_posix); - as constantes, inclusive as tipadas e as de record com variante;
- os genéricos: o texto da declaração (a monomorfização reparseia texto; sem ele um
TList<T>do package seria inutilizável no cliente). É literalmente o que o.dcpdo Delphi carrega, e pela mesma razão; - os corpos que o cliente reanalisa:
inlinee o corpo curto=>; - os atributos e o que alimenta o
.rtti.json.
É um formato binário próprio (uma serialização da árvore sintática), que carrega rápido e se distribui sem fonte. O .fxd leva a data e o hash do compilador que o gerou, e abrir um .fxd de outra versão do compilador é erro, não aviso.
A RTL como package#
A RTL BedRock é o primeiro cliente do mecanismo, não um caso especial: a Fenner.BedRock.dll com seu .fxd, .fxnames e .lib. O que isso resolve, em ordem de importância:
- Alocador único. Sem package, cada binário tem o seu alocador; uma cadeia alocada na DLL e liberada no
.exeé um crash. Com a runtime em package isso deixa de existir, e é o ganho que muda o que se pode escrever. - Binários pequenos. O
.exede um aplicativo que usa o package tem algo como 1 MB em vez de 12 MB com a RTL embutida, e compila em bem menos tempo (medido no RAD: 22 s contra 39 s no mesmo projeto). is/asentre módulos. A VMT e a informação de tipo são únicas; sem isso, o mesmo tipo teria duas VMTs e oismentiria entre.exee.dll.- Exceção atravessando módulo. As exceções são
threadvare o tratador de hardware é registrado por módulo; com a runtime única, a intenção é que umraiseno package possa ser capturado no.exe. É um objetivo do desenho: a primeira entrega do mecanismo prometia só o caso do mesmo lado.
O preço é que o executável só abre com a Fenner.BedRock.dll ao lado: ela se distribui junto.
Ligando um package#
No .config do projeto:
Config All RtlPackage C:\Fenner\Common\BedRock\Fenner.BedRockA chave RtlPackage aponta o package de runtime (o .fxd, .fxnames, .lib e .dll com esse nome-base). LinkPackage liga um .fxd qualquer, e UnlinkPackage cancela um que veio de uma configuração acima. Para ignorar tudo isso numa compilação e embutir a RTL inteira, use --NoPackages.
protected#
No package aparece o sentido da terceira visibilidade de módulo. public significa exportado: o símbolo é publicado e o custo de mantê-lo estável passa a ser seu. protected é visível para todos os módulos do projeto e invisível fora dele: para quem está de fora do package, é como private. Sem isso, empacotar a RTL obrigaria a exportar tudo o que os módulos dela usam entre si; com isso, a superfície exportada passa a ser uma decisão. Veja Módulos e visibilidade.
Por dentro, para quem for mexer#
- Para a RTL, o mecanismo não é o
.fxd. O cliente continua lendo o fonte da RTL (declarações, tipos, o texto dos genéricos, com os layouts idênticos garantidos de graça), mas não emite nada dela: cada corpo viraexternalcom o símbolo vindo do.fxnames. O.fxdé o par das DLLs de usuário, que distribuem sem fonte. É o espelho do que o Delphi faz: o compilador lê o DCU inteiro e o código fica no BPL. - A DLL exporta todas as rotinas com corpo, não só as públicas, como um BPL: um genérico da RTL instanciado no cliente compila no cliente e chama os internos da RTL, e o mesmo vale para
inline. Os nomes obscuros do.fxnamesmantêm a superfície ilegível. - VMTs são importação de dado: a de uma classe do cliente que herda de
TObjectprecisa apontar a VMT doTObjectda DLL, o que faz oisdizer a verdade. O arranque preenche uma tabela de remendos, e esse arranque é o papel doSysInit. class constructoreinitializationda RTL rodam noDllMainda DLL, e o carregador do Windows garante a ordem (a DLL inicializa antes domain).- O
.fxnamespode mudar entre recompilações do package; um cliente compilado contra um mapa antigo precisa ser recompilado.