我想构建一个 COM 可见 C# 类,比如 DynamicComponent
,它将通过 COM 提供一个动态接口(interface)。
在内部这个类会维护一个委托(delegate)字典:
"GetTheAnswer" -> () => { return 42; }
"Add" -> (int a, int b) => { return a + b; }
...
客户端代码将是一些 VBA。
这是我天真的想象的工作流程:
- 用户从 Excel/VBA 编辑器引用 TLB
- 用户实例化一个新的
DynamicComponent
(好吧,至少得到一个由 Excel/VBA 提供的 stub ) - Excel/VBA COM 基础结构通过其IDispatch 接口(interface)查询组件
- 组件使用类似
["GetTheAnswer"-> 1, "Add"-> 2]
的 disp-ids 映射来回答
- 用户可以受益于自动完成并看到两种方法:
GetTheAnswer
和Add
- 用户调用这些方法中的任何一个,就好像它们是静态定义的一样
我的第一个问题:可能吗?
如果不是:为什么?
如果是:如何?
根据我对 COM 的了解,如果可能的话, IDispatch COM 接口(interface)是我最好的 friend 。
此外,据我了解, ICustomQueryInterface 来自 .Net 4 的界面也有很大帮助。
但现在 COM 并不是真正前沿的 ;) 很难找到代码示例等资源。
我发现了这个有趣的示例:https://clrinterop.codeplex.com/releases/view/32350它使用 ICustomQueryInterface 接口(interface)实现 COM 聚合
但它不是动态的,而是基于静态定义的类型和接口(interface)。
如有任何帮助,我们将不胜感激。
谢谢。
最佳答案
公开 IDispatchEx
将适用于 JavaScript,但我认为 VBA 不会使用它。 AFAIK,VBA 依赖于 IDispatch
进行后期绑定(bind)。此外,C# dynamic
非常适合在 .NET 端使用基于 COM IDispatch
的对象,但反之则不然。出于某种原因(.NET 设计者的决定?),ExpandoObject
和 DynamicObject
的动态属性和方法默认不会暴露给 COM。
幸运的是,有一种方法可以覆盖它:通过实现 IReflect界面。引用这个优秀blog post有关实现细节。我自己看过at exposing properties of C# anonymous class to COM ,并最终使用 IReflect
。这就是您可以向 COM 公开动态方法和属性的方法。形象地说,IReflect
作为 IDispatch
暴露给 COM。
旁注,IExpando为 IDispatchEx
做同样的工作,因此 JavaScript 客户端可以添加新的属性,这些属性稍后可以被托管代码访问。
[更新] 下面是一个原型(prototype)实现,它将 DynamicComponent
的实例公开给在 WebBrowser
中运行的 VBScript。它适用于 VBScript,也适用于 VBA。虽然,我怀疑 VBA 自动完成是否有效,或者是否有一种简单的方法来实现这种功能。 AFAIU,VBA 自动完成依赖于 COM 类型库(可通过 IDispatch::GetTypeInfo
获得),但我认为 .NET 互操作引擎在实现 IDispatch 时不会生成动态类型库
通过 IReflect
(我可能是错的)。此外,此实现对于按名称查找方法区分大小写,由于 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
}
关于具有动态接口(interface)的 C# COM 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19055995/