|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
namespace Microsoft.CSharp.RuntimeBinder.ComInterop
{
/// <summary>
/// Provides helper methods to bind COM objects dynamically.
/// </summary>
internal static class ComBinder
{
/// <summary>
/// Determines if an object is a COM object.
/// </summary>
/// <param name="value">The object to test.</param>
/// <returns>true if the object is a COM object, false otherwise.</returns>
public static bool IsComObject(object value)
{
return value != null && Marshal.IsComObject(value);
}
/// <summary>
/// Tries to perform binding of the dynamic get member operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="GetMemberBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation. </param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <param name="delayInvocation">true if member evaluation may be delayed.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result, bool delayInvocation)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
if (TryGetMetaObject(ref instance))
{
var comGetMember = new ComGetMemberBinder(binder, delayInvocation);
result = instance.BindGetMember(comGetMember);
if (result.Expression.Type.IsValueType)
{
result = new DynamicMetaObject(
Expression.Convert(result.Expression, typeof(object)),
result.Restrictions
);
}
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic set member operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="SetMemberBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation.</param>
/// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set member operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindSetMember(SetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject value, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
Requires.NotNull(value);
if (TryGetMetaObject(ref instance))
{
result = instance.BindSetMember(binder, value);
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic invoke operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="InvokeBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation. </param>
/// <param name="args">An array of <see cref="DynamicMetaObject"/> instances - arguments to the invoke member operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindInvoke(InvokeBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
Requires.NotNull(args);
if (TryGetMetaObjectInvoke(ref instance))
{
result = instance.BindInvoke(binder, args);
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic invoke member operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="InvokeMemberBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation. </param>
/// <param name="args">An array of <see cref="DynamicMetaObject"/> instances - arguments to the invoke member operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
Requires.NotNull(args);
if (TryGetMetaObject(ref instance))
{
result = instance.BindInvokeMember(binder, args);
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic get index operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="GetIndexBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation. </param>
/// <param name="args">An array of <see cref="DynamicMetaObject"/> instances - arguments to the invoke member operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
Requires.NotNull(args);
if (TryGetMetaObjectInvoke(ref instance))
{
result = instance.BindGetIndex(binder, args);
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic set index operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="SetIndexBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation. </param>
/// <param name="args">An array of <see cref="DynamicMetaObject"/> instances - arguments to the invoke member operation.</param>
/// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set index operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
public static bool TryBindSetIndex(SetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, DynamicMetaObject value, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
Requires.NotNull(args);
Requires.NotNull(value);
if (TryGetMetaObjectInvoke(ref instance))
{
result = instance.BindSetIndex(binder, args, value);
return true;
}
result = null;
return false;
}
/// <summary>
/// Tries to perform binding of the dynamic Convert operation.
/// </summary>
/// <param name="binder">An instance of the <see cref="ConvertBinder"/> that represents the details of the dynamic operation.</param>
/// <param name="instance">The target of the dynamic operation.</param>
/// <param name="result">The new <see cref="DynamicMetaObject"/> representing the result of the binding.</param>
/// <returns>true if operation was bound successfully; otherwise, false.</returns>
public static bool TryConvert(ConvertBinder binder, DynamicMetaObject instance, out DynamicMetaObject result)
{
Requires.NotNull(binder);
Requires.NotNull(instance);
if (IsComObject(instance.Value))
{
// Converting a COM object to any interface is always considered possible - it will result in
// a QueryInterface at runtime
if (binder.Type.IsInterface)
{
result = new DynamicMetaObject(
Expression.Convert(
instance.Expression,
binder.Type
),
BindingRestrictions.GetExpressionRestriction(
Expression.Call(
typeof(ComBinder).GetMethod(nameof(ComBinder.IsComObject), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public),
Helpers.Convert(instance.Expression, typeof(object))
)
)
);
return true;
}
}
result = null;
return false;
}
/// <summary>
/// Gets the member names of the data-like members associated with the object.
/// This function can operate only with objects for which <see cref="IsComObject"/> returns true.
/// </summary>
/// <param name="value">The object for which member names are requested.</param>
/// <returns>The collection of member names.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
internal static IList<string> GetDynamicDataMemberNames(object value)
{
Requires.NotNull(value);
Requires.Condition(IsComObject(value), nameof(value));
return ComObject.ObjectToComObject(value).GetMemberNames(true);
}
/// <summary>
/// Gets the data-like members and associated data for an object.
/// This function can operate only with objects for which <see cref="IsComObject"/> returns true.
/// </summary>
/// <param name="value">The object for which data members are requested.</param>
/// <param name="names">The enumeration of names of data members for which to retrieve values.</param>
/// <returns>The collection of pairs that represent data member's names and their data.</returns>
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
internal static IList<KeyValuePair<string, object>> GetDynamicDataMembers(object value, IEnumerable<string> names)
{
Requires.NotNull(value);
Requires.Condition(IsComObject(value), nameof(value));
return ComObject.ObjectToComObject(value).GetMembers(names);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private static bool TryGetMetaObject(ref DynamicMetaObject instance)
{
// If we're already a COM MO don't make a new one
// (we do this to prevent recursion if we call Fallback from COM)
if (instance is ComUnwrappedMetaObject)
{
return false;
}
if (IsComObject(instance.Value))
{
instance = new ComMetaObject(instance.Expression, instance.Restrictions, instance.Value);
return true;
}
return false;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private static bool TryGetMetaObjectInvoke(ref DynamicMetaObject instance)
{
// If we're already a COM MO don't make a new one
// (we do this to prevent recursion if we call Fallback from COM)
if (TryGetMetaObject(ref instance))
{
return true;
}
if (instance.Value is IPseudoComObject o)
{
instance = o.GetMetaObject(instance.Expression);
return true;
}
return false;
}
/// <summary>
/// Special binder that indicates special semantics for COM GetMember operation.
/// </summary>
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
internal sealed class ComGetMemberBinder : GetMemberBinder
{
private readonly GetMemberBinder _originalBinder;
internal bool _canReturnCallables;
internal ComGetMemberBinder(GetMemberBinder originalBinder, bool canReturnCallables) :
base(originalBinder.Name, originalBinder.IgnoreCase)
{
_originalBinder = originalBinder;
_canReturnCallables = canReturnCallables;
}
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
{
return _originalBinder.FallbackGetMember(target, errorSuggestion);
}
public override int GetHashCode()
{
return _originalBinder.GetHashCode() ^ (_canReturnCallables ? 1 : 0);
}
public override bool Equals(object obj)
{
return obj is ComGetMemberBinder other
&& _canReturnCallables == other._canReturnCallables
&& _originalBinder.Equals(other._originalBinder);
}
}
}
}
|