using System; using System.Reflection; using System.Runtime.Remoting; using System.Runtime.Remoting.Activation; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; using Developpez.Dotnet.Reflection; namespace Developpez.Dotnet.Diagnostics { /// /// Moniteur d'appels à des méthodes d'un objet /// /// Type de l'objet à surveiller public class MethodCallMonitor : RealProxy where T : MarshalByRefObject { /// /// Uri de l'objet /// protected virtual string ObjUri { get; set; } /// /// Liste des méthodes surveillées. null ou un tableau vide /// signifie de surveiller toutes les méthodes sans exception /// public virtual string[] MonitoredMethods { get; set; } MethodCallMonitor(MarshalByRefObject target) : base(typeof(T)) { if (target == null) throw new ArgumentNullException("target"); MonitoredMethods = null; ObjUri = RemotingServices.Marshal(target).URI; } /// /// Transmet l'appel d'une méthode au proxy sous-jacent /// /// Le message contenant les informations d'appel de méthode /// Le message renvoyé par la méthode appelée, contenant la valeur de retour et les paramètres ref ou out public override IMessage Invoke(IMessage invokeMessage) { if (invokeMessage is IConstructionCallMessage) { IConstructionReturnMessage myIConstructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)invokeMessage); ConstructionResponse constructionResponse = new ConstructionResponse(null, (IMethodCallMessage)invokeMessage); return constructionResponse; } else { IMethodCallMessage methodCall_base = invokeMessage as IMethodCallMessage; if (methodCall_base != null) { MethodCallMessageWrapper methodCall = new MethodCallMessageWrapper(methodCall_base); methodCall.Properties["__Uri"] = ObjUri; bool ignoreMonitoring = false; /* on regarde s'il faut surveiller cette méthode */ if (MonitoredMethods != null && MonitoredMethods.Length > 0) { ignoreMonitoring = !methodCall.MethodName.In(MonitoredMethods); } if (!ignoreMonitoring) { // Build default return message object retVal = ((MethodInfo)methodCall.MethodBase).ReturnType.GetDefaultValue(); var outArgs = methodCall.Args; var retMsg = new ReturnMessage(retVal, outArgs, outArgs.Length, methodCall.LogicalCallContext, methodCall); BeforeMethodCallEventArgs arg = new BeforeMethodCallEventArgs(methodCall) { Cancel = false, ReturnMessage = retMsg }; OnBeforeMethodCall(arg); if (arg.Cancel) { AfterMethodCallEventArgs r_arg = new AfterMethodCallEventArgs(methodCall, true) { ReturnMessage = arg.ReturnMessage }; OnAfterMethodCall(r_arg); return r_arg.ReturnMessage; } } IMessage returnMessage = null; try { returnMessage = ChannelServices.SyncDispatchMessage(methodCall); IMethodReturnMessage methodReturnMessage = (IMethodReturnMessage)returnMessage; if (!ignoreMonitoring) { AfterMethodCallEventArgs r_arg = new AfterMethodCallEventArgs(methodCall, true) { ReturnMessage = returnMessage }; OnAfterMethodCall(r_arg); returnMessage = r_arg.ReturnMessage; } return returnMessage; } catch (Exception ex) { returnMessage = null; if (!ignoreMonitoring) { MethodCallExceptionEventArgs ex_args = new MethodCallExceptionEventArgs(methodCall, ex) { Rethrow = true }; OnMethodCallException(ex_args); if (ex_args.Rethrow) { throw new TargetInvocationException(ex_args.Exception); } else return ex_args.ReturnMessage; } else { /* en l'absence de monitor on génère une exception */ throw new TargetInvocationException(ex); } } } } return ChannelServices.SyncDispatchMessage(invokeMessage); } /// /// Crée un nouveau proxy dynamique crée à partir de l'objet target passé en paramètre /// /// Objet pour lequel créer un proxy /// Paramètre de sortie qui reçoit le MethodCallMonitor créé /// Le proxy dynamique créé public static T Create(T target, out MethodCallMonitor proxy) { proxy = new MethodCallMonitor(target); return (T)proxy.GetTransparentProxy(); } #region Events /// /// Appelé avant l'exécution d'une méthode au sein d'un objet surveillé /// public event BeforeMethodCallEventHandler BeforeMethodCall; /// /// Appelé après l'exécution d'une méthode au sein d'un objet surveillé, si l'exécution n'entraine pas d'exception /// public event AfterMethodCallEventHandler AfterMethodCall; /// /// Appelé si une exception survient dans une des méthodes surveillées /// public event MethodCallExceptionEventHandler MethodCallException; /// /// Déclanche l'évènement /// /// Paramètre de l'évènement protected virtual void OnBeforeMethodCall(BeforeMethodCallEventArgs e) { BeforeMethodCallEventHandler evt = BeforeMethodCall; if (evt != null) evt(this, e); } /// /// Déclanche l'évènement /// /// Paramètre de l'évènement protected virtual void OnAfterMethodCall(AfterMethodCallEventArgs e) { AfterMethodCallEventHandler evt = AfterMethodCall; if (evt != null) evt(this, e); } /// /// Déclanche l'évènement /// /// Paramètre de l'évènement protected virtual void OnMethodCallException(MethodCallExceptionEventArgs e) { MethodCallExceptionEventHandler evt = MethodCallException; if (evt != null) evt(this, e); } #endregion } #region Events Objects /// /// Représente le délégué utilisé pour un évènement précédent l'appel d'une méthode au sein d'un objet surveillé /// /// La source de l'évènement /// Un qui contient les arguments de l'évènement public delegate void BeforeMethodCallEventHandler(object sender, BeforeMethodCallEventArgs e); /// /// Représente le délégué utilisé pour un évènement suivant l'appel d'une méthode au sein d'un objet surveillé /// /// La source de l'évènement /// Un qui contient les arguments de l'évènement public delegate void AfterMethodCallEventHandler(object sender, AfterMethodCallEventArgs e); /// /// Représente le délégué utilisé pour un évènement résultant d'une exception dans une des méthodes surveillées /// /// La source de l'évènement /// Un qui contient les arguments de l'évènement public delegate void MethodCallExceptionEventHandler(object sender, MethodCallExceptionEventArgs e); /// /// Classe de base pour les évènements de /// public abstract class MethodCallMonitorBaseEventArgs : EventArgs { /// /// Initialise une nouvelle instance de MethodCallMonitorBaseEventArgs pour le message spécifié /// /// Message d'appel de méthode protected MethodCallMonitorBaseEventArgs(IMethodCallMessage message) { this.MethodName = message.MethodName; this.Msg = message; } /// /// Nom de la méthode /// public virtual string MethodName { get; private set; } /// /// Message (remoting) utilisé par le Framework pour tracer l'appel à la méthode /// public virtual IMethodCallMessage Msg { get; private set; } } /// /// Classe contenant les informations pour l'évènement /// public class MethodCallExceptionEventArgs : MethodCallMonitorBaseEventArgs { /// /// Initialise une nouvelle instance de MethodCallExceptionEventArgs pour le message et l'exception spécifiés /// /// Message d'appel de méthode /// Exception levée pendant l'exécution de la méthode public MethodCallExceptionEventArgs(IMethodCallMessage message, Exception exception) : base(message) { this.Exception = exception; } /// /// Exception détectée au sein de la méthode (résultant d'un "throw") /// public Exception Exception { get; private set; } /// /// Indique si, une fois cet évènement traité, l'exception doit être à nouveau lancée /// pour être interceptée par un niveau supérieur.
/// Par défaut, vaut true (dans le cas d'une non gestion de cet évènement, l'exception est remontée) ///
public bool Rethrow { get; set; } /// /// Message de retour à passer à l'appelant, contenant la valeur renvoyée par la méthode /// public IMessage ReturnMessage { get; set; } } /// /// Classe contenant les informations pour l'évènement /// public class BeforeMethodCallEventArgs : MethodCallMonitorBaseEventArgs { /// /// Initialise une nouvelle instance de BeforeMethodCallEventArgs pour le message spécifié /// /// Message d'appel de méthode public BeforeMethodCallEventArgs(IMethodCallMessage message) : base(message) { } /// /// Indique si l'appel doit être annulé. Dans ce cas, il faut placer un message dans ReturnMessage /// pour assurer l'intégrité des appels (ou déclancher une exception). /// public bool Cancel { get; set; } /// /// Message de retour à passer à l'appelant, contenant la valeur renvoyée par la méthode /// public IMessage ReturnMessage { get; set; } } /// /// Classe contenant les informations pour l'évènement /// public class AfterMethodCallEventArgs : MethodCallMonitorBaseEventArgs { /// /// Initialise une nouvelle instance de AfterMethodCallEventArgs pour le message spécifié /// /// Message d'appel de méthode /// Indique si l'appel à la méthode a été annulé public AfterMethodCallEventArgs(IMethodCallMessage message, bool cancelled) : base(message) { this.Cancelled = cancelled; } /// /// Indique si l'appel à la méthode a été annulé (résultant de la gestion de l'évènement ) /// public bool Cancelled { get; set; } /// /// Message de retour à passer à l'appelant, contenant la valeur renvoyée par la méthode /// public IMessage ReturnMessage { get; set; } } #endregion }