Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Breaking Changes:

Enhancements:
- Minimally improved support for methods having `ref struct` parameter and return types, such as `Span<T>`: Intercepting such methods caused the runtime to throw `InvalidProgramException` and `NullReferenceException` due to forbidden conversions of `ref struct` values when transferring them into & out of `IInvocation` instances. To prevent these exceptions from being thrown, such values now get replaced with `null` in `IInvocation`, and with `default` values in return values and `out` arguments. When proceeding to a target, the target methods likewise receive such nullified values. (@stakx, #665)
- Restore ability on .NET 9 and later to save dynamic assemblies to disk using `PersistentProxyBuilder` (@stakx, #718)
- Dependencies were updated

Bugfixes:
Expand Down
10 changes: 10 additions & 0 deletions ref/Castle.Core-net9.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2585,6 +2585,16 @@ public ModuleScope(bool savePhysicalAssembly, bool disableSignedModule, string s
public string WeakNamedModuleName { get; }
public static byte[] GetKeyPair() { }
}
public sealed class PersistentProxyBuilder : Castle.DynamicProxy.DefaultProxyBuilder
{
public PersistentProxyBuilder() { }
public event System.EventHandler<Castle.DynamicProxy.PersistentProxyBuilderAssemblyEventArgs>? AssemblyCreated;
}
public sealed class PersistentProxyBuilderAssemblyEventArgs : System.EventArgs
{
public System.Reflection.Assembly Assembly { get; }
public byte[] AssemblyBytes { get; }
}
public class ProxyGenerationOptions
{
public static readonly Castle.DynamicProxy.ProxyGenerationOptions Default;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,14 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if FEATURE_ASSEMBLYBUILDER_SAVE

namespace Castle.DynamicProxy.Tests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using Castle.DynamicProxy.Tests.Interfaces;

using NUnit.Framework;

#if NET462_OR_GREATER

[TestFixture]
public class PersistentProxyBuilderTestCase
{
Expand All @@ -44,6 +50,55 @@ public void PersistentProxyBuilder_SavesSignedFile()
Assert.IsTrue(path.EndsWith(ModuleScope.DEFAULT_FILE_NAME));
}
}
}

#endif
#elif NET9_0_OR_GREATER

[TestFixture]
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
public class PersistentProxyBuilderTestCase
{
private List<Assembly> assemblies;
private PersistentProxyBuilder builder;

[SetUp]
public void SetupProxyBuilder()
{
assemblies = new List<Assembly>();
builder = new PersistentProxyBuilder();
builder.AssemblyCreated += (object _, PersistentProxyBuilderAssemblyEventArgs e) =>
{
assemblies.Add(e.Assembly);
};
}

[Test]
public void SavesOneAssemblyPerProxiedType()
{
var oneProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(IOne), Type.EmptyTypes, ProxyGenerationOptions.Default);
Assert.AreEqual(1, assemblies.Count);

var twoProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(ITwo), Type.EmptyTypes, ProxyGenerationOptions.Default);
Assert.AreEqual(2, assemblies.Count);

var oneAssembly = assemblies[0];
var twoAssembly = assemblies[1];
Assert.AreSame(oneAssembly, oneProxyType.Assembly);
Assert.AreSame(twoAssembly, twoProxyType.Assembly);
Assert.AreNotSame(oneAssembly, twoAssembly);
}

[Test]
public void TypeCacheWorks()
{
var proxyType1 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default);
var proxyType2 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default);

Assert.AreEqual(1, assemblies.Count);
Assert.AreSame(proxyType1, proxyType2);
Assert.AreSame(proxyType1.Assembly, proxyType2.Assembly);
}
}

#endif

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2004-2025 Castle Project - http://www.castleproject.org/
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -412,7 +412,7 @@ public Type BuildType()
builder.Generate();
}

var type = typeBuilder.CreateTypeInfo();
var type = moduleScope.BuildType(typeBuilder);

return type;
}
Expand Down
91 changes: 57 additions & 34 deletions src/Castle.Core/DynamicProxy/ModuleScope.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -311,50 +311,59 @@ internal ModuleBuilder ObtainDynamicModuleWithWeakName()

private ModuleBuilder CreateModule(bool signStrongName)
{
var assemblyName = GetAssemblyName(signStrongName);
var assemblyBuilder = CreateAssembly(signStrongName);
var moduleName = signStrongName ? StrongNamedModuleName : WeakNamedModuleName;
#if FEATURE_APPDOMAIN
#if NET462_OR_GREATER
if (savePhysicalAssembly)
{
AssemblyBuilder assemblyBuilder;
try
{
assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.RunAndSave, signStrongName ? StrongNamedModuleDirectory : WeakNamedModuleDirectory);
}
catch (ArgumentException e)
{
if (signStrongName == false && e.StackTrace.Contains("ComputePublicKey") == false)
{
// I have no idea what that could be
throw;
}
var message = string.Format(
"There was an error creating dynamic assembly for your proxies - you don't have permissions " +
"required to sign the assembly. To workaround it you can enforce generating non-signed assembly " +
"only when creating {0}. Alternatively ensure that your account has all the required permissions.",
GetType());
throw new ArgumentException(message, e);
}
var module = assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false);
return module;
return assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false);
}
else
#endif
{
#if FEATURE_APPDOMAIN
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.Run);
return assemblyBuilder.DefineDynamicModule(moduleName);
}
#else
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
return assemblyBuilder.DefineDynamicModule(moduleName);
#endif
}

var module = assemblyBuilder.DefineDynamicModule(moduleName);
return module;
internal virtual AssemblyBuilder CreateAssembly(bool signStrongName)
{
var assemblyName = GetAssemblyName(signStrongName);
try
{
#if NET462_OR_GREATER
if (savePhysicalAssembly)
{
return AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave,
signStrongName ? StrongNamedModuleDirectory : WeakNamedModuleDirectory);
}
else
{
return AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);
}
#else
return AssemblyBuilder.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);
#endif
}
catch (ArgumentException e) when (signStrongName || e.StackTrace?.Contains("ComputePublicKey") == true)
{
var message = string.Format(
"There was an error creating dynamic assembly for your proxies - you don't have permissions " +
"required to sign the assembly. To workaround it you can enforce generating non-signed assembly " +
"only when creating {0}. Alternatively ensure that your account has all the required permissions.",
GetType());
throw new ArgumentException(message, e);
}
}

private AssemblyName GetAssemblyName(bool signStrongName)
internal AssemblyName GetAssemblyName(bool signStrongName)
{
var assemblyName = new AssemblyName {
Name = signStrongName ? strongAssemblyName : weakAssemblyName
Expand All @@ -372,6 +381,15 @@ private AssemblyName GetAssemblyName(bool signStrongName)
return assemblyName;
}

internal void ResetModules()
{
lock (moduleLocker)
{
moduleBuilder = null;
moduleBuilderWithStrongName = null;
}
}

#if FEATURE_ASSEMBLYBUILDER_SAVE
/// <summary>
/// Saves the generated assembly with the name and directory information given when this <see cref = "ModuleScope" /> instance was created (or with
Expand Down Expand Up @@ -539,10 +557,15 @@ public void LoadAssemblyIntoCache(Assembly assembly)
}
#endif

internal TypeBuilder DefineType(bool inSignedModulePreferably, string name, TypeAttributes flags)
internal virtual TypeBuilder DefineType(bool inSignedModulePreferably, string name, TypeAttributes flags)
{
var module = ObtainDynamicModule(disableSignedModule == false && inSignedModulePreferably);
return module.DefineType(name, flags);
}

internal virtual Type BuildType(TypeBuilder typeBuilder)
{
return typeBuilder.CreateTypeInfo()!;
}
}
}
47 changes: 42 additions & 5 deletions src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if FEATURE_ASSEMBLYBUILDER_SAVE

#nullable enable

namespace Castle.DynamicProxy
{
using System;
using System.Reflection;

#if NET462_OR_GREATER

/// <summary>
/// ProxyBuilder that persists the generated type.
/// </summary>
Expand Down Expand Up @@ -46,6 +49,40 @@ public PersistentProxyBuilder() : base(new ModuleScope(true))
return ModuleScope.SaveAssembly();
}
}
}

#endif
#elif NET9_0_OR_GREATER

/// <summary>
/// <see cref="IProxyBuilder"/> that allows you to persist proxy types.
/// Each generated proxy type will be placed in its own separate assembly.
/// </summary>
public sealed class PersistentProxyBuilder : DefaultProxyBuilder
{
/// <summary>
/// Initializes a new instance of the <see cref = "PersistentProxyBuilder" /> class.
/// </summary>
public PersistentProxyBuilder()
: this(new PersistentProxyBuilderModuleScope())
{
}

private PersistentProxyBuilder(PersistentProxyBuilderModuleScope scope)
: base(scope)
{
scope.AssemblyCreated += OnAssemblyCreated;
}

/// <summary>
/// Raised when a new proxy type assembly has been created and loaded into the runtime.
/// </summary>
public event EventHandler<PersistentProxyBuilderAssemblyEventArgs>? AssemblyCreated;

private void OnAssemblyCreated(Assembly assembly, byte[] assemblyBytes)
{
AssemblyCreated?.Invoke(this, new PersistentProxyBuilderAssemblyEventArgs(assembly, assemblyBytes));
}
}

#endif

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if NET9_0_OR_GREATER

#nullable enable

namespace Castle.DynamicProxy
{
using System;
using System.Reflection;

/// <summary>
/// Provides data for the <see cref="PersistentProxyBuilder.AssemblyCreated"/> event.
/// </summary>
public sealed class PersistentProxyBuilderAssemblyEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PersistentProxyBuilderAssemblyEventArgs"/> class.
/// </summary>
/// <param name="assembly">The assembly that has been created and loaded into the runtime.</param>
/// <param name="assemblyBytes">The raw bytes of the created assembly (can be saved as a DLL file).</param>
internal PersistentProxyBuilderAssemblyEventArgs(Assembly assembly, byte[] assemblyBytes)
{
Assembly = assembly;
AssemblyBytes = assemblyBytes;
}

/// <summary>
/// The assembly that has been created and loaded into the runtime.
/// </summary>
public Assembly Assembly { get; }

/// <summary>
/// The raw bytes of the created assembly (can be saved as a DLL file).
/// </summary>
/// <remarks></remarks>
public byte[] AssemblyBytes { get; }
}
}

#endif
Loading