-
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,11 @@ | |
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| #if NET9_0_OR_GREATER | ||
| using System.IO; | ||
| using System.Reflection.Emit; | ||
| using System.Runtime.Loader; | ||
| #endif | ||
|
|
||
| using Castle.Core.Internal; | ||
| using Castle.Core.Logging; | ||
|
|
@@ -30,9 +35,14 @@ | |
| /// </summary> | ||
| public class DefaultProxyBuilder : IProxyBuilder | ||
| { | ||
| private readonly ModuleScope scope; | ||
| private ModuleScope scope; | ||
| private ILogger logger = NullLogger.Instance; | ||
|
|
||
| #if NET9_0_OR_GREATER | ||
| protected ModuleScope lastScope; | ||
| protected MemoryStream lastAssemblyGenerated; | ||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref = "DefaultProxyBuilder" /> class with new <see cref = "ModuleScope" />. | ||
| /// </summary> | ||
|
|
@@ -45,7 +55,7 @@ | |
| /// Initializes a new instance of the <see cref = "DefaultProxyBuilder" /> class. | ||
| /// </summary> | ||
| /// <param name = "scope">The module scope for generated proxy types.</param> | ||
| public DefaultProxyBuilder(ModuleScope scope) | ||
|
Check warning on line 58 in src/Castle.Core/DynamicProxy/DefaultProxyBuilder.cs
|
||
| { | ||
| this.scope = scope; | ||
| } | ||
|
|
@@ -68,7 +78,7 @@ | |
| AssertValidMixins(options, nameof(options)); | ||
|
|
||
| var generator = new ClassProxyGenerator(scope, classToProxy, additionalInterfacesToProxy, options) { Logger = logger }; | ||
| return generator.GetProxyType(); | ||
| return ToInstantiableType(generator.GetProxyType(), generator); | ||
| } | ||
|
|
||
| public Type CreateClassProxyTypeWithTarget(Type classToProxy, Type[]? additionalInterfacesToProxy, | ||
|
|
@@ -80,7 +90,7 @@ | |
|
|
||
| var generator = new ClassProxyWithTargetGenerator(scope, classToProxy, additionalInterfacesToProxy, options) | ||
| { Logger = logger }; | ||
| return generator.GetProxyType(); | ||
| return ToInstantiableType(generator.GetProxyType(), generator); | ||
| } | ||
|
|
||
| public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, | ||
|
|
@@ -92,7 +102,7 @@ | |
| AssertValidMixins(options, nameof(options)); | ||
|
|
||
| var generator = new InterfaceProxyWithTargetGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, targetType, options) { Logger = logger }; | ||
| return generator.GetProxyType(); | ||
| return ToInstantiableType(generator.GetProxyType(), generator); | ||
| } | ||
|
|
||
| public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, | ||
|
|
@@ -103,7 +113,7 @@ | |
| AssertValidMixins(options, nameof(options)); | ||
|
|
||
| var generator = new InterfaceProxyWithTargetInterfaceGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, interfaceToProxy, options) { Logger = logger }; | ||
| return generator.GetProxyType(); | ||
| return ToInstantiableType(generator.GetProxyType(), generator); | ||
| } | ||
|
|
||
| public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, | ||
|
|
@@ -114,7 +124,28 @@ | |
| AssertValidMixins(options, nameof(options)); | ||
|
|
||
| var generator = new InterfaceProxyWithoutTargetGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, typeof(object), options) { Logger = logger }; | ||
| return generator.GetProxyType(); | ||
| return ToInstantiableType(generator.GetProxyType(), generator); | ||
| } | ||
|
|
||
| private Type ToInstantiableType(Type type, BaseProxyGenerator generator) | ||
| { | ||
| #if NET9_0_OR_GREATER | ||
| if (type.Assembly is PersistedAssemblyBuilder persistedAssemblyBuilder) | ||
| { | ||
| 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(); | ||
|
Comment on lines
+135
to
+143
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would likely have to be made thread-safe. |
||
| } | ||
|
|
||
| generator.InitializeStaticFields(type); | ||
| #endif | ||
| return type; | ||
| } | ||
|
|
||
| private void AssertValidMixins(ProxyGenerationOptions options, string paramName) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,7 +102,9 @@ protected sealed override Type GenerateType(string name, INamingScope namingScop | |
| // Crosses fingers and build type | ||
|
|
||
| var proxyType = emitter.BuildType(); | ||
| #if !NET9_0_OR_GREATER | ||
| InitializeStaticFields(proxyType); | ||
| #endif | ||
|
Comment on lines
+105
to
+107
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| return proxyType; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,6 +150,13 @@ internal INamingScope NamingScope | |
|
|
||
| internal SynchronizedDictionary<CacheKey, Type> TypeCache => typeCache; | ||
|
|
||
| #if NET9_0_OR_GREATER | ||
| internal ModuleScope Recycle() | ||
| { | ||
| return new ModuleScope(savePhysicalAssembly, disableSignedModule, strongAssemblyName, strongModulePath, weakAssemblyName, weakModulePath); | ||
| } | ||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// Gets the key pair used to sign the strong-named assembly generated by this <see cref = "ModuleScope" />. | ||
| /// </summary> | ||
|
|
@@ -336,6 +343,14 @@ private ModuleBuilder CreateModule(bool signStrongName) | |
| return module; | ||
| } | ||
| else | ||
| #elif NET9_0_OR_GREATER | ||
| if (savePhysicalAssembly) | ||
| { | ||
| AssemblyBuilder assemblyBuilder = new PersistedAssemblyBuilder(assemblyName, typeof(object).Assembly); | ||
| var module = assemblyBuilder.DefineDynamicModule(moduleName); | ||
| return module; | ||
| } | ||
| else | ||
| #endif | ||
| { | ||
| #if FEATURE_APPDOMAIN | ||
|
|
@@ -368,7 +383,7 @@ private AssemblyName GetAssemblyName(bool signStrongName) | |
| return assemblyName; | ||
| } | ||
|
|
||
| #if FEATURE_ASSEMBLYBUILDER_SAVE | ||
| #if FEATURE_ASSEMBLYBUILDER_SAVE || NET9_0_OR_GREATER | ||
| /// <summary> | ||
| /// Saves the generated assembly with the name and directory information given when this <see cref = "ModuleScope" /> instance was created (or with | ||
| /// the <see cref = "DEFAULT_FILE_NAME" /> and current directory if none was given). | ||
|
|
@@ -450,7 +465,11 @@ private AssemblyName GetAssemblyName(bool signStrongName) | |
| } | ||
| assemblyBuilder = (AssemblyBuilder)StrongNamedModule.Assembly; | ||
| assemblyFileName = StrongNamedModuleName; | ||
| #if NET9_0_OR_GREATER | ||
| assemblyFilePath = weakModulePath; | ||
| #else | ||
|
Comment on lines
+468
to
+470
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| assemblyFilePath = StrongNamedModule.FullyQualifiedName; | ||
| #endif | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -460,7 +479,11 @@ private AssemblyName GetAssemblyName(bool signStrongName) | |
| } | ||
| assemblyBuilder = (AssemblyBuilder)WeakNamedModule.Assembly; | ||
| assemblyFileName = WeakNamedModuleName; | ||
| #if NET9_0_OR_GREATER | ||
| assemblyFilePath = weakModulePath; | ||
| #else | ||
| assemblyFilePath = WeakNamedModule.FullyQualifiedName; | ||
| #endif | ||
| } | ||
|
|
||
| if (File.Exists(assemblyFilePath)) | ||
|
|
@@ -472,7 +495,12 @@ private AssemblyName GetAssemblyName(bool signStrongName) | |
| AddCacheMappings(assemblyBuilder); | ||
| #endif | ||
|
|
||
| #if FEATURE_ASSEMBLYBUILDER_SAVE | ||
| assemblyBuilder.Save(assemblyFileName); | ||
| #elif NET9_0_OR_GREATER | ||
| var persistedAssemblyBuilder = (PersistedAssemblyBuilder)assemblyBuilder; | ||
| persistedAssemblyBuilder.Save(assemblyFilePath); | ||
|
Comment on lines
+500
to
+502
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| #endif | ||
| return assemblyFilePath; | ||
| } | ||
| #endif | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,16 @@ | |
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #if FEATURE_ASSEMBLYBUILDER_SAVE | ||
| #if FEATURE_ASSEMBLYBUILDER_SAVE || NET9_0_OR_GREATER | ||
|
|
||
| #nullable enable | ||
|
|
||
| namespace Castle.DynamicProxy | ||
| { | ||
| #if NET9_0_OR_GREATER | ||
| using System.IO; | ||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// ProxyBuilder that persists the generated type. | ||
| /// </summary> | ||
|
|
@@ -43,7 +47,17 @@ public PersistentProxyBuilder() : base(new ModuleScope(true)) | |
| /// </remarks> | ||
| public string? SaveAssembly() | ||
| { | ||
| #if NET9_0_OR_GREATER | ||
| 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; | ||
|
Comment on lines
+51
to
+57
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should perhaps be replaced with |
||
| #else | ||
| return ModuleScope.SaveAssembly(); | ||
| #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.
Instead of using
NET9_0_OR_GREATERwe'd probably want aFEATURE_PERSISTEDASSEMBLYBUILDERconditional compilation symbol.