具有动态接口的 C# COM 对象

本文关键字:COM 对象 接口 动态 | 更新日期: 2023-09-27 18:33:01

我想构建一个COM可见的C#类,比如DynamicComponent,它将通过COM提供动态接口

在内部,此类将维护委托字典:

"GetTheAnswer" -> () => { return 42; }
"Add" -> (int a, int b) => { return a + b; }
...

客户端代码将是一些 VBA。

这是我天真想象的工作流程:

  • 从 Excel/VBA 编辑器中,用户引用 TLB
  • 用户实例化一个新DynamicComponent(至少获得 Excel/VBA 提供的存根(
  • Excel/VBA COM基础结构通过其IDispatch接口查询组件
  • 该组件使用类似 ["GetTheAnswer" -> 1, "Add" -> 2] 的 disp-ids 映射进行回答
  • 用户可以从自动完成中受益,并看到两种方法:GetTheAnswerAdd
  • 用户调用这些方法中的任何一个,就好像它是静态定义的一样

我的第一个问题:这可能吗

如果没有:为什么

如果是:如何

根据我对COM的了解,如果可能的话,IDispatch COM接口是我最好的朋友。

此外,据我了解,.Net 4的ICustomQueryInterface接口也可以有很大帮助。

但就像现在一样,COM并不是真正的尖端;)很难找到代码示例等资源。

我发现了这个有趣的示例:https://clrinterop.codeplex.com/releases/view/32350 它使用 ICustomQueryInterface 接口实现 COM 聚合

但它不是动态的,并且基于静态定义的类型和接口。

任何帮助将不胜感激。

谢谢。

具有动态接口的 C# COM 对象

公开IDispatchEx适用于JavaScript,但我认为VBA不会使用它。AFAIK,VBA依靠IDispatch进行后期绑定。此外,C# dynamic非常适合在 .NET 端使用基于 COM IDispatch的对象,但反之则不然。由于某种原因(.NET 设计人员决定?(,默认情况下,ExpandoObjectDynamicObject的动态属性和方法不会向 COM 公开。

幸运的是,有一种方法可以覆盖它:通过实现 IReflect 接口。有关实现详细信息,请参阅这篇出色的博客文章。我自己研究过向 COM 公开 C# 匿名类的属性,最终使用了 IReflect .这是向 COM 公开动态方法和属性的方法。 形象地说,IReflect作为IDispatch向 COM 公开。

附带说明一下,IExpando为IDispatchEx做了同样的工作,所以JavaScript客户端可以添加新的属性,以后可以通过托管代码访问这些属性。

[更新] 下面是一个原型实现,它向在 WebBrowser 中运行的 VBScript 公开DynamicComponent实例。它对于VBScript非常有效,对于VBA也应该如此。虽然,我怀疑 VBA 自动完成是否有效,或者有一种简单的方法可以实现这样的功能。AFAIU,VBA 自动完成依赖于 COM 类型库(可通过 IDispatch::GetTypeInfo 获得(,但我认为 .NET 互操作引擎在通过 IReflect 实现IDispatch时不会生成动态类型库(我可能是错的(。此外,此实现对于按名称查找的方法区分大小写,应进行调整,因为 VB 不区分大小写。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WebBrowserApp
{
    // https://stackoverflow.com/a/19067386/1768303
    public partial class MainForm : Form
    {
        WebBrowser wb;
        public MainForm()
        {
            InitializeComponent();
            this.wb = new WebBrowser();
            this.wb.Dock = DockStyle.Fill;
            this.Controls.Add(this.wb);
            this.wb.Visible = true;
            var dynamicComponent = new DynamicComponent();
            // make dynamicComponent available to VBScript
            this.wb.ObjectForScripting = dynamicComponent;
            // add a dynamic method "Convert"
            dynamicComponent.SetMethod("Convert", new Func<int, string>((a) =>
            {
                MessageBox.Show("Convert called: " + a.ToString());
                return a.ToString();
            }));
            this.Load += (s, e) =>
            {
                this.wb.DocumentText =
                    "<script type='text/vbscript'>'n" +
                    "Sub OnLoadHandler'n" +
                    "    alert window.external.Convert(42)'n" +
                    "End Sub'n" +
                    "window.onload = GetRef('"OnLoadHandler'")'n" +
                    "</script>";
            };
        }
    }
    #region DynamicComponent
    [ComVisible(true), ClassInterface(ClassInterfaceType.None)]
    public class DynamicComponent : System.Reflection.IReflect
    {
        readonly Dictionary<string, Delegate> _methods = new Dictionary<string, Delegate>();
        public void SetMethod(string name, Delegate value)
        {
            _methods[name] = value;
        }
        static Exception NotImplemented()
        {
            var method = new StackTrace(true).GetFrame(1).GetMethod().Name;
            Debug.Assert(false, method);
            return new NotImplementedException(method);
        }
        #region IReflect
        // IReflect
        public FieldInfo GetField(string name, BindingFlags bindingAttr)
        {
            throw NotImplemented();
        }
        public FieldInfo[] GetFields(BindingFlags bindingAttr)
        {
            return new FieldInfo[0];
        }
        public MemberInfo[] GetMember(string name, BindingFlags bindingAttr)
        {
            throw NotImplemented();
        }
        public MemberInfo[] GetMembers(BindingFlags bindingAttr)
        {
            return new MemberInfo[0];
        }
        public MethodInfo GetMethod(string name, BindingFlags bindingAttr)
        {
            throw NotImplemented();
        }
        public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
        {
            throw NotImplemented();
        }
        public MethodInfo[] GetMethods(BindingFlags bindingAttr)
        {
            return _methods.Keys.Select(name => new DynamicMethodInfo(name, _methods[name].Method)).ToArray();
        }
        public PropertyInfo[] GetProperties(BindingFlags bindingAttr)
        {
            return new PropertyInfo[0];
        }
        public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
        {
            throw NotImplemented();
        }
        public PropertyInfo GetProperty(string name, BindingFlags bindingAttr)
        {
            throw NotImplemented();
        }
        public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters)
        {
            if (target == this && invokeAttr.HasFlag(BindingFlags.InvokeMethod))
            {
                Delegate method;
                if (!_methods.TryGetValue(name, out method))
                    throw new MissingMethodException();
                return method.DynamicInvoke(args);
            }
            throw new ArgumentException();
        }
        public Type UnderlyingSystemType
        {
            get { throw NotImplemented(); }
        }
        #endregion
        #region DynamicMethodInfo
        // DynamicPropertyInfo
        class DynamicMethodInfo : System.Reflection.MethodInfo
        {
            string _name;
            MethodInfo _mi;
            public DynamicMethodInfo(string name, MethodInfo mi)
                : base()
            {
                _name = name;
                _mi = mi;
            }
            public override MethodInfo GetBaseDefinition()
            {
                return _mi.GetBaseDefinition();
            }
            public override ICustomAttributeProvider ReturnTypeCustomAttributes
            {
                get { return _mi.ReturnTypeCustomAttributes; }
            }
            public override MethodAttributes Attributes
            {
                get { return _mi.Attributes; }
            }
            public override MethodImplAttributes GetMethodImplementationFlags()
            {
                return _mi.GetMethodImplementationFlags();
            }
            public override ParameterInfo[] GetParameters()
            {
                return _mi.GetParameters();
            }
            public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, System.Globalization.CultureInfo culture)
            {
                return _mi.Invoke(obj, invokeAttr, binder, parameters, culture);
            }
            public override RuntimeMethodHandle MethodHandle
            {
                get { return _mi.MethodHandle; }
            }
            public override Type DeclaringType
            {
                get { return _mi.DeclaringType; }
            }
            public override object[] GetCustomAttributes(Type attributeType, bool inherit)
            {
                return _mi.GetCustomAttributes(attributeType, inherit);
            }
            public override object[] GetCustomAttributes(bool inherit)
            {
                return _mi.GetCustomAttributes(inherit);
            }
            public override bool IsDefined(Type attributeType, bool inherit)
            {
                return _mi.IsDefined(attributeType, inherit);
            }
            public override string Name
            {
                get { return _name; }
            }
            public override Type ReflectedType
            {
                get { return _mi.ReflectedType; }
            }
        }
        #endregion
    }
    #endregion
}

不能即时创建接口,但可以创建方法。 查看 C# 中的 Expando 对象。

特别是,您可以使用 expandos 创建自定义 IDispatchEx 实现;您可以使用 expando 将名称映射到 ID,并使用反射来调用对象。