-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
System.Reflection.Emit.AssemblyBuilder.Save #15704
Comments
Another interesting omission is the lack of LambdaExpression.Compile accepting a MethodBuilder which tends to be very useful when combined with AssemblyBuilder.Save support. |
Large chunks of Reflection.Emit code in the runtime are Windows-specific. It is not easy to just enable them for CoreCLR because of it would not work on Unix. In particular, the support for emitting debug information depends on unmanaged PDB writer that is very complex Windows-specific component that we have no plans to open source and bring x-plat. We are also not happy about Reflection.Emit being integrated deeply into the runtime. Reflection.Emit should be a independent higher level component that generates PE file that the runtime loads as any other assembly (note that runtime supports loading assemblies from byte array, so the PE file does not have to be persisted on disk). IKVM.Reflection.Emit done by Jeroen Frijters proved that it is pretty doable and can actually work much better than the current Reflection.Emit built into the runtime. The plan to address the Reflection.Emit is:
|
@jkotas many thanks for the explanation here, it makes complete sense. I'm glad you guys are planning to provide a more general purpose object model around the heart of .NET, IL. I'd seen the portable PDB reader/writer and some metadata object model appear in Roslyn, they look great. In the past I've used Cecil for the exact reason the .NET Framework API is so heavily tied into the runtime, glad to see more stuff being pulled out of the runtime. |
Next step: We need API proposal with deep analysis how implementable it is. The old Desktop API might be an option, likely not a good one. |
There is currently no plan (milestone Future + my comment above) - area owners @atsushikan @DnlHarvey @joshfree can provide more details. |
@atsushikan @DnlHarvey @joshfree, one question about the plan that @jkotas laid out above:
I do not fully understand the plan, in particular with regard to dynamic assemblies ( I imagine one would have to write each dynamically generated type to a single assembly... but wouldn't this in turn interfere with Would it be possible to load more than one assembly having the exact same name (whether weak or strong), so that each such single-type assembly can have a |
Yes, there would need to be a runtime hook for this. Conceptually, each type addition in Reflection.Emit produces a new assembly that contains all existing metadata (with all existing tokens preserved) plus the new type. Then the runtime is asked to update the metadata of the already loaded assembly to the new version. Sending the whole assembly over for each edit is not efficient for large assemblies with small edits. It would be better to just send the metadata delta with the new stuff over. Fortunately, this exists in the runtime for edit and continue already. Look for https://github.com/dotnet/coreclr/search?q=ApplyEditAndContinue. So exposing the ApplyDelta metadata API for the standalone Reflection.Emit implementation should do the trick here. |
The existence of cross-platform Roslyn suggests that this isn't a particularly intractable problem, at a high level at least. Could this please be made a high priority? The inability to use Reflection.Emit is currently my dotnet/corefx#1 blocker preventing me from migrating existing work to Core, and as such it's far more urgent, from my perspective at least, than the development of new features. |
Here's a good starting point for a non-Windows implementation (and it's rather mature): https://github.com/mono/mono/tree/master/mcs/class/corlib/System.Reflection.Emit |
@atsushikan: Out of curiosity, is there any documentation / spec at all about the data format used for EnC metadata deltas, other than the actual CoreCLR source code? |
Not that I've heard of but EnC is not an area where I have any knowledge. |
@atsushikan - Thanks for the quick reply. 👍 I almost expected this to be a really arcane area, but I thought I'd give it a try and ask. I'll see what I can gather together on my own. |
@stakx EnC MetaData format was never thoroughly documented (neither publicly nor internally). It was always defined just in the implementation. |
@stakx Curious, what do you need it for? |
@karelz - I almost suspected so... Thanks for this info! 👍 @tmat - Mostly out of curiousity. I have spent a lot of time with System.Reflection in the past few months, and noticed its various limitations or bugs. There are sometimes situations where you'd rather just see plain, uninterpreted metadata instead of the semi-high-level view that Reflection provides—which is neither original metadata, nor as high-level as e.g. C#, but something of its own sitting somewhere in-between. I have also discovered System.Reflection.Metadata (SRM). I have been thinking about how nice it would be if the (Core)CLR had the necessary runtime hooks and extension points so that both Reflection and Reflection.Emit could be rebuilt on top of SRM, outside of mscorlib / System.Private.CoreLib. (At the same time, this would also enable folks to build their own custom reflection / dynamic code emission libraries.) Two things appear to be missing as of today to make this possible:
I realise that having such runtime hooks closely allied with System.Reflection.Metadata is probably a pipe dream at this stage. But it might allow some super-interesting scenarios. So I'm trying to learn how this part of the runtime works to better gauge how big of a project this would be. If there are any plans or efforts in this direction, I'd love to learn about it & be directed to the appropriate places. Otherwise... I don't intend to hijack this issue, which is just about |
|
@jkotas - Yes, I have, and that's a really nice extension point! But it comes with some limitations: It's a |
We have finally rolled our own open-source version of Check-out https://github.com/Lokad/ILPack Feedback will be most welcome. |
@vermorel Looks interesting. Does it generate PDBs too? |
@masonwheeler |
I tried it on a Sigil branch of mine ( https://github.com/arlm/Sigil-vNext ) nad could not make it work because it does not install on .NET Standard, only on .NET Core solutions. |
Update: We have added partial support for AssemblyBuilder.Save in .NET 8: We have completed our goal to add MVP (Minimum Viable Product) support in .NET 8 that allows us to save *.tlb files produced by TlbImp tool. Now we can port the tool into .NET Core using the new AssemblyBuilder.Save implementation. The implementation support following:
The implementation doesn't support other members (a method with body (IL not supported), Properties, Constructors, Events). Because we have only partial implementation we did not exposed the approved new APIs publicly. Those APIs are internal, if you want to give it a try you could use reflection, example: using System.Reflection.Emit;
using System.Reflection;
namespace TestApp
{
internal class Program
{
static void Main(string[] args)
{
AssemblyName aName = new AssemblyName("MyAssembly");
AssemblyBuilder ab= PopulateAssemblyBuilderAndSaveMethod(aName, null, out MethodInfo saveMethod);
ModuleBuilder moduleBuilder = ab.DefineDynamicModule("ModuleName"); // Only one module supported
moduleBuilder.DefineType("TestInterface", TypeAttributes.Interface | TypeAttributes.Abstract);
// Add other classes, interfaces, and structs. Define method, field, custom attributes for them
// Note that non abstract method not supported yet as writing IL is not supported yet
// defining Constructors, Properties and Events are also not supported yet
// Then save the assembly into a file (or Stream)
saveMethod.Invoke(ab, new object[] { "MyAssembly.dll" });
}
private static AssemblyBuilder PopulateAssemblyBuilderAndSaveMethod(AssemblyName assemblyName,
List<CustomAttributeBuilder>? assemblyAttributes, out MethodInfo saveMethod)
{
Type abType= Type.GetType("System.Reflection.Emit.AssemblyBuilderImpl, System.Reflection.Emit", throwOnError: true)!;
saveMethod = abType.GetMethod("Save", BindingFlags.NonPublic | BindingFlags.Instance,
new Type[] { typeof(string) /* or use typeof(Stream) */ });
MethodInfo defineDynamicAssembly = abType.GetMethod("DefinePersistedAssembly", BindingFlags.NonPublic | BindingFlags.Static,
new Type[] { typeof(AssemblyName), typeof(Assembly), typeof(List<CustomAttributeBuilder>) });
return (AssemblyBuilder)defineDynamicAssembly.Invoke(null, new object[] { assemblyName, typeof(object).Assembly, assemblyAttributes });
}
}
} |
While this is, of course, some good news, the implemented surface is still very far from what we would ideally like to see. In most use cases I've seen so far, people wanted to save assemblies with actual executable code / method bodies, and as far as I understand, this is yet not implemented. |
@ForNeVeR I got the definitive answer here in #62956 (comment) Summary: No, right now we should not expect support executable code / method bodies in .NET 8 |
I had modified the milestone into .NET 9, even though seems it was not clear about what happens with the remaining work. |
UPDATE: we are making a good progress:
Planning to finish the main functionality (excluding entry point and PDB) and make related APIs public in preview 1. Here is the tracking issue for details: #92975. Now it would be great to test the implementation with a real-life scenario. I would really appreciate if you could:
Thank you in advance! |
I have a large application currently targeting net4x that uses AssemblyBuilder. Would having generated assemblies from it be useful at all for this? I could also provide the code for the implementation, but I don't have the spare time to do a test port to NET8/NET9 yet. It's a fairly complex script compiler with a small automated test suite, and then there are around a hundred scripts or so that are loaded by a host .NET application. It leans heavily on LINQ Expression.CompileToMethod to generate most of the methods (and does classic SRE for things that LINQ can't generate, like instance methods or pointers), so I don't know if that is relevant here and making that scenario work is considered in scope. |
Yes, the generated assembly with your app would be useful, thanks!
LINQ |
Will |
I do not expect that XmlSerializer.GenerateSerializer is going to be added back. See #1413 and related issues. |
Thanks for the reply. I know there is the new package, but it's different from the .NET Framework In one of the projects that I am working on, we have an obfuscator that extracts types that need to be serialized and generate the corresponding serializer assembly, which gets merged into the main assembly and obfuscated together. The current package based method is very hard to be used in this process. Our workaround is to use a .NET Framework app to generate the serializer assembly, which is rewritten to target .NET. This workaround currently works fine, but obviously it's risky. I think a source-generator based method or a public generator method should be more helpful. I'm not sure whether there are undocumented methods or better solutions for this though. This is probably not a good place to continue this topic. If you think it's necessary I can open a new issue. |
Thank you @kg for sending assemblies, I have regenerated the assembly with .NET Core implementation and found many issues that now fixed and will be ported with .NET 9 preview 2. |
The feature is now available in https://dotnet.microsoft.com/en-us/download/dotnet/9.0, please give it a try and give us a feedback. We also adding bunch of fixes in preview 2 mostly related to complex generic types/methods Here is related doc: https://learn.microsoft.com/dotnet/fundamentals/runtime-libraries/system-reflection-emit-assemblybuilder#persistable-dynamic-assemblies-in-net-core |
Will Portable PDB support be there as well in this preview? |
Entry point and PDB not included in preview 1. Entry point will be added by preview 3. PDB support timeline not clear yet. |
There are a few methods still missing unrelated to PDB generation:
There might be alternatives that I'm unaware of. If not, will these methods also be supported in the future? |
I really hope it makes it in by release. At its core, this is a tool for compiler developers, and a compiler that can't produce debug builds is severely disadvantaged. |
Portable PDB is not blocked.
|
@Martin1994 That link looks kind of useful, but it looks like it only works if you already have an existing
IIRC IronPython style dynamic emitting was covered by the DLR, which was released with .NET Framework 4.0. |
FYI: We took completely different approach for adding 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);
} See more details and usage example from the API proposal. This change will be available in preview 3. |
Further the PDB support will be handled similar way as one would set With that I am closing this issue as alternate for the |
AssemblyBuilder.Save
andAssemblyBuilderAccess.RunAndSave
isn't available in .NET Core, however coreclr seems to have the code to implement it but I looks conditionally compiled out.https://github.com/dotnet/coreclr/blob/bc146608854d1db9cdbcc0b08029a87754e12b49/src/mscorlib/src/System/Reflection/Emit/AssemblyBuilder.cs#L1690-L1711
Our use case in Castle DynamicProxy is to write out dynamically created assemblies to disk so we can run peverify over the assembly in unit tests. It also greatly helps writing out the assembly and opening it in ildasm to manually verify IL.
Is there any chance we can get this functionality that .NET Framework has introduced into .NET Core, or an alternative way of doing the same thing. This might not sound like a big deal if you aren't familiar with Castle DynamicProxy's internals, but it really is as it heavily reduces our confidence in both our code and the .NET runtime as well as reducing our ability to track down defects.
The text was updated successfully, but these errors were encountered: