/// <summary> /// Returns an EventAttribute for the Completed or Faulted events for a call context. /// </summary> /// <param name="baseAttribute">The EventAttribute for the method call that should be copied.</param> /// <param name="context">The context of the call.</param> /// <param name="nextEventId">The next event ID to use if not specified by some other mechanism.</param> /// <returns>The EventAttribute for the call context.</returns> public virtual EventAttribute CopyEventAttribute(EventAttribute baseAttribute, InvocationContext context, int nextEventId) { if (baseAttribute == null) throw new ArgumentNullException("baseAttribute"); if (context == null) throw new ArgumentNullException("context"); return new EventAttribute(nextEventId) { Keywords = baseAttribute.Keywords, Level = GetEventLevelForContext(context, baseAttribute), Message = GetEventMessage(context), Opcode = baseAttribute.Opcode, Task = baseAttribute.Task, Version = baseAttribute.Version }; }
/// <summary> /// Determines whether the given invocation context requires context to be provided. /// </summary> /// <param name="context">The context of the invocation.</param> /// <returns>True if EventSourceProxy should ask for context, false to skip context generation.</returns> public virtual bool ShouldProvideContext(InvocationContext context) { if (context == null) throw new ArgumentNullException("context"); // NOTE: this method is called at proxy generation time and is not called at runtime // so we don't need to cache anything here // if this returns false, then ProvideContext will never be called for the given context // check the method first var attribute = context.MethodInfo.GetCustomAttribute<TraceContextAttribute>(); if (attribute != null) return attribute.EnabledFor.HasFlag(context.ContextType); // now check the class attribute = context.MethodInfo.DeclaringType.GetCustomAttribute<TraceContextAttribute>(); if (attribute != null) return attribute.EnabledFor.HasFlag(context.ContextType); // it's enabled by default return true; }
/// <summary> /// Emits a _Faulted version of a given event that logs the result of an operation. /// The _Completed event is used by TracingProxy to signal an exception in a method call. /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="beginMethod">The begin method for this interface call.</param> /// <param name="eventId">The next available event ID.</param> /// <param name="autoKeyword">The auto-keyword to use if enabled.</param> /// <returns>The MethodBuilder for the method.</returns> private MethodBuilder EmitMethodFaultedImpl(InvocationContext invocationContext, MethodInfo beginMethod, ref int eventId, EventKeywords autoKeyword) { return EmitMethodComplementImpl(invocationContext.SpecifyType(InvocationContextTypes.MethodFaulted), FaultedSuffix, typeof(Exception), beginMethod, ref eventId, autoKeyword, null); }
/// <summary> /// Emits a _Completed version of a given event that logs the result of an operation. /// The _Completed event is used by TracingProxy to signal the end of a method call. /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="beginMethod">The begin method for this interface call.</param> /// <param name="eventId">The next available event ID.</param> /// <param name="autoKeyword">The auto-keyword to use if enabled.</param> /// <param name="faultedMethod">A faulted method to call or null if no other faulted method is available.</param> /// <returns>The MethodBuilder for the method.</returns> private MethodBuilder EmitMethodCompletedImpl(InvocationContext invocationContext, MethodInfo beginMethod, ref int eventId, EventKeywords autoKeyword, MethodBuilder faultedMethod) { return EmitMethodComplementImpl(invocationContext.SpecifyType(InvocationContextTypes.MethodCompletion), CompletedSuffix, invocationContext.MethodInfo.ReturnType, beginMethod, ref eventId, autoKeyword, faultedMethod); }
/// <summary> /// Emits a method to complement an interface method. The complement method will have a suffix such as _Completed, /// and will take one parameter. /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="suffix">The suffix to use on the method.</param> /// <param name="parameterType">The type of the parameter of the method.</param> /// <param name="beginMethod">The begin method for this interface call.</param> /// <param name="eventId">The next available event ID.</param> /// <param name="autoKeyword">The auto-keyword to use if enabled.</param> /// <param name="faultedMethod">A faulted method to call or null if no other faulted method is available.</param> /// <returns>The MethodBuilder for the method.</returns> private MethodBuilder EmitMethodComplementImpl(InvocationContext invocationContext, string suffix, Type parameterType, MethodInfo beginMethod, ref int eventId, EventKeywords autoKeyword, MethodBuilder faultedMethod) { var interfaceMethod = invocationContext.MethodInfo; // if there is a NonEvent attribute, then no need to emit this method if (interfaceMethod.GetCustomAttribute<NonEventAttribute>() != null) return null; // if the method ends in _Completed, then don't emit another one if (interfaceMethod.Name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) return null; var methodName = beginMethod.Name + suffix; // if the interface already has a _Completed method, don't emit a new one var parameterTypes = parameterType == typeof(void) ? Type.EmptyTypes : new Type[] { parameterType }; if (interfaceMethod.DeclaringType.GetMethod(methodName, parameterTypes) != null) return null; var targetParameterType = GetTypeSupportedByEventSource(parameterType); var targetParameters = parameterTypes.Select(t => TypeIsSupportedByEventSource(t) ? t : typeof(string)).ToList(); bool supportsContext = SupportsContext(invocationContext); if (supportsContext) targetParameters.Add(typeof(string)); var targetParameterTypes = targetParameters.ToArray(); // determine if this is a non-event or an event // if an event, but there is no event attribute, just add one to the last event id EventAttribute startEventAttribute = interfaceMethod.GetCustomAttribute<EventAttribute>() ?? new EventAttribute(eventId); EventAttribute eventAttribute = _eventAttributeProvider.CopyEventAttribute(startEventAttribute, invocationContext, eventId); eventId = Math.Max(eventId, eventAttribute.EventId + 1); if (eventAttribute.Keywords == EventKeywords.None) eventAttribute.Keywords = autoKeyword; // if we have a return type, then we need to implement two methods if (parameterTypes.Length == 1) { // emit the internal method MethodBuilder m = _typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(void), targetParameterTypes); m.SetCustomAttribute(EventAttributeHelper.ConvertEventAttributeToAttributeBuilder(eventAttribute)); EmitCallWriteEvent(invocationContext, m, eventAttribute, targetParameterTypes, targetParameterTypes); // emit an overloaded wrapper method that calls the method when it's enabled // note this is a non-event so EventSource doesn't try to log it MethodBuilder im = _typeBuilder.DefineMethod(methodName, MethodAttributes.Public); ProxyHelper.CopyGenericSignature(interfaceMethod, im); im.SetReturnType(parameterTypes[0]); im.SetParameters(parameterTypes); // mark the method as a non-event im.SetCustomAttribute(EventAttributeHelper.CreateNonEventAttribute()); // put the return value on the stack so we can return the value as a passthrough im.GetILGenerator().Emit(OpCodes.Ldarg_1); if (EmitIsEnabled(im, eventAttribute)) { EmitTaskCompletion(im, parameterType, faultedMethod); EmitDirectProxy(invocationContext, im, m, parameterTypes, targetParameterTypes); } return im; } else { // the method does not have a return value // so create the internal method that calls WriteEvent MethodBuilder m = _typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(void), targetParameterTypes); m.SetCustomAttribute(EventAttributeHelper.ConvertEventAttributeToAttributeBuilder(eventAttribute)); ProxyHelper.EmitDefaultValue(m.GetILGenerator(), m.ReturnType); if (EmitIsEnabled(m, eventAttribute)) EmitCallWriteEvent(invocationContext, m, eventAttribute, targetParameterTypes, targetParameterTypes); return m; } }
/// <summary> /// Emits a proxy to a method that just calls the base method. /// </summary> /// <param name="invocationContext">The current invocation context.</param> /// <param name="methodBuilder">The method to implement.</param> /// <param name="baseMethod">The base method.</param> /// <param name="sourceParameterTypes">The types of the parameters on the source method.</param> /// <param name="targetParameterTypes">The types of the parameters on the target method.</param> private void EmitDirectProxy(InvocationContext invocationContext, MethodBuilder methodBuilder, MethodInfo baseMethod, Type[] sourceParameterTypes, Type[] targetParameterTypes) { /* * This method assume that a default return value has been pushed on the stack. * * base(params); * return (top of stack); */ ILGenerator mIL = methodBuilder.GetILGenerator(); // copy the parameters to the stack // arg.0 = this // so we go to length+1 mIL.Emit(OpCodes.Ldarg_0); for (int i = 0; i < sourceParameterTypes.Length; i++) { ProxyHelper.EmitSerializeValue( methodBuilder, invocationContext, _invocationContexts, _invocationContextsField, i, sourceParameterTypes[i], targetParameterTypes[i], _serializationProvider, _serializationProviderField); } // if this method supports context, then add a context parameter // note that we pass null in here and then build the context from within EmitCallWriteEvent if (SupportsContext(invocationContext)) mIL.Emit(OpCodes.Ldnull); // now that all of the parameters have been loaded, call the base method mIL.Emit(OpCodes.Call, baseMethod); mIL.Emit(OpCodes.Ret); }
/// <summary> /// Emits an implementation of a given method. /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="eventId">The next eventID to use.</param> /// <param name="autoKeyword">The auto-keyword to use if enabled.</param> /// <returns>The method that is implemented.</returns> private MethodBuilder EmitMethodImpl(InvocationContext invocationContext, ref int eventId, EventKeywords autoKeyword) { // get the method we are implementing and the parameter mapping var interfaceMethod = invocationContext.MethodInfo; var parameterMapping = _traceParameterProvider.ProvideParameterMapping(invocationContext.MethodInfo).Where(p => p.HasSource).ToList(); // if we are implementing an interface, then add an string context parameter if (SupportsContext(invocationContext)) parameterMapping.Add(new ParameterMapping(Context)); // calculate the method name // if there is more than one method with the given name, then append an ID to it var methodName = interfaceMethod.Name; var matchingMethods = interfaceMethod.DeclaringType.GetMethods().AsEnumerable().Where(im => String.Compare(im.Name, methodName, StringComparison.OrdinalIgnoreCase) == 0).ToArray(); if (matchingMethods.Length > 1) methodName += "_" + Array.IndexOf(matchingMethods, interfaceMethod).ToString(CultureInfo.InvariantCulture); // determine if this is a non-event or an event // if an event, but there is no event attribute, just add one to the last event id EventAttribute eventAttribute = null; if (interfaceMethod.GetCustomAttribute<NonEventAttribute>() == null) { eventAttribute = _eventAttributeProvider.GetEventAttribute(invocationContext, eventId); eventId = Math.Max(eventId, eventAttribute.EventId + 1); } // if auto-keywords are enabled, use them if (eventAttribute != null && eventAttribute.Keywords == EventKeywords.None) eventAttribute.Keywords = autoKeyword; // create the internal method that calls WriteEvent // this cannot be virtual or static, or the manifest builder will skip it // it also cannot return a value MethodBuilder m = _typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(void), parameterMapping.Select(p => p.CleanTargetType).ToArray()); // copy the Event or NonEvent attribute from the interface if (eventAttribute != null) m.SetCustomAttribute(EventAttributeHelper.ConvertEventAttributeToAttributeBuilder(eventAttribute)); else m.SetCustomAttribute(EventAttributeHelper.CreateNonEventAttribute()); // add the parameter names for (int i = 0; i < parameterMapping.Count; i++) { var parameter = parameterMapping[i]; m.DefineParameter(i + 1, ParameterAttributes.In, parameter.Name); } if (interfaceMethod.IsAbstract || !interfaceMethod.DeclaringType.IsSubclassOf(typeof(EventSource))) { // for interface methods, implement a call to write event ProxyHelper.EmitDefaultValue(m.GetILGenerator(), m.ReturnType); if (EmitIsEnabled(m, eventAttribute)) EmitCallWriteEvent(invocationContext, m, eventAttribute, parameterMapping); // since EventSource only accepts non-virtual methods, and we need a virtual method to implement the abstract method // we need to implement a wrapper method on the interface that calls into the base method // and handles the bundling/unbundling of parameters MethodBuilder im = _typeBuilder.DefineMethod("_" + methodName, MethodAttributes.Public | MethodAttributes.Virtual); ProxyHelper.CopyMethodSignature(interfaceMethod, im); ProxyHelper.EmitDefaultValue(im.GetILGenerator(), im.ReturnType); if (EmitIsEnabled(im, eventAttribute)) EmitDirectProxy(invocationContext, im, m, parameterMapping); // if this is an interface, then tell the system to map our method to the interface implementation if (interfaceMethod.IsAbstract) _typeBuilder.DefineMethodOverride(im, interfaceMethod); } else { // we are implementing a non-abstract method in event source, then // all we can do is call the base implementation EmitDirectProxy(invocationContext, m, interfaceMethod, parameterMapping); } return m; }
/// <summary> /// Emits the code needed to properly push an object on the stack, /// serializing the value if necessary. /// </summary> /// <param name="typeBuilder">The TypeBuilder for the method being built.</param> /// <param name="methodBuilder">The method currently being built.</param> /// <param name="invocationContext">The invocation context for this call.</param> /// <param name="invocationContexts">A list of invocation contexts that will be appended to.</param> /// <param name="invocationContextsField">The static field containing the array of invocation contexts at runtime.</param> /// <param name="i">The index of the current parameter being pushed.</param> /// <param name="sourceType">The type that the parameter is being converted from.</param> /// <param name="targetType">The type that the parameter is being converted to.</param> /// <param name="converter">An optional converter to apply to the source type.</param> /// <param name="serializationProvider">The serialization provider for the current interface.</param> /// <param name="serializationProviderField"> /// The field on the current object that contains the serialization provider at runtime. /// This method assume the current object is stored in arg.0. /// </param> internal static void EmitSerializeValue( TypeBuilder typeBuilder, MethodBuilder methodBuilder, InvocationContext invocationContext, List<InvocationContext> invocationContexts, FieldBuilder invocationContextsField, int i, Type sourceType, Type targetType, LambdaExpression converter, TraceSerializationProvider serializationProvider, FieldBuilder serializationProviderField) { ILGenerator mIL = methodBuilder.GetILGenerator(); // if the source is a parameter, then load the parameter onto the stack if (i >= 0) mIL.Emit(OpCodes.Ldarg, i + 1); // if a converter is passed in, then define a static method and use it to convert if (converter != null) { MethodBuilder mb = typeBuilder.DefineMethod(Guid.NewGuid().ToString(), MethodAttributes.Static | MethodAttributes.Public, converter.ReturnType, converter.Parameters.Select(p => p.Type).ToArray()); converter.CompileToMethod(mb); mIL.Emit(OpCodes.Call, mb); // the object on the stack is now the return type. we may need to convert it further sourceType = converter.ReturnType; } // if the source type is a reference to the target type, we have to dereference it if (sourceType.IsByRef && sourceType.GetElementType() == targetType) { sourceType = sourceType.GetElementType(); mIL.Emit(OpCodes.Ldobj, sourceType); return; } // if the types match, just put the argument on the stack if (sourceType == targetType) return; // this is not a match, so convert using the serializer. // verify that the target type is a string if (targetType != typeof(string)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot convert type {0} to a type compatible with EventSource", targetType.FullName)); // for fundamental types, just convert them with ToString and be done with it var underlyingType = Nullable.GetUnderlyingType(sourceType) ?? sourceType; if (!sourceType.IsGenericParameter && (underlyingType.IsEnum || (underlyingType.IsValueType && underlyingType.Assembly == typeof(string).Assembly))) { // convert the argument to a string with ToString LocalBuilder lb = mIL.DeclareLocal(sourceType); mIL.Emit(OpCodes.Stloc, lb.LocalIndex); mIL.Emit(OpCodes.Ldloca, lb.LocalIndex); mIL.Emit(OpCodes.Call, sourceType.GetMethod("ToString", Type.EmptyTypes)); return; } // non-fundamental types use the object serializer var context = new TraceSerializationContext(invocationContext, i); context.EventLevel = serializationProvider.GetEventLevelForContext(context); if (context.EventLevel != null) { LocalBuilder lb = mIL.DeclareLocal(sourceType); mIL.Emit(OpCodes.Stloc, lb.LocalIndex); // get the object serializer from the this pointer mIL.Emit(OpCodes.Ldsfld, serializationProviderField); mIL.Emit(OpCodes.Ldloc, lb.LocalIndex); // if the source type is a reference to the target type, we have to dereference it if (sourceType.IsByRef) { sourceType = sourceType.GetElementType(); mIL.Emit(OpCodes.Ldobj, sourceType); } // if it's a value type, we have to box it to log it if (sourceType.IsGenericParameter || sourceType.IsValueType) mIL.Emit(OpCodes.Box, sourceType); // get the invocation context from the array on the provider mIL.Emit(OpCodes.Ldsfld, invocationContextsField); mIL.Emit(OpCodes.Ldc_I4, invocationContexts.Count); mIL.Emit(OpCodes.Ldelem, typeof(TraceSerializationContext)); invocationContexts.Add(context); mIL.Emit(OpCodes.Callvirt, typeof(TraceSerializationProvider).GetMethod("ProvideSerialization", BindingFlags.Instance | BindingFlags.Public)); } else { mIL.Emit(OpCodes.Pop); mIL.Emit(OpCodes.Ldnull); } }
/// <summary> /// Determines whether the given invocation supports a context provider. /// </summary> /// <param name="invocationContext">The current InvocationContext.</param> /// <returns>True if the context provider should be invoked for the context.</returns> private bool SupportsContext(InvocationContext invocationContext) { return _contextProvider != null && _contextProvider.ShouldProvideContext(invocationContext); }
/// <summary> /// Returns an EventAttribute for the given call context. /// </summary> /// <param name="context">The context of the call.</param> /// <param name="nextEventId">The next event ID to use if not specified by some other mechanism.</param> /// <returns>The EventAttribute for the call context.</returns> public virtual EventAttribute GetEventAttribute(InvocationContext context, int nextEventId) { if (context == null) throw new ArgumentNullException("context"); EventAttribute eventAttribute = context.MethodInfo.GetCustomAttribute<EventAttribute>(); if (eventAttribute != null) return eventAttribute; return new EventAttribute(nextEventId) { Level = GetEventLevelForContext(context, null), Message = GetEventMessage(context) }; }
/// <summary> /// Gets the message for an event. /// </summary> /// <param name="context">The context of the call.</param> /// <returns>The message for the event.</returns> protected virtual string GetEventMessage(InvocationContext context) { if (context == null) throw new ArgumentNullException("context"); switch (context.ContextType) { case InvocationContextTypes.MethodCall: return String.Join(" ", Enumerable.Range(0, context.MethodInfo.GetParameters().Length).Select(i => String.Format(CultureInfo.InvariantCulture, "{{{0}}}", i))); case InvocationContextTypes.MethodFaulted: return "{0}"; case InvocationContextTypes.MethodCompletion: if (context.MethodInfo.ReturnType != typeof(void)) return "{0}"; else return String.Empty; } return String.Empty; }
/// <summary> /// Gets the appropriate EventLevel for the call context. /// </summary> /// <param name="context">The context of the call.</param> /// <param name="baseAttribute">The base attribute to copy if there are no additional attributes.</param> /// <returns>The EventLevel for the call context.</returns> protected virtual EventLevel GetEventLevelForContext(InvocationContext context, EventAttribute baseAttribute) { if (context == null) throw new ArgumentNullException("context"); // for faulted methods, allow the EventExceptionAttribute to override the event level if (context.ContextType == InvocationContextTypes.MethodFaulted) { var attribute = context.MethodInfo.GetCustomAttribute<EventExceptionAttribute>(); if (attribute != null) return attribute.Level; attribute = context.MethodInfo.DeclaringType.GetCustomAttribute<EventExceptionAttribute>(); if (attribute != null) return attribute.Level; return ExceptionEventLevel; } // check for an attribute on the type var implementationAttribute = context.MethodInfo.DeclaringType.GetCustomAttribute<EventSourceImplementationAttribute>(); if (implementationAttribute != null && implementationAttribute.Level.HasValue) return implementationAttribute.Level.Value; if (baseAttribute != null) return baseAttribute.Level; return EventLevel; }
/// <summary> /// Emits the code needed to properly push an object on the stack, /// serializing the value if necessary. /// </summary> /// <param name="methodBuilder">The method currently being built.</param> /// <param name="invocationContext">The invocation context for this call.</param> /// <param name="invocationContexts">A list of invocation contexts that will be appended to.</param> /// <param name="invocationContextsField">The static field containing the array of invocation contexts at runtime.</param> /// <param name="i">The index of the current parameter being pushed.</param> /// <param name="sourceType">The type that the parameter is being converted from.</param> /// <param name="targetType">The type that the parameter is being converted to.</param> /// <param name="serializationProvider">The serialization provider for the current interface.</param> /// <param name="serializationProviderField"> /// The field on the current object that contains the serialization provider at runtime. /// This method assume the current object is stored in arg.0. /// </param> internal static void EmitSerializeValue( MethodBuilder methodBuilder, InvocationContext invocationContext, List<InvocationContext> invocationContexts, FieldBuilder invocationContextsField, int i, Type sourceType, Type targetType, TraceSerializationProvider serializationProvider, FieldBuilder serializationProviderField) { ILGenerator mIL = methodBuilder.GetILGenerator(); // if the source type is a reference to the target type, we have to dereference it if (sourceType.IsByRef && sourceType.GetElementType() == targetType) { mIL.Emit(OpCodes.Ldarg, (int)i + 1); sourceType = sourceType.GetElementType(); mIL.Emit(OpCodes.Ldobj, sourceType); return; } // if the types match, just put the argument on the stack if (sourceType == targetType) { mIL.Emit(OpCodes.Ldarg, (int)i + 1); return; } // this is not a match, so convert using the serializer. // verify that the target type is a string if (targetType != typeof(string)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot convert type {0} to a type compatible with EventSource", targetType.FullName)); // for fundamental types, just convert them with ToString and be done with it var underlyingType = Nullable.GetUnderlyingType(sourceType) ?? sourceType; if (!sourceType.IsGenericParameter && (underlyingType.IsEnum || (underlyingType.IsValueType && underlyingType.Assembly == typeof(string).Assembly))) { // convert the argument to a string with ToString mIL.Emit(OpCodes.Ldarga_S, i + 1); mIL.Emit(OpCodes.Call, sourceType.GetMethod("ToString", Type.EmptyTypes)); return; } // non-fundamental types use the object serializer var context = new TraceSerializationContext(invocationContext, i); context.EventLevel = serializationProvider.GetEventLevelForContext(context); if (context.EventLevel != null) { // get the object serializer from the this pointer mIL.Emit(OpCodes.Ldsfld, serializationProviderField); // load the value mIL.Emit(OpCodes.Ldarg, (int)i + 1); // if the source type is a reference to the target type, we have to dereference it if (sourceType.IsByRef) { sourceType = sourceType.GetElementType(); mIL.Emit(OpCodes.Ldobj, sourceType); } // if it's a value type, we have to box it to log it if (sourceType.IsGenericParameter || sourceType.IsValueType) mIL.Emit(OpCodes.Box, sourceType); // get the invocation context from the array on the provider mIL.Emit(OpCodes.Ldsfld, invocationContextsField); mIL.Emit(OpCodes.Ldc_I4, invocationContexts.Count); mIL.Emit(OpCodes.Ldelem, typeof(TraceSerializationContext)); invocationContexts.Add(context); mIL.Emit(OpCodes.Callvirt, typeof(TraceSerializationProvider).GetMethod("ProvideSerialization", BindingFlags.Instance | BindingFlags.Public)); } else mIL.Emit(OpCodes.Ldnull); }
/// <summary> /// Provides context information, such as security context, for a trace session. /// </summary> /// <param name="context">The context of the current invocation.</param> /// <returns>A string representing the current context.</returns> public abstract string ProvideContext(InvocationContext context);
/// <summary> /// Emits an implementation of a given method. /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="eventId">The next eventID to use.</param> /// <param name="autoKeyword">The auto-keyword to use if enabled.</param> /// <returns>The method that is implemented.</returns> private MethodBuilder EmitMethodImpl(InvocationContext invocationContext, ref int eventId, EventKeywords autoKeyword) { var interfaceMethod = invocationContext.MethodInfo; // look at the parameters on the interface var parameters = interfaceMethod.GetParameters(); var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); // some types aren't supported in event source. we will convert them to strings var targetParameters = parameterTypes.Select(t => GetTypeSupportedByEventSource(t)).ToList(); // if we are implementing an interface, then add an string context parameter bool supportsContext = SupportsContext(invocationContext); if (supportsContext) targetParameters.Add(typeof(string)); var targetParameterTypes = targetParameters.ToArray(); // calculate the method name // if there is more than one method with the given name, then append an ID to it var methodName = interfaceMethod.Name; var matchingMethods = interfaceMethod.DeclaringType.GetMethods().AsEnumerable().Where(im => String.Compare(im.Name, methodName, StringComparison.OrdinalIgnoreCase) == 0).ToArray(); if (matchingMethods.Length > 1) methodName += "_" + Array.IndexOf(matchingMethods, interfaceMethod).ToString(CultureInfo.InvariantCulture); // determine if this is a non-event or an event // if an event, but there is no event attribute, just add one to the last event id EventAttribute eventAttribute = null; if (interfaceMethod.GetCustomAttribute<NonEventAttribute>() == null) { eventAttribute = _eventAttributeProvider.GetEventAttribute(invocationContext, eventId); eventId = Math.Max(eventId, eventAttribute.EventId + 1); } // if auto-keywords are enabled, use them if (eventAttribute != null && eventAttribute.Keywords == EventKeywords.None) eventAttribute.Keywords = autoKeyword; // create the internal method that calls WriteEvent // this cannot be virtual or static, or the manifest builder will skip it // it also cannot return a value MethodBuilder m = _typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(void), targetParameterTypes); // copy the Event or NonEvent attribute from the interface if (eventAttribute != null) m.SetCustomAttribute(EventAttributeHelper.ConvertEventAttributeToAttributeBuilder(eventAttribute)); else m.SetCustomAttribute(EventAttributeHelper.CreateNonEventAttribute()); // add the parameter names for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; m.DefineParameter(i + 1, parameter.Attributes, parameter.Name); } // add the context parameter if (supportsContext) m.DefineParameter(parameters.Length + 1, ParameterAttributes.In, Context); if (interfaceMethod.IsAbstract) { // for interface methods, implement a call to write event EmitCallWriteEvent(invocationContext, m, eventAttribute, parameterTypes, targetParameterTypes); // since EventSource only accepts non-virtual methods, and we need a virtual method to implement the abstract method // we need to implement a wrapper method on the interface that calls into the base method MethodBuilder im = _typeBuilder.DefineMethod("_" + methodName, MethodAttributes.Public | MethodAttributes.Virtual); ProxyHelper.CopyMethodSignature(interfaceMethod, im); ProxyHelper.EmitDefaultValue(im.GetILGenerator(), im.ReturnType); if (EmitIsEnabled(im, eventAttribute)) EmitDirectProxy(invocationContext, im, m, parameterTypes, targetParameterTypes); // map our method to the interface implementation _typeBuilder.DefineMethodOverride(im, interfaceMethod); } else if (interfaceMethod.DeclaringType.IsSubclassOf(typeof(EventSource))) { // if we are implementing an event source, then // for non-abstract methods we just proxy the base implementation EmitDirectProxy(invocationContext, m, interfaceMethod, parameterTypes, targetParameterTypes); } else { // the base class is not an event source, so we are creating an eventsource-derived class // that just logs the event // so we need to call write event // call IsEnabled with the given event level and keywords to check whether we should log ProxyHelper.EmitDefaultValue(m.GetILGenerator(), m.ReturnType); if (EmitIsEnabled(m, eventAttribute)) EmitCallWriteEvent(invocationContext, m, eventAttribute, parameterTypes, targetParameterTypes); } return m; }
/// <summary> /// Emits a call to the base method by pushing all of the arguments. /// </summary> /// <param name="m">The method to append to.</param> /// <param name="invocationContext">The invocation context for this call.</param> /// <param name="field">The field containing the interface to call.</param> /// <param name="originalMethod">The the original method signature.</param> /// <param name="baseMethod">The method to call.</param> private void EmitBaseMethodCall(MethodBuilder m, InvocationContext invocationContext, FieldInfo field, MethodInfo originalMethod, MethodInfo baseMethod) { // if this is a generic method, we have to instantiate our type of method if (baseMethod.IsGenericMethodDefinition) baseMethod = baseMethod.MakeGenericMethod(baseMethod.GetGenericArguments()); var sourceParameters = originalMethod.GetParameters(); var targetParameters = baseMethod.GetParameters(); // load the pointer from the field, push the parameters and call the method // this is an instance method, so arg.0 is the this pointer ILGenerator mIL = m.GetILGenerator(); mIL.Emit(OpCodes.Ldarg_0); mIL.Emit(OpCodes.Ldfld, field); // go through and serialize the parameters for (int i = 0; i < targetParameters.Length; i++) { ProxyHelper.EmitSerializeValue( m, invocationContext, _invocationContexts, _invocationContextsField, i, sourceParameters[i].ParameterType, targetParameters[i].ParameterType, _serializationProvider, _serializerField); } // call the method mIL.Emit(OpCodes.Callvirt, baseMethod); }
/// <summary> /// Implement the type. /// </summary> private void ImplementType() { // create a new assembly AssemblyName an = Assembly.GetExecutingAssembly().GetName(); an.Name = ProxyHelper.AssemblyName; AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); ModuleBuilder mb = ab.DefineDynamicModule(an.Name); // create a type based on EventSource and call the default constructor if (_interfaceType.IsSubclassOf(typeof(EventSource))) _typeBuilder = mb.DefineType(_interfaceType.FullName + "_Implemented", TypeAttributes.Class | TypeAttributes.Public, _interfaceType); else if (_interfaceType.IsInterface) _typeBuilder = mb.DefineType(_interfaceType.FullName + "_Implemented", TypeAttributes.Class | TypeAttributes.Public, typeof(EventSource), new Type[] { _interfaceType }); else _typeBuilder = mb.DefineType(_interfaceType.FullName + "_Implemented", TypeAttributes.Class | TypeAttributes.Public, typeof(EventSource)); // assign an EventSource attribute to the type so it gets the original name and guid _typeBuilder.SetCustomAttribute(EventSourceAttributeHelper.GetEventSourceAttributeBuilder(_interfaceType)); // implement the fields and constructor EmitFields(); // find all of the methods that need to be implemented var interfaceMethods = ProxyHelper.DiscoverMethods(_interfaceType); var implementationAttribute = _interfaceType.GetCustomAttribute<EventSourceImplementationAttribute>() ?? new EventSourceImplementationAttribute(); // find the first free event id, in case we need to assign some ourselves int eventId = interfaceMethods .Select(m => m.GetCustomAttribute<EventAttribute>()) .Where(a => a != null) .Select(a => a.EventId) .DefaultIfEmpty(0) .Max() + 1; // if there isn't a keyword class, then auto-generate the keywords bool hasKeywords = (implementationAttribute.Keywords != null) || (FindNestedType(_interfaceType, "Keywords") != null); ulong autoKeyword = hasKeywords ? (ulong)0 : 1; // for each method on the interface, try to implement it with a call to eventsource Dictionary<string, ulong> autoKeywords = new Dictionary<string, ulong>(); foreach (MethodInfo interfaceMethod in interfaceMethods) { var invocationContext = new InvocationContext(interfaceMethod, InvocationContextTypes.MethodCall); var beginMethod = EmitMethodImpl(invocationContext, ref eventId, (EventKeywords)autoKeyword); var faultedMethod = EmitMethodFaultedImpl(invocationContext, beginMethod, ref eventId, (EventKeywords)autoKeyword); EmitMethodCompletedImpl(invocationContext, beginMethod, ref eventId, (EventKeywords)autoKeyword, faultedMethod); // shift to the next keyword autoKeywords.Add(beginMethod.Name, autoKeyword); autoKeyword <<= 1; } // create the type Type t = _typeBuilder.CreateType(); // define the internal enum classes if they are defined if (hasKeywords) EmitEnumImplementation(implementationAttribute.Keywords, Keywords, typeof(EventKeywords)); else EmitKeywordImpl(autoKeywords); EmitEnumImplementation(implementationAttribute.OpCodes, Opcodes, typeof(EventOpcode)); EmitEnumImplementation(implementationAttribute.Tasks, Tasks, typeof(EventTask)); // initialize the static fields t.GetField(_invocationContextsField.Name, BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, _invocationContexts.ToArray()); t.GetField(_serializationProviderField.Name, BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, _serializationProvider); t.GetField(_contextProviderField.Name, BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, _contextProvider); // instantiate the singleton EventSource = (EventSource)t.GetConstructor(Type.EmptyTypes).Invoke(null); // fill in the event source for all of the invocation contexts foreach (var context in _invocationContexts) context.EventSource = EventSource; }
/// <summary> /// Emits the implementation of a given interface method. /// </summary> /// <param name="executeMethod">The execute method to implement.</param> private void EmitMethodImpl(MethodInfo executeMethod) { /* * public TReturn Method (params) * { * var scope = new EventActivityScope(true); * try * { * _log.Method(params); * object value = _execute.Method(params); * _log.Method_Completed(value); * } * catch (Exception e) * { * _log.Method_Faulted(e); * throw; * } * finally * { * scope.Dispose(); * } * } */ var invocationContext = new InvocationContext(executeMethod, InvocationContextTypes.MethodCall); // start building the interface MethodBuilder m = _typeBuilder.DefineMethod(executeMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual); ProxyHelper.CopyMethodSignature(executeMethod, m); var parameterTypes = executeMethod.GetParameters().Select(p => p.ParameterType).ToArray(); ILGenerator mIL = m.GetILGenerator(); // set up a place to hold the return value LocalBuilder returnValue = null; if (m.ReturnType != typeof(void)) returnValue = mIL.DeclareLocal(m.ReturnType); // set up the activity scope LocalBuilder scope = null; if (_callWithActivityScope) { scope = mIL.DeclareLocal(typeof(EventActivityScope)); mIL.Emit(OpCodes.Ldc_I4_1); mIL.Emit(OpCodes.Newobj, typeof(EventSourceProxy.EventActivityScope).GetConstructor(new Type[] { typeof(bool) })); mIL.Emit(OpCodes.Stloc, scope); } // start the try block mIL.BeginExceptionBlock(); // call the method on the log that matches the execute method var targetParameterTypes = parameterTypes.Select(p => p.IsGenericParameter ? TypeImplementer.GetTypeSupportedByEventSource(p) : p).ToArray(); var logMethod = DiscoverMethod(_logField.FieldType, executeMethod.Name, String.Empty, targetParameterTypes); if (logMethod != null) { // call the log method and throw away the result if there is one EmitBaseMethodCall(m, invocationContext, _logField, executeMethod, logMethod); if (logMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Pop); } // call execute EmitBaseMethodCall(m, invocationContext, _executeField, executeMethod, executeMethod); if (executeMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Stloc, returnValue); // if there is a completed method, then call that var completedParameterTypes = (executeMethod.ReturnType == typeof(void)) ? Type.EmptyTypes : new Type[] { executeMethod.ReturnType }; var completedMethod = DiscoverMethod(_logField.FieldType, executeMethod.Name, TypeImplementer.CompletedSuffix, completedParameterTypes); if (completedMethod != null) { // load this._log mIL.Emit(OpCodes.Ldarg_0); mIL.Emit(OpCodes.Ldfld, _logField); // load the value from the local variable if (executeMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Ldloc, returnValue); mIL.Emit(OpCodes.Call, completedMethod); if (completedMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Pop); } // handle exceptions by logging them and rethrowing mIL.BeginCatchBlock(typeof(Exception)); var faultedMethod = DiscoverMethod(_logField.FieldType, executeMethod.Name, TypeImplementer.FaultedSuffix, new Type[] { typeof(Exception) }); if (faultedMethod != null) { // save the exception var exception = mIL.DeclareLocal(typeof(Exception)); mIL.Emit(OpCodes.Stloc, exception); // load this._log mIL.Emit(OpCodes.Ldarg_0); mIL.Emit(OpCodes.Ldfld, _logField); // load the exception mIL.Emit(OpCodes.Ldloc, exception); // call the fault handler mIL.Emit(OpCodes.Call, faultedMethod); if (faultedMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Pop); } mIL.Emit(OpCodes.Rethrow); // clean up the activity scope if (_callWithActivityScope) { mIL.BeginFinallyBlock(); mIL.Emit(OpCodes.Ldloc, scope); mIL.Emit(OpCodes.Callvirt, typeof(EventActivityScope).GetMethod("Dispose")); } mIL.EndExceptionBlock(); // return the result if (executeMethod.ReturnType != typeof(void)) mIL.Emit(OpCodes.Ldloc, returnValue); mIL.Emit(OpCodes.Ret); }
/// <summary> /// Emits the code needed to properly push an object on the stack, /// serializing the value if necessary. /// </summary> /// <param name="typeBuilder">The TypeBuilder for the method being built.</param> /// <param name="methodBuilder">The method currently being built.</param> /// <param name="invocationContext">The invocation context for this call.</param> /// <param name="invocationContexts">A list of invocation contexts that will be appended to.</param> /// <param name="invocationContextsField">The static field containing the array of invocation contexts at runtime.</param> /// <param name="parameterMapping">The mapping of source parameters to destination parameters.</param> /// <param name="serializationProvider">The serialization provider for the current interface.</param> /// <param name="serializationProviderField"> /// The field on the current object that contains the serialization provider at runtime. /// This method assume the current object is stored in arg.0. /// </param> internal static void EmitSerializeValue( TypeBuilder typeBuilder, MethodBuilder methodBuilder, InvocationContext invocationContext, List<InvocationContext> invocationContexts, FieldBuilder invocationContextsField, ParameterMapping parameterMapping, TraceSerializationProvider serializationProvider, FieldBuilder serializationProviderField) { var sourceCount = parameterMapping.Sources.Count(); if (sourceCount == 0) return; if (sourceCount == 1) { var parameter = parameterMapping.Sources.First(); EmitSerializeValue( typeBuilder, methodBuilder, invocationContext, invocationContexts, invocationContextsField, parameter.Position, parameter.SourceType, parameterMapping.CleanTargetType, parameter.Converter, serializationProvider, serializationProviderField); return; } var il = methodBuilder.GetILGenerator(); // use the serializer to serialize the objects var context = new TraceSerializationContext(invocationContext.SpecifyType(InvocationContextTypes.BundleParameters), -1); context.EventLevel = serializationProvider.GetEventLevelForContext(context); if (context.EventLevel != null) { // get the object serializer from the this pointer il.Emit(OpCodes.Ldsfld, serializationProviderField); // create a new dictionary strings and values il.Emit(OpCodes.Newobj, typeof(Dictionary<string, string>).GetConstructor(Type.EmptyTypes)); foreach (var parameter in parameterMapping.Sources) { il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldstr, parameter.Alias); EmitSerializeValue( typeBuilder, methodBuilder, invocationContext, invocationContexts, invocationContextsField, parameter.Position, parameter.SourceType, parameterMapping.CleanTargetType, parameter.Converter, serializationProvider, serializationProviderField); var method = typeof(Dictionary<string, string>).GetMethod("Add"); il.Emit(OpCodes.Call, method); } // get the invocation context from the array on the provider il.Emit(OpCodes.Ldsfld, invocationContextsField); il.Emit(OpCodes.Ldc_I4, invocationContexts.Count); il.Emit(OpCodes.Ldelem, typeof(TraceSerializationContext)); invocationContexts.Add(context); il.Emit(OpCodes.Callvirt, typeof(TraceSerializationProvider).GetMethod("ProvideSerialization", BindingFlags.Instance | BindingFlags.Public)); } else il.Emit(OpCodes.Ldnull); }
/// <summary> /// Emit a call to WriteEvent(param object[]). /// </summary> /// <param name="invocationContext">The InvocationContext for this call.</param> /// <param name="methodBuilder">The MethodBuilder to append to.</param> /// <param name="eventAttribute">The EventAttribute to use as values in the method.</param> /// <param name="sourceParameterTypes">The types of parameters on the source method.</param> /// <param name="targetParameterTypes">The types of the parameters on the target method.</param> private void EmitCallWriteEvent(InvocationContext invocationContext, MethodBuilder methodBuilder, EventAttribute eventAttribute, Type[] sourceParameterTypes, Type[] targetParameterTypes) { ILGenerator mIL = methodBuilder.GetILGenerator(); // if there is no event attribute, then this is a non-event, so just return silently if (eventAttribute == null) { ProxyHelper.EmitDefaultValue(mIL, methodBuilder.ReturnType); mIL.Emit(OpCodes.Ret); return; } // call write event with the parameters in an object array mIL.Emit(OpCodes.Ldarg_0); mIL.Emit(OpCodes.Ldc_I4, eventAttribute.EventId); // create a new array of the proper length to pass the values in mIL.Emit(OpCodes.Ldc_I4, targetParameterTypes.Length); mIL.Emit(OpCodes.Newarr, typeof(object)); for (int i = 0; i < sourceParameterTypes.Length; i++) { mIL.Emit(OpCodes.Dup); mIL.Emit(OpCodes.Ldc_I4, (int)i); // load the argument and box it mIL.Emit(OpCodes.Ldarg, (int)i + 1); // if the target is a value type, then we can box the source type // and the CLR will apply conversions for us // at this point, all invalid types have been converted to strings in EmitDirectProxy // and references have been dereferenced if (targetParameterTypes[i].IsValueType) { var sourceType = sourceParameterTypes[i]; if (sourceType.IsByRef) sourceType = sourceType.GetElementType(); mIL.Emit(OpCodes.Box, sourceType); } mIL.Emit(OpCodes.Stelem, typeof(object)); } // if there is a context, call the context provider and add the context parameter if (SupportsContext(invocationContext)) { // load the array and index onto the stack mIL.Emit(OpCodes.Dup); mIL.Emit(OpCodes.Ldc_I4, (int)targetParameterTypes.Length - 1); // load the context provider mIL.Emit(OpCodes.Ldsfld, _contextProviderField); // get the invocation context from the array on the provider mIL.Emit(OpCodes.Ldsfld, _invocationContextsField); mIL.Emit(OpCodes.Ldc_I4, _invocationContexts.Count); mIL.Emit(OpCodes.Ldelem, typeof(InvocationContext)); mIL.Emit(OpCodes.Callvirt, typeof(TraceContextProvider).GetMethod("ProvideContext")); _invocationContexts.Add(invocationContext); // put the result into the array mIL.Emit(OpCodes.Stelem, typeof(object)); } // prepare for write event by setting the ETW activity ID mIL.Emit(OpCodes.Call, typeof(EventActivityScope).GetMethod("PrepareForWriteEvent")); // call writeevent mIL.Emit(OpCodes.Call, _writeEvent); mIL.Emit(OpCodes.Ret); }
/// <summary> /// Initializes a new instance of the TraceSerializationContext class. /// </summary> /// <param name="invocationContext">The InvocationContext this is based on.</param> /// <param name="parameterIndex">The index of the parameter being serialized.</param> internal TraceSerializationContext(InvocationContext invocationContext, int parameterIndex) : base(invocationContext.MethodInfo, invocationContext.ContextType) { ParameterIndex = parameterIndex; }
/// <summary> /// Emits a proxy to a method that just calls the base method. /// </summary> /// <param name="invocationContext">The current invocation context.</param> /// <param name="methodBuilder">The method to implement.</param> /// <param name="baseMethod">The base method.</param> /// <param name="parameterMapping">The mapping of the parameters.</param> private void EmitDirectProxy(InvocationContext invocationContext, MethodBuilder methodBuilder, MethodInfo baseMethod, List<ParameterMapping> parameterMapping) { /* * This method assume that a default return value has been pushed on the stack. * * base(params); * return (top of stack); */ ILGenerator mIL = methodBuilder.GetILGenerator(); // copy the parameters to the stack // arg.0 = this // so we go to length+1 mIL.Emit(OpCodes.Ldarg_0); for (int i = 0; i < parameterMapping.Count; i++) { var parameter = parameterMapping[i]; if (parameter.HasSource) { ProxyHelper.EmitSerializeValue( _typeBuilder, methodBuilder, invocationContext, _invocationContexts, _invocationContextsField, parameter, _serializationProvider, _serializationProviderField); } else { // if this method supports context, then add a context parameter // note that we pass null in here and then build the context from within EmitCallWriteEvent mIL.Emit(OpCodes.Ldnull); } } // now that all of the parameters have been loaded, call the base method mIL.Emit(OpCodes.Call, baseMethod); mIL.Emit(OpCodes.Ret); }