Skip to content
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

Draft: Add support for by-ref-like (ref struct) parameter types such as Span<T> and ReadOnlySpan<T> #664

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Enhancements:
- Two new generic method overloads `proxyGenerator.CreateClassProxy<TClass>([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<T>` and `ReadOnlySpan<T>` (@stakx, #664)

Bugfixes:
- `ArgumentException`: "Could not find method overriding method" with overridden class method having generic by-ref parameter (@stakx, #657)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` and `ReadOnlySpan<T>`.
* `FEATURE_SERIALIZATION` - enables support for serialization of dynamic proxies and other types.
* `FEATURE_SYSTEM_CONFIGURATION` - enables features that use System.Configuration and the ConfigurationManager.
12 changes: 10 additions & 2 deletions buildscripts/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<PropertyGroup>
<DiagnosticsConstants>DEBUG</DiagnosticsConstants>
<NetStandard20Constants>TRACE</NetStandard20Constants>
<NetStandard21Constants>TRACE</NetStandard21Constants>
<NetStandard21Constants>TRACE;FEATURE_BYREFLIKE</NetStandard21Constants>
<DesktopClrConstants>TRACE;FEATURE_APPDOMAIN;FEATURE_ASSEMBLYBUILDER_SAVE;FEATURE_SERIALIZATION;FEATURE_SYSTEM_CONFIGURATION</DesktopClrConstants>
</PropertyGroup>

Expand Down Expand Up @@ -89,7 +89,15 @@
<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='netcoreapp3.1|Release'">
<DefineConstants>$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>


<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='net6.0|Debug'">
<DefineConstants>$(DiagnosticsConstants);$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='net6.0|Release'">
<DefineConstants>$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
<None Include="$(SolutionDir)docs\images\castle-logo.png" Pack="true" PackagePath=""/>
</ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions ref/Castle.Core-net6.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2469,6 +2469,10 @@ public class DefaultProxyBuilder : Castle.DynamicProxy.IProxyBuilder
}
[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);
Expand Down Expand Up @@ -2615,6 +2619,7 @@ public class ProxyGenerationOptions
public ProxyGenerationOptions(Castle.DynamicProxy.IProxyGenerationHook hook) { }
public System.Collections.Generic.IList<Castle.DynamicProxy.CustomAttributeInfo> 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; }
Expand Down
5 changes: 5 additions & 0 deletions ref/Castle.Core-netstandard2.1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2467,6 +2467,10 @@ public class DefaultProxyBuilder : Castle.DynamicProxy.IProxyBuilder
}
[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);
Expand Down Expand Up @@ -2613,6 +2617,7 @@ public class ProxyGenerationOptions
public ProxyGenerationOptions(Castle.DynamicProxy.IProxyGenerationHook hook) { }
public System.Collections.Generic.IList<Castle.DynamicProxy.CustomAttributeInfo> 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; }
Expand Down
4 changes: 4 additions & 0 deletions src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public ClassEmitter(TypeBuilder typeBuilder)
{
}

#if FEATURE_BYREFLIKE
public IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; }
#endif

public ModuleScope ModuleScope
{
get { return moduleScope; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}

Expand Down
39 changes: 39 additions & 0 deletions src/Castle.Core/DynamicProxy/IByRefLikeConverterSelector.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides an extension point that allows proxies to convert by-ref-like argument values
/// on a per method parameter basis.
/// </summary>
public interface IByRefLikeConverterSelector
{
/// <summary>
/// Selects the converter that should be used to convert by-ref-like argument values of the given method parameter.
/// </summary>
/// <param name="method">The method that will be intercepted.</param>
/// <param name="parameterPosition">The zero-based index of the method parameter for which an argument value is to be converted.</param>
/// <param name="parameterType">The type of the method parameter for which an argument value is to be converted.</param>
/// <returns>The type of converter that should be used to convert argument values of the given method parameter.</returns>
Type SelectConverterType(MethodInfo method, int parameterPosition, Type parameterType);
}
}

#endif
24 changes: 24 additions & 0 deletions src/Castle.Core/DynamicProxy/ProxyGenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
/// </summary>
public IInterceptorSelector Selector { get; set; }

#if FEATURE_BYREFLIKE
/// <summary>
/// Gets or sets the <see cref="IByRefLikeConverterSelector"/> that should be used by created proxies
/// to determine how to convert argument values of by-ref-like types when transferring them
/// into &amp; out of the <see cref="IInvocation.Arguments"/> array during interception.
/// If set to <see langword="null"/> (which is the default), created proxies will represent by-ref-like
/// arguments as <see langword="null"/> in <see cref="IInvocation.Arguments"/>.
/// <para>
/// You should not modify this property once this <see cref="ProxyGenerationOptions"/> instance
/// has been used to create a proxy.
/// </para>
/// </summary>
public IByRefLikeConverterSelector ByRefLikeConverterSelector { get; set; }
#endif

/// <summary>
/// Gets or sets the class type from which generated interface proxy types will be derived.
/// Defaults to <c><see langword="typeof"/>(<see langword="object"/>)</c>.
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down
24 changes: 24 additions & 0 deletions src/Castle.Core/DynamicProxy/Tokens/ActivatorMethods.cs
Original file line number Diff line number Diff line change
@@ -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) });
}
}