WORK IN PROGRESS
Finding the place for the cut.
Current stack of execution for the type's custom attribute copy is (from the bottom up):
Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Type type = {Name = "CustomTestAttribute" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+CustomTestAttribute"}, object[] ctorArgs = {Dimensions:[0]}, System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 688 |
Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Type type = {Name = "CustomTestAttribute" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+CustomTestAttribute"}, System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 772 |
Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 788 |
Spring.Core.dll!Spring.Proxy.AbstractProxyTypeBuilder.ApplyTypeAttributes(System.Reflection.Emit.TypeBuilder typeBuilder = {Name = "CompositionAopProxy_6a4ecf7bdbd8440396704c96dd555e9e" FullName = "CompositionAopProxy_6a4ecf7bdbd8440396704c96dd555e9e"}, System.Type targetType = {Name = "WcfServiceImpl" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl"}) Line 238 |
Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.CompositionAopProxyTypeBuilder.BuildProxyType() Line 84 |
Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.CachedAopProxyFactory.BuildProxyType(Spring.Proxy.IProxyTypeBuilder typeBuilder = {Spring.Aop.Framework.DynamicProxy.CompositionAopProxyTypeBuilder}) Line 79 |
Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.DefaultAopProxyFactory.CreateAopProxy(Spring.Aop.Framework.AdvisedSupport advisedSupport = {Spring.Aop.Framework.ProxyFactory: 1 interfaces=[[Spring.Wcf.Services.WcfServiceProxyFactoryTests+IWcfService] -> target[Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl]]; 1 pointcuts=[DefaultPointcutAdvisor: pointcut=[TruePointcut.TRUE] advice=[Spring.Aop.Interceptor.NopInterceptor]]; targetSource=[Singleton target source (not dynamic): target=[Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl]]; advisorChainFactory=Spring.Aop.Framework.HashtableCachingAdvisorChainFactory; proxyTargetType=False; proxyTargetAttributes=True; exposeProxy=False; isFrozen=False; optimize=False; aopProxyFactory=Spring.Aop.Framework.DynamicProxy.CachedAopProxyFactory; }) Line 91 |
Spring.Aop.dll!Spring.Aop.Framework.AdvisedSupport.CreateAopProxy() Line 1391 |
Spring.Aop.dll!Spring.Aop.Framework.ProxyFactory.GetProxy() Line 92 |
Spring.Wcf.Tests.exe!Spring.Wcf.Services.WcfServiceProxyFactoryTests.TestWcfBasicHttpBindingWithAop() Line 101 |
Spring.Wcf.Tests.exe!Spring.Program.Main(string[] args = {Dimensions:[0]}) Line 15 |
Walking the stack of the test execution brings us to the ApplyTypeAttributes method of \Spring.Core\Proxy\AbstractProxyTypeBuilder.cs. Its signature is equivalent to the signature of the method created during the proof of concept work:
CopyTypeAttributes(Type source, TypeBuilder target)
With parameters named in quite an opposite way but meaning the same as those in ApplyTypeAttributes.
The idea was to have a less granular API than the current of the ReflectionUtils, with few reasons behind it.
ApplyTypeAttributes signature and body:
{
foreach (object attr in GetTypeAttributes(targetType))
{
if (attr is CustomAttributeBuilder)
{
typeBuilder.SetCustomAttribute((CustomAttributeBuilder)attr);
}
else if (attr is Attribute)
{
typeBuilder.SetCustomAttribute(
ReflectionUtils.CreateCustomAttribute((Attribute) attr));
}
}
}
lends itself to the natural application of a Mono.Cecil proof of concept method created.
Sequence of changes.
- Creating ReflectionUtils2 class side by side with a ReflectionUtils.
- Changing the signature of the method to be
CopyTypeAttributes(TypeBuilder typeBuilder, Type targetType)
So it just matches fully the current convention of the ApplyTypeAttributes method.
- Adding Mono.Cecil.dll into the C:\DEV\OpenSource\Spring.Net\lib\Net\2.0 folder and creating the reference to it from the Spring.Core project.
Pitfalls.
- Nested types.
Nested types FullName notation is as: Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl
Mono.Cecil notation for the same is:
Spring.Wcf.Services.WcfServiceProxyFactoryTests/WcfServiceImpl
For nested types which refer to the nested attributes the Mono.Cecil scope is different than for the regular classes (and namespace value is empty). That is to be communicated with the Mono.Cecil author if he is interested, but workaround is very simple:
if (typeReference.Scope is Mono.Cecil.ModuleReference)
{
return typeReference.Module.Assembly.Name.ToString();
}
if (typeReference.Scope is Mono.Cecil.AssemblyNameReference)
{
return typeReference.Scope.ToString();
}
// TODO: Use resource string, but Spring.NET is not using resources... ask Mark/Bruno (SD)
throw new NotImplementedException("GetAssemblyFullName method is not implemented " +
" to work for the {0} type of scope. Only supported scopes are " +
"Mono.Cecil.ModuleReference, Mono.Cecil.AssemblyNameReference");
- System and GAC'ed types
I really expected an issue to happen when copying attributes from the system or GAC'ed types, but there was no problem with that as we fed to Mono.Cecil the location of system or GAC'ed assemblies as:
System.Type - C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll |
System.Web.Services.WebService - C:\WINDOWS\assembly\GAC_MSIL\System.Web.Services\2.0.0.0__b03f5f7f11d50a3a\System. Web.Services.dll |
- Security
Though as follows from the above note on GAC'ed and System types there will be security implications for access rights to read those dlls. Normally the service account would have those rights (as those in the Worker Process group) but still, in case when it is impersonated account that is coming and we read assembly in this context it may cause a problem then.
Though the same constraints and conditions apply as in case of ASP.NET apps (or any) for example.
- Performance
It takes quite a while to Mono.Cecil to read through the System.Web.Services.dll assembly (less then 1 minute but close to it). Mono.Cecil.AssemblyFactory.GetAssembly method parses all of the types inside the assembly. Lets see if there is any way how to limit it only to reading the metadata for type we are interested into...
And I'm quite sorry to Cecil! It turns out that I had tracepoints all over the place onto Cecil's code and that caused quite a delay when it ran. I tried to profile it (no debugger then) and it was just instant, I removed the tracepoints and again it is just instant on the mscorlib even. Me bad! Though as now I can get into the milliseconds copy attributes for System.Type, it shows that there is a problem for the ComDefaultInterface(Type) to be copied.
- Compiler resolved values
We do use text editor when we create the code, which means that we are pretty limited into what we can put into our custom attributes constructor, as it should be pretty static content. There is a twist though, as compiler can still do some static resolutions!
What happens here is that for the following definition of the Type attributes, the ComDefaultInterfaceAttribute is applied with typeof resolved during the compile time.
[Serializable, ComDefaultInterface(typeof(_Type)), ComVisible(true), ClassInterface(ClassInterfaceType.None)] public abstract class Type : MemberInfo, _Type, IReflect {
I believe that it is the only compiler resolvable entity right now in .NET. It means that it is still static, though question is what are metadata available to us to bring it back to life as Type. Lets step back a bit and add the scenario to the tests!
Ok, added, and as one could expect it is the fully qualified type name, yupiee anyway :).
if (!attribute.Resolved) attribute.Resolve();
To be continued tomorrow....
No comments:
Post a Comment