diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c205052b..983a2f54ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Enhancements: - Two new generic method overloads `proxyGenerator.CreateClassProxy([options], constructorArguments, interceptors)` (@backstromjoel, #636) - Allow specifying which attributes should always be copied to proxy class by adding attribute type to `AttributesToAlwaysReplicate`. Previously only non-inherited, with `Inherited=false`, attributes were copied. (@shoaibshakeel381, #633) +- Support by-ref-like (`ref struct`) parameter types such as `Span` and `ReadOnlySpan` (@stakx, #664) Bugfixes: - `ArgumentException`: "Could not find method overriding method" with overridden class method having generic by-ref parameter (@stakx, #657) diff --git a/README.md b/README.md index 9756c3ef1f..03198411cb 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,12 @@ Symbol | .NET 4.6.2 | .NET Standard 2.x and ----------------------------------- | ------------------ | ---------------------------- `FEATURE_APPDOMAIN` | :white_check_mark: | :no_entry_sign: `FEATURE_ASSEMBLYBUILDER_SAVE` | :white_check_mark: | :no_entry_sign: +`FEATURE_BYREFLIKE` | :no_entry_sign: | :white_check_mark: (.NET Standard 2.0 excluded) `FEATURE_SERIALIZATION` | :white_check_mark: | :no_entry_sign: `FEATURE_SYSTEM_CONFIGURATION` | :white_check_mark: | :no_entry_sign: * `FEATURE_APPDOMAIN` - enables support for features that make use of an AppDomain in the host. * `FEATURE_ASSEMBLYBUILDER_SAVE` - enabled support for saving the dynamically generated proxy assembly. +* `FEATURE_BYREFLIKE` - enables support for by-ref-like (`ref struct`) types such as `Span` and `ReadOnlySpan`. * `FEATURE_SERIALIZATION` - enables support for serialization of dynamic proxies and other types. * `FEATURE_SYSTEM_CONFIGURATION` - enables features that use System.Configuration and the ConfigurationManager. diff --git a/buildscripts/common.props b/buildscripts/common.props index 157f3f4433..b24dec8f99 100644 --- a/buildscripts/common.props +++ b/buildscripts/common.props @@ -46,7 +46,7 @@ DEBUG TRACE - TRACE + TRACE;FEATURE_BYREFLIKE TRACE;FEATURE_APPDOMAIN;FEATURE_ASSEMBLYBUILDER_SAVE;FEATURE_SERIALIZATION;FEATURE_SYSTEM_CONFIGURATION @@ -89,7 +89,15 @@ $(NetStandard21Constants) - + + + $(DiagnosticsConstants);$(NetStandard21Constants) + + + + $(NetStandard21Constants) + + diff --git a/ref/Castle.Core-net6.0.cs b/ref/Castle.Core-net6.0.cs index 38e7bed03c..ccf61c902c 100644 --- a/ref/Castle.Core-net6.0.cs +++ b/ref/Castle.Core-net6.0.cs @@ -2469,6 +2469,10 @@ public System.Type CreateInterfaceProxyTypeWithoutTarget(System.Type interfaceTo } [System.Serializable] public sealed class DynamicProxyException : System.Exception { } + public interface IByRefLikeConverterSelector + { + System.Type SelectConverterType(System.Reflection.MethodInfo method, int parameterPosition, System.Type parameterType); + } public interface IChangeProxyTarget { void ChangeInvocationTarget(object target); @@ -2615,6 +2619,7 @@ public ProxyGenerationOptions() { } public ProxyGenerationOptions(Castle.DynamicProxy.IProxyGenerationHook hook) { } public System.Collections.Generic.IList AdditionalAttributes { get; } public System.Type BaseTypeForInterfaceProxy { get; set; } + public Castle.DynamicProxy.IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; } public bool HasMixins { get; } public Castle.DynamicProxy.IProxyGenerationHook Hook { get; set; } public Castle.DynamicProxy.MixinData MixinData { get; } diff --git a/ref/Castle.Core-netstandard2.1.cs b/ref/Castle.Core-netstandard2.1.cs index 1a81b5e911..c376d10406 100644 --- a/ref/Castle.Core-netstandard2.1.cs +++ b/ref/Castle.Core-netstandard2.1.cs @@ -2467,6 +2467,10 @@ public System.Type CreateInterfaceProxyTypeWithoutTarget(System.Type interfaceTo } [System.Serializable] public sealed class DynamicProxyException : System.Exception { } + public interface IByRefLikeConverterSelector + { + System.Type SelectConverterType(System.Reflection.MethodInfo method, int parameterPosition, System.Type parameterType); + } public interface IChangeProxyTarget { void ChangeInvocationTarget(object target); @@ -2613,6 +2617,7 @@ public ProxyGenerationOptions() { } public ProxyGenerationOptions(Castle.DynamicProxy.IProxyGenerationHook hook) { } public System.Collections.Generic.IList AdditionalAttributes { get; } public System.Type BaseTypeForInterfaceProxy { get; set; } + public Castle.DynamicProxy.IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; } public bool HasMixins { get; } public Castle.DynamicProxy.IProxyGenerationHook Hook { get; set; } public Castle.DynamicProxy.MixinData MixinData { get; } diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs index 2d790409d6..c16882444c 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs @@ -131,7 +131,11 @@ protected virtual ClassEmitter BuildClassEmitter(string typeName, Type parentTyp CheckNotGenericTypeDefinition(parentType, nameof(parentType)); CheckNotGenericTypeDefinitions(interfaces, nameof(interfaces)); +#if FEATURE_BYREFLIKE + return new ClassEmitter(Scope, typeName, parentType, interfaces) { ByRefLikeConverterSelector = ProxyGenerationOptions.ByRefLikeConverterSelector }; +#else return new ClassEmitter(Scope, typeName, parentType, interfaces); +#endif } protected void CheckNotGenericTypeDefinition(Type type, string argumentName) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs index 07a658aee4..48dac5bcba 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs @@ -65,6 +65,10 @@ public ClassEmitter(TypeBuilder typeBuilder) { } +#if FEATURE_BYREFLIKE + public IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; } +#endif + public ModuleScope ModuleScope { get { return moduleScope; } diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs index dea87ef9c7..5f3a32a843 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs @@ -18,14 +18,28 @@ namespace Castle.DynamicProxy.Generators.Emitters.SimpleAST using System.Reflection; using System.Reflection.Emit; + using Castle.DynamicProxy.Tokens; + internal class ReferencesToObjectArrayExpression : IExpression { private readonly TypeReference[] args; +#if FEATURE_BYREFLIKE + private readonly MethodInfo proxiedMethod; + private readonly IByRefLikeConverterSelector byRefLikeConverterSelector; + + public ReferencesToObjectArrayExpression(MethodInfo proxiedMethod, IByRefLikeConverterSelector byRefLikeConverterSelector, params TypeReference[] args) + { + this.proxiedMethod = proxiedMethod; + this.byRefLikeConverterSelector = byRefLikeConverterSelector; + this.args = args; + } +#else public ReferencesToObjectArrayExpression(params TypeReference[] args) { this.args = args; } +#endif public void Emit(ILGenerator gen) { @@ -42,6 +56,37 @@ public void Emit(ILGenerator gen) var reference = args[i]; +#if FEATURE_BYREFLIKE + if (reference.Type.IsByRefLike) + { + var converterType = byRefLikeConverterSelector?.SelectConverterType(proxiedMethod, i, reference.Type); + if (converterType != null) + { + // instantiate the by-ref-like value converter: + gen.Emit(OpCodes.Ldtoken, converterType); + gen.Emit(OpCodes.Call, TypeMethods.GetTypeFromHandle); + gen.EmitCall(OpCodes.Call, ActivatorMethods.CreateInstance, null); + + // invoke it: + var boxMethodOnConverter = converterType.GetMethod("Box"); + // (TODO: isn't there a nicer way to figure out whether or not the argument was passed by-ref, + // and then ensure that we end up with the argument's address on the evaluation stack?) + var argumentReference = (ArgumentReference)(reference is IndirectReference ? reference.OwnerReference : reference); + gen.Emit(argumentReference.Type.IsByRef ? OpCodes.Ldarg_S : OpCodes.Ldarga_S, argumentReference.Position); + gen.EmitCall(OpCodes.Callvirt, boxMethodOnConverter, null); + } + else + { + // no by-ref-like value converter is available, fall back to substituting the argument value with `null` + // because the logic further down would attempt to box it (which isn't allowed for by-ref-like values): + gen.Emit(OpCodes.Ldnull); + } + + gen.Emit(OpCodes.Stelem_Ref); + continue; + } +#endif + ArgumentsUtil.EmitLoadOwnerAndReference(reference, gen); if (reference.Type.IsByRef) diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index ccf636a39e..f362a9bf9d 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -221,7 +221,11 @@ private IExpression[] GetCtorArguments(ClassEmitter @class, IExpression proxiedM SelfReference.Self, methodInterceptors ?? interceptors, proxiedMethodTokenExpression, +#if FEATURE_BYREFLIKE + new ReferencesToObjectArrayExpression(MethodToOverride, @class.ByRefLikeConverterSelector, dereferencedArguments) +#else new ReferencesToObjectArrayExpression(dereferencedArguments) +#endif }; } diff --git a/src/Castle.Core/DynamicProxy/IByRefLikeConverterSelector.cs b/src/Castle.Core/DynamicProxy/IByRefLikeConverterSelector.cs new file mode 100644 index 0000000000..37cf3478d0 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/IByRefLikeConverterSelector.cs @@ -0,0 +1,39 @@ +// Copyright 2004-2023 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 FEATURE_BYREFLIKE + +namespace Castle.DynamicProxy +{ + using System; + using System.Reflection; + + /// + /// Provides an extension point that allows proxies to convert by-ref-like argument values + /// on a per method parameter basis. + /// + public interface IByRefLikeConverterSelector + { + /// + /// Selects the converter that should be used to convert by-ref-like argument values of the given method parameter. + /// + /// The method that will be intercepted. + /// The zero-based index of the method parameter for which an argument value is to be converted. + /// The type of the method parameter for which an argument value is to be converted. + /// The type of converter that should be used to convert argument values of the given method parameter. + Type SelectConverterType(MethodInfo method, int parameterPosition, Type parameterType); + } +} + +#endif diff --git a/src/Castle.Core/DynamicProxy/ProxyGenerationOptions.cs b/src/Castle.Core/DynamicProxy/ProxyGenerationOptions.cs index 515de4bef0..ca46d0a87f 100644 --- a/src/Castle.Core/DynamicProxy/ProxyGenerationOptions.cs +++ b/src/Castle.Core/DynamicProxy/ProxyGenerationOptions.cs @@ -126,6 +126,21 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) /// public IInterceptorSelector Selector { get; set; } +#if FEATURE_BYREFLIKE + /// + /// Gets or sets the that should be used by created proxies + /// to determine how to convert argument values of by-ref-like types when transferring them + /// into & out of the array during interception. + /// If set to (which is the default), created proxies will represent by-ref-like + /// arguments as in . + /// + /// You should not modify this property once this instance + /// has been used to create a proxy. + /// + /// + public IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; } +#endif + /// /// Gets or sets the class type from which generated interface proxy types will be derived. /// Defaults to (). @@ -269,6 +284,12 @@ public override bool Equals(object obj) { return false; } +#if FEATURE_BYREFLIKE + if (!Equals(ByRefLikeConverterSelector == null, proxyGenerationOptions.ByRefLikeConverterSelector == null)) + { + return false; + } +#endif if (!Equals(MixinData, proxyGenerationOptions.MixinData)) { return false; @@ -291,6 +312,9 @@ public override int GetHashCode() var result = Hook != null ? Hook.GetType().GetHashCode() : 0; result = 29*result + (Selector != null ? 1 : 0); +#if FEATURE_BYREFLIKE + result = 29*result + (ByRefLikeConverterSelector != null ? 1 : 0); +#endif result = 29*result + MixinData.GetHashCode(); result = 29*result + (BaseTypeForInterfaceProxy != null ? BaseTypeForInterfaceProxy.GetHashCode() : 0); result = 29*result + GetAdditionalAttributesHashCode(); diff --git a/src/Castle.Core/DynamicProxy/Tokens/ActivatorMethods.cs b/src/Castle.Core/DynamicProxy/Tokens/ActivatorMethods.cs new file mode 100644 index 0000000000..2ebef9f559 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Tokens/ActivatorMethods.cs @@ -0,0 +1,24 @@ +// Copyright 2004-2023 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. + +namespace Castle.DynamicProxy.Tokens +{ + using System; + using System.Reflection; + + internal class ActivatorMethods + { + public static readonly MethodInfo CreateInstance = typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) }); + } +}