【问题标题】:C# COM object with a dynamic interface具有动态接口的 C# COM 对象
【发布时间】:2013-10-04 01:26:22
【问题描述】:

我想构建一个 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# 版本 4 中添加了 dynamic 关键字,以便更容易在 C# 代码中后期绑定到 COM 服务器。使用支持后期绑定的 [ComVisible] 在 C# 中创建自己的 COM 服务器始终是可能的,回到 C# 版本 1。
  • 感谢您的评论汉斯。当我使用“动态”这个词时,我并不是指添加到 C# 中的 dynamic 类型,很抱歉造成混淆。这就是我期望 C# 一直支持 IDispatch COM 接口的原因。但我不确定的是 VBA 的整个工作流程是否有机会工作。因此,我正在搜索任何资源,其中包含基于用 C# 实现的 IDispatch 的动态 COM 接口的最小示例。有了它,我就可以在 Excel/VBA 中对其进行测试并得到明确的答案。
  • 使用 [ComVisible] 类的 any 示例。 CLR 实现 IDispatch 无需您显式继承该接口,只是不要使用 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]。如果您想要自动完成支持,请使用 InterfaceIsDual。
  • 感谢输入 Hans。但是如何让客户端发现动态创建的接口?或者更准确地说,就像在 .Net 中,CLR 充当客户端和 .Net 类之间的代理,我如何对 CLR 说“这是我希望您向任何 COM 客户端呈现的接口”?

标签: c# .net com idispatch


【解决方案1】:

公开IDispatchEx 对JavaScript 有效,但我认为VBA 不会使用它。 AFAIK,VBA 依赖IDispatch 进行后期绑定。此外,C#dynamic 非常适合在 .NET 端使用基于 COM IDispatch 的对象,但反之则不然。出于某种原因(.NET 设计者的决定?),ExpandoObjectDynamicObject 的动态属性和方法默认不会暴露给 COM。

幸运的是,有一种方法可以覆盖它:通过实现IReflect 接口。参考这个优秀的blog post 了解实现细节。我自己看过at exposing properties of C# anonymous class to COM,最后使用IReflect。这就是您可以向 COM 公开动态方法和属性的方式。形象地说,IReflectIDispatch 的形式暴露给 COM。

顺便说一句,IExpandoIDispatchEx 执行相同的工作,因此 JavaScript 客户端可以添加新的属性,以后可以通过托管代码访问这些属性。

[更新] 下面是一个原型实现,它将DynamicComponent 的实例暴露给在WebBrowser 内部运行的VBScript。它适用于 VBScript,也适用于 VBA。虽然,我怀疑 VBA 自动完成是否会起作用,或者有一种简单的方法可以实现这种功能。 AFAIU,VBA 自动完成依赖于 COM 类型库(可通过 IDispatch::GetTypeInfo 获得),但我认为 .NET 互操作引擎在实现 IDispatchIReflect 时不会生成动态类型库(我可能错了)。此外,此实现对于按名称查找方法是区分大小写的,应进行调整,因为 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
}

【讨论】:

  • 感谢所有这些东西。到目前为止,这是一条更有希望的道路。我会试试... :)
【解决方案2】:

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

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

【讨论】:

  • 感谢 Eric 查看问题。我知道ExpandoObject,但在这里我想从COM 的“动态”功能中受益。而我感兴趣的接口是COM接口。如果 COM 互操作与 DLR 集成,那就太好了:从 DynamicObject 继承并实现 TryInvoke 就可以了。 :) 但是 AFAIK 这并不是那么简单,实现这种动态性充其量也很麻烦,但我希望我错了......
  • @Pragmateek 鉴于您在问题中概述的规范,ExpandoObjects 似乎非常合适。特别是,编写从 IDispatchEx 到 Expandos 的适配器似乎是“简单的编程问题”。
  • 感谢您指出 IDispatchEx,我以前从未听说过它,经过一些研究后,它似乎是为这种用途而创建的:blogs.msdn.com/b/ericlippert/archive/2004/10/07/239289.aspx .这可能是一个完美的选择,但是:首先这个接口在 C# 中不存在(这不是我发现的一些端口的真正问题),其次似乎没有人关心在 C# 中实现它(即使在 C++ 中也很少有资源) ,最后我担心 CLR 的 COM 互操作管理会妨碍...:/
猜你喜欢
  • 2012-12-15
  • 1970-01-01
  • 1970-01-01
  • 2011-06-27
  • 2017-01-08
  • 2018-07-03
  • 2012-04-03
  • 2011-07-05
  • 1970-01-01
相关资源
最近更新 更多