Delphi's .NET code generator emits IL as bytecode directly into memory, much like x86 code generation, though with appropriate headers etc. That is, the code generator directly emits bytes, exception tables etc., corresponding to the encoded IL format. It doesn't do this with an API, but rather the old-fashioned way: write the code a byte at a time.
Later, Delphi's built-in linker works with IMetaDataEmit
etc. to generate metadata, and IMetaDataEmit::SetRVA
to tell the metadata where the code is going to be located in the executable. The metadata is copied out with IMetaDataEmit::SaveToMemory
and then copied out into the PE that the linker has been building up, with the CLR header correspondingly patched to point to the metadata start.
It's a lot of code, some of it fiddly, as it's much of it is threaded through Delphi's existing x86 linker, which does things like branch optimization and elimination of unused code (smart linking) which strictly speaking isn't generally necessary for .NET.
If we were to do it all again, we might very well avoid the .NET APIs for creating metadata, and generate the whole thing straight from the spec. The APIs ended up being a black box for optimization, and added up to a substantial amount of compilation time.