Description
Background and motivation
We added a new persisted AssemblyBuilder implementation and related new APIs in .NET 9 preview 1. While discussing adding the SetEntryPoint
method questions raised for adding SetEntryPoint(MethodInfo, PEFileKinds)
the overload that sets PEFileKinds option.
Further we would need to add more options that used in .NET framework, like PortableExecutableKinds or ImageFileMachine that used to set with Save(String, PortableExecutableKinds, ImageFileMachine)
overload, plus would need to add APIs for setting AddResourceFile
, DefineResource
, DefineUnmanagedResource
.
Instead of adding all old .NET framework APIs we prefer to let the customers handle their assembly building process by themselves using the PEHeaderBuilder and System.Reflection.PortableExecutable.PEBuilder
implementation ManagedPEBuilder options.
These options are used to produce the assembly and allow users to configure it any way they want. These covers all options that existed in .NET framework, (but not exactly same way), plus provide many other options that not available in .NET framework that customers may want to set on the final binary.
In order to achieve this, we would need to provide all metadata information produced with Reflection.Emit APIs (MetadataBuilder and ILStreams) so that user could embed them into the corresponding section of PEBuidler
. But because the MetadataBuilder and BlobBuilder types are not accessible from within CoreLib we decided to make the PersistedAssemblyBuilder
type public and reintroduce the related new APIs there, remove the new APIs from the base AssemblyBuilder
type.
API Proposal
namespace System.Reflection.Emit;
public abstract partial class AssemblyBuilder
{
// Previously approved new APIs
- public static AssemblyBuilder DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);
- public void Save(Stream stream);
- public void Save(string assemblyFileName);
- protected abstract void SaveCore(Stream stream);
}
+public sealed class PersistedAssemblyBuilder : AssemblyBuilder
{
+ public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);
+ public void Save(Stream stream);
+ public void Save(string assemblyFileName);
+ public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData);
}
API Usage
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
// ...
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
// ...
il2.Emit(OpCodes.Ret);
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(
// Header for PEFileKinds.WindowApplication imageCharacteristics and subsystem need to be set
imageBase: 0x00400000, // this is the default value, was not necessarily needed to set.
imageCharacteristics: Characteristics.ExecutableImage,
subsystem: Subsystem.WindowsGui);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: peHeaderBuilder,
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
Old AssemblyBuilder.SetEntryPoint(MethodInfo)
Proposal folded below:
Add AssemblyBuilder.SetEntryPoint(MethodInfo) method
### Background and motivationAs a new persist able AssemblyBuilder implementation is being added in .NET 9 it also needs an API for setting the Entry point. We could add similar but virtual API we have in .NET framework.
API Proposal
namespace System.Reflection.Emit;
public abstract partial class AssemblyBuilder
{
// Previously approved new APIs
public static AssemblyBuilder DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);
public void Save(Stream stream);
public void Save(string assemblyFileName);
protected abstract void SaveCore(Stream stream);
+ public virtual void SetEntryPoint(MethodInfo entryMethod);
}
API Usage
AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(new AssemblyName("MyAssembly"), typeof(object).Assembly, null);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb1 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]);
ILGenerator il = mb1.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
MethodBuilder main = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = main.GetILGenerator();
il2.Emit(OpCodes.Ldc_I4_S, 10);
il2.Emit(OpCodes.Ldc_I4_1);
il2.Emit(OpCodes.Call, mb1);
il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])!);
il2.Emit(OpCodes.Ret);
tb.CreateType();
ab.SetEntryPoint(main);
ab.Save("MyAssembly.exe");
Alternative Designs
Or keep the same pattern (have protected virtual *Core
method).
namespace System.Reflection.Emit;
public abstract partial class AssemblyBuilder
{
// Previously approved new APIs
public static AssemblyBuilder DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);
public void Save(Stream stream);
public void Save(string assemblyFileName);
protected abstract void SaveCore(Stream stream);
+ public void SetEntryPoint(MethodInfo entryMethod);
+ protected virtual void SetEntryPointCore(MethodInfo entryMethod);
}