/// <summary> /// Execute the function on the given instance /// </summary> public object Execute(Expander expander, object objectInstance, BuildPropertyGroup properties, ExpanderOptions options) { object functionResult = String.Empty; object[] args = null; try { // If there is no object instance, then the method invocation will be a static if (objectInstance == null) { // Check that the function that we're going to call is valid to call if (!IsStaticMethodAvailable(ObjectType, name)) { ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionMethodUnavailable", name, ObjectType.FullName); } bindingFlags |= BindingFlags.Static; // For our intrinsic function we need to support calling of internal methods // since we don't want them to be public if (objectType == typeof(Microsoft.Build.BuildEngine.IntrinsicFunctions)) { bindingFlags |= BindingFlags.NonPublic; } } else { bindingFlags |= BindingFlags.Instance; } // We have a methodinfo match, need to plug in the arguments args = new object[arguments.Length]; // Assemble our arguments ready for passing to our method for (int n = 0; n < arguments.Length; n++) { object argument = expander.ExpandPropertiesLeaveTypedAndEscaped(this.arguments[n], null); string argumentValue = argument as string; if (argumentValue != null) { // remove our 'quotes' from the escaped string, leaving escaped quotes intact args[n] = EscapingUtilities.UnescapeAll(argumentValue.Trim('`', '"', '\'')); } else { args[n] = argument; } } // Handle special cases where the object type needs to affect the choice of method // The default binder and method invoke, often chooses the incorrect Equals and CompareTo and // fails the comparison, because what we have on the right is generally a string. // This special casing is to realize that its a comparison that is taking place and handle the // argument type coercion accordingly; effectively pre-preparing the argument type so // that it matches the left hand side ready for the default binder�s method invoke. if (objectInstance != null && args.Length == 1 && (String.Equals("Equals", this.name, StringComparison.OrdinalIgnoreCase) || String.Equals("CompareTo", this.name, StringComparison.OrdinalIgnoreCase))) { // change the type of the final unescaped string into the destination args[0] = Convert.ChangeType(args[0], objectInstance.GetType(), CultureInfo.InvariantCulture); } // If we've been asked for and instance to be constructed, then we // need to locate an appropriate constructor and invoke it if (String.Equals("new", this.name, StringComparison.OrdinalIgnoreCase)) { functionResult = LateBindExecute(null /* no previous exception */, BindingFlags.Public | BindingFlags.Instance, null /* no instance for a constructor */, args, true /* is constructor */); } else { // Execute the function given converted arguments // The only exception that we should catch to try a late bind here is missing method // otherwise there is the potential of running a function twice! try { // First use InvokeMember using the standard binder - this will match and coerce as needed functionResult = objectType.InvokeMember(this.name, bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture); } catch (MissingMethodException ex) // Don't catch and retry on any other exception { // If we're invoking a method, then there are deeper attempts that // can be made to invoke the method if ((bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { // The standard binder failed, so do our best to coerce types into the arguments for the function // This may happen if the types need coercion, but it may also happen if the object represents a type that contains open type parameters, that is, ContainsGenericParameters returns true. functionResult = LateBindExecute(ex, bindingFlags, objectInstance, args, false /* is not constructor */); } else { // We were asked to get a property or field, and we found that we cannot // locate it. Since there is no further argument coersion possible // we'll throw right now. throw; } } } // If the result of the function call is a string, then we need to escape the result // so that we maintain the "engine contains escaped data" state. // The exception is that the user is explicitly calling MSBuild::Unescape or MSBuild::Escape if (functionResult is string && !String.Equals("Unescape", name, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", name, StringComparison.OrdinalIgnoreCase)) { functionResult = EscapingUtilities.Escape((string)functionResult); } // There's nothing left to deal within the function expression, return the result from the execution if (String.IsNullOrEmpty(remainder)) { return functionResult; } // Recursively expand the remaining property body after execution return expander.ExpandPropertyBody(remainder, functionResult, properties, options); } // Exceptions coming from the actual function called are wrapped in a TargetInvocationException catch (TargetInvocationException ex) { // We ended up with something other than a function expression string partiallyEvaluated = GenerateStringOfMethodExecuted(expression, objectInstance, name, args); ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.InnerException.Message.Replace("\r\n", " ")); return null; } // Any other exception was thrown by trying to call it catch (Exception ex) { if (ExceptionHandling.NotExpectedFunctionException(ex)) { throw; } // We ended up with something other than a function expression string partiallyEvaluated = GenerateStringOfMethodExecuted(expression, objectInstance, name, args); ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message); return null; } }