File: System\Reflection\Emit\PersistedAssemblyBuilder.cs
Web Access
Project: src\src\libraries\System.Reflection.Emit\src\System.Reflection.Emit.csproj (System.Reflection.Emit)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
 
namespace System.Reflection.Emit
{
    /// <summary>
    /// AssemblyBuilder implementation that can persist assembly to a disk or stream.
    /// </summary>
    public sealed class PersistedAssemblyBuilder : AssemblyBuilder
    {
        private readonly AssemblyName _assemblyName;
        private readonly Assembly _coreAssembly;
        private readonly MetadataBuilder _metadataBuilder;
        private ModuleBuilderImpl? _module;
        private bool _isMetadataPopulated;
 
        internal List<CustomAttributeWrapper>? _customAttributes;
 
        /// <summary>
        /// Creates a <see cref="PersistedAssemblyBuilder"/> instance that can be saved to a file or stream.
        /// </summary>
        /// <param name="name">The name of the assembly.</param>
        /// <param name="coreAssembly">The assembly that denotes the "system assembly" that houses the well-known types such as <see cref="object"/></param>
        /// <param name="assemblyAttributes">A collection that contains the attributes of the assembly.</param>
        /// <returns>An <see cref="PersistedAssemblyBuilder"/> that can be persisted.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="name.Name"/> or <paramref name="coreAssembly"/> is null.</exception>
        /// <remarks>Currently the persisted assembly doesn't support running, need to save it and load back to run.</remarks>
        public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null)
        {
            ArgumentNullException.ThrowIfNull(name);
            ArgumentException.ThrowIfNullOrEmpty(name.Name, "AssemblyName.Name");
            ArgumentNullException.ThrowIfNull(coreAssembly);
 
            _assemblyName = (AssemblyName)name.Clone();
            _coreAssembly = coreAssembly;
            _metadataBuilder = new MetadataBuilder();
 
            if (assemblyAttributes != null)
            {
                foreach (CustomAttributeBuilder assemblyAttribute in assemblyAttributes)
                {
                    SetCustomAttribute(assemblyAttribute);
                }
            }
        }
 
        private void WritePEImage(Stream peStream, BlobBuilder ilBuilder, BlobBuilder fieldData)
        {
            var peHeaderBuilder = new PEHeaderBuilder(
                // For now only support DLL, DLL files are considered executable files
                // for almost all purposes, although they cannot be directly run.
                imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll);
 
            var peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: new MetadataRootBuilder(_metadataBuilder),
                ilStream: ilBuilder,
                mappedFieldData: fieldData,
                strongNameSignatureSize: 0);
 
            // Write executable into the specified stream.
            var peBlob = new BlobBuilder();
            peBuilder.Serialize(peBlob);
            peBlob.WriteContentTo(peStream);
        }
 
        /// <summary>
        /// Serializes the assembly to <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to which the assembly serialized.</param>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
        /// <exception cref="InvalidOperationException">A module not defined for the assembly.</exception>
        /// <exception cref="InvalidOperationException">The metadata already populated for the assembly before.</exception>
        public void Save(Stream stream) => SaveInternal(stream);
 
        /// <summary>
        /// Saves the assembly to disk.
        /// </summary>
        /// <param name="assemblyFileName">The file name of the assembly.</param>
        /// <exception cref="ArgumentNullException"><paramref name="assemblyFileName"/> is null.</exception>
        /// <exception cref="InvalidOperationException">A module not defined for the assembly.</exception>
        /// <exception cref="InvalidOperationException">The metadata already populated for the assembly before.</exception>
        public void Save(string assemblyFileName)
        {
            ArgumentNullException.ThrowIfNull(assemblyFileName);
 
            using var peStream = new FileStream(assemblyFileName, FileMode.Create, FileAccess.Write);
            SaveInternal(peStream);
        }
 
        private void SaveInternal(Stream stream)
        {
            ArgumentNullException.ThrowIfNull(stream);
 
            PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out _);
            WritePEImage(stream, ilStream, fieldData);
        }
 
        /// <summary>
        /// Generates the metadata for the <see cref="PersistedAssemblyBuilder"/>.
        /// </summary>
        /// <param name="ilStream">Outputs <see cref="BlobBuilder"/> bytes that includes all method's IL (body) emitted.</param>
        /// <param name="mappedFieldData">Outputs <see cref="BlobBuilder"/> bytes that includes all field RVA data defined in the assembly.</param>
        /// <returns>A <see cref="MetadataBuilder"/> that includes all members defined in the Assembly.</returns>
        /// <exception cref="InvalidOperationException">A module not defined for the assembly.</exception>
        /// <exception cref="InvalidOperationException">The metadata already populated for the assembly previously.</exception>
        [CLSCompliant(false)]
        public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
        {
            PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out _);
 
            return _metadataBuilder;
        }
 
        /// <summary>
        /// Generates the metadata for the <see cref="PersistedAssemblyBuilder"/>.
        /// </summary>
        /// <param name="ilStream">Outputs <see cref="BlobBuilder"/> bytes that includes all method's IL (body) emitted.</param>
        /// <param name="mappedFieldData">Outputs <see cref="BlobBuilder"/> bytes that includes all field RVA data defined in the assembly.</param>
        /// <param name="pdbBuilder">Outputs <see cref="MetadataBuilder"/> that includes PDB metadata.</param>
        /// <returns>A <see cref="MetadataBuilder"/> that includes all members defined in the Assembly.</returns>
        /// <exception cref="InvalidOperationException">A module not defined for the assembly.</exception>
        /// <exception cref="InvalidOperationException">The metadata already populated for the assembly previously.</exception>
        [CLSCompliant(false)]
        public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData, out MetadataBuilder pdbBuilder)
        {
            PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out pdbBuilder);
 
            return _metadataBuilder;
        }
 
        private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out MetadataBuilder pdbBuilder)
        {
            if (_module == null)
            {
                throw new InvalidOperationException(SR.InvalidOperation_AModuleRequired);
            }
 
            if (_isMetadataPopulated) // Cannot populate assembly metadata multiple times. This is consistent with Save() in .Net Framework.
            {
                throw new InvalidOperationException(SR.InvalidOperation_CannotPopulateMultipleTimes);
            }
 
            ilStream = new BlobBuilder();
            fieldData = new BlobBuilder();
 
            // Add assembly metadata
            AssemblyDefinitionHandle assemblyHandle = _metadataBuilder.AddAssembly(
               _metadataBuilder.GetOrAddString(value: _assemblyName.Name!),
               version: _assemblyName.Version ?? new Version(0, 0, 0, 0),
               culture: _assemblyName.CultureName == null ? default : _metadataBuilder.GetOrAddString(value: _assemblyName.CultureName),
               publicKey: _assemblyName.GetPublicKey() is byte[] publicKey ? _metadataBuilder.GetOrAddBlob(value: publicKey) : default,
               flags: AddContentType((AssemblyFlags)_assemblyName.Flags, _assemblyName.ContentType),
#pragma warning disable SYSLIB0037 // Type or member is obsolete
               hashAlgorithm: (AssemblyHashAlgorithm)_assemblyName.HashAlgorithm
#pragma warning restore SYSLIB0037
               );
 
            _module.WriteCustomAttributes(_customAttributes, assemblyHandle);
            _module.AppendMetadata(new MethodBodyStreamEncoder(ilStream), fieldData, out pdbBuilder);
            _isMetadataPopulated = true;
        }
 
        private static AssemblyFlags AddContentType(AssemblyFlags flags, AssemblyContentType contentType)
            => (AssemblyFlags)((int)contentType << 9) | flags;
 
        protected override ModuleBuilder DefineDynamicModuleCore(string name)
        {
            if (_module != null)
            {
                throw new InvalidOperationException(SR.InvalidOperation_NoMultiModuleAssembly);
            }
 
            _module = new ModuleBuilderImpl(name, _coreAssembly, _metadataBuilder, this);
            return _module;
        }
 
        protected override ModuleBuilder? GetDynamicModuleCore(string name)
        {
            if (_module != null && _module.ScopeName.Equals(name))
            {
                return _module;
            }
 
            return null;
        }
 
        protected override void SetCustomAttributeCore(ConstructorInfo con, ReadOnlySpan<byte> binaryAttribute)
        {
            _customAttributes ??= new List<CustomAttributeWrapper>();
            _customAttributes.Add(new CustomAttributeWrapper(con, binaryAttribute));
        }
 
        public override string? FullName => _assemblyName.FullName;
 
        public override Module ManifestModule => _module ?? throw new InvalidOperationException(SR.InvalidOperation_AModuleRequired);
 
        public override AssemblyName GetName(bool copiedName) => (AssemblyName)_assemblyName.Clone();
    }
}