-
Notifications
You must be signed in to change notification settings - Fork 483
Draft: Enable saving dynamic assemblies to disk on .NET 9+ using PersistentAssemblyBuilder
#701
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
base: master
Are you sure you want to change the base?
Draft: Enable saving dynamic assemblies to disk on .NET 9+ using PersistentAssemblyBuilder
#701
Conversation
| #if !NET9_0_OR_GREATER | ||
| InitializeStaticFields(proxyType); | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must be skipped here because the generated type cannot be activated while tied to a PersitedAssemblyBuilder.
| lastAssemblyGenerated?.Dispose(); | ||
| var stream = new MemoryStream(); | ||
| persistedAssemblyBuilder.Save(stream); | ||
| stream.Seek(0, SeekOrigin.Begin); | ||
| var assembly = AssemblyLoadContext.Default.LoadFromStream(stream); | ||
| type = assembly.GetType(type.FullName!)!; | ||
| lastAssemblyGenerated = stream; | ||
| lastScope = scope; | ||
| scope = scope.Recycle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would likely have to be made thread-safe.
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| #if NET9_0_OR_GREATER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using NET9_0_OR_GREATER we'd probably want a FEATURE_PERSISTEDASSEMBLYBUILDER conditional compilation symbol.
| #if NET9_0_OR_GREATER | ||
| assemblyFilePath = weakModulePath; | ||
| #else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This alternative assignment may actually work also for .NET 4.6.2+, as [Weak|Strong]NamedModule.FullyQualifiedName is possibly equal to [weak|strong]ModulePath.
| #elif NET9_0_OR_GREATER | ||
| var persistedAssemblyBuilder = (PersistedAssemblyBuilder)assemblyBuilder; | ||
| persistedAssemblyBuilder.Save(assemblyFilePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is left-over code that (if it worked) would have to be called by the PersistentProxyBuilder on its lastScope. This code has been replaced with code directly in PersistentProxyBuilder however... probably not ideal.
| var assemblyPath = lastScope.WeakNamedModule != null ? lastScope.WeakNamedModuleName : lastScope.StrongNamedModuleName; | ||
| using var file = File.Create(assemblyPath); | ||
| lastAssemblyGenerated.Seek(0, SeekOrigin.Begin); | ||
| lastAssemblyGenerated.CopyTo(file); | ||
| file.Flush(); | ||
| file.Close(); | ||
| return assemblyPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should perhaps be replaced with return lastScope.SaveAssembly();.
This is the smallest code change I can think of off the top of my head to enable
PersistentAssemblyBuilder.SaveAssembly()on .NET 9+. It represents only a code exploration to test feasibility, but it is far from an ideal solution and won't be merged in this form.The approach chosen here works using the following approach:
Dynamic types are generated using .NET 9's new
PersistedAssemblyBuilder. The major obstacle here is that it does not support type activation. In order to activate a type, the assembly is emitted into a stream, which can then be loaded into the current assembly load context like any regular assembly.This means that the dynamic assembly is "baked" after every single proxy type activation, and it cannot be reused to emit further types. Because of that, the
ModuleScopegets replaced with a new one after every single proxy type creation.Because of the
ModuleScoperecycling, we essentially lose the type cache functionality. In theory, this functionality could be restored by lifting the type cache out ofModuleScopeintoDefaultProxyBuilder(so that it can span several module scopes). This refactoring would be fairly involved if breaking changes in the public API were to be avoided.Something similar to this might be good enough to run out test suite (including PE / IL verification of generated assemblies) on .NET 9, as we could likely do well without the type cache in this scenario.