为什么我的.NET组件上的索引器不能始终从VBScript访问?

我有一个.NET程序集,我通过COM互操作从VBScript(经典ASP)访问。 一个类有一个索引器(也就是默认属性),我通过向索引器添加以下属性从VBScript开始工作: [DispId(0)] 。 它适用于大多数情况,但不能作为另一个对象的成员访问该类。

如何使用以下语法使其工作: Parent.Member("key")其中Member具有索引器(类似于访问内置Request.QueryString的默认属性: Request.QueryString("key") ) ?

在我的例子中,有一个带有QueryString属性的父类TestRequest ,它返回一个IRequestDictionary ,它有默认的索引器。

VBScript示例:

 Dim testRequest, testQueryString Set testRequest = Server.CreateObject("AspObjects.TestRequest") Set testQueryString = testRequest.QueryString testQueryString("key") = "value" 

以下行导致错误而不是打印“值”。 这是我想要工作的语法:

 Response.Write(testRequest.QueryString("key")) 

Microsoft VBScript运行时(0x800A01C2)
参数数量错误或属性赋值无效:’QueryString’

但是,以下行无错误地工作并输出预期的“值”(请注意,第一行访问临时变量上的默认索引器):

 Response.Write(testQueryString("key")) Response.Write(testRequest.QueryString.Item("key")) 

下面是C#2.0中的简化接口和类。 它们已通过RegAsm.exe /path/to/AspObjects.dll /codebase /tlb

 [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IRequest { IRequestDictionary QueryString { get; } } [ClassInterface(ClassInterfaceType.None)] public class TestRequest : IRequest { private IRequestDictionary _queryString = new RequestDictionary(); public IRequestDictionary QueryString { get { return _queryString; } } } [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IRequestDictionary : IEnumerable { [DispId(0)] object this[object key] { [DispId(0)] get; [DispId(0)] set; } } [ClassInterface(ClassInterfaceType.None)] public class RequestDictionary : IRequestDictionary { private Hashtable _dictionary = new Hashtable(); public object this[object key] { get { return _dictionary[key]; } set { _dictionary[key] = value; } } } 

我已经尝试过研究和试验各种选项,但还没有找到解决方案。 任何帮助将不胜感激,以弄清楚为什么testRequest.QueryString("key")语法不起作用以及如何使其工作。

注意:这是通过COM Interop公开索引器/默认属性的后续操作 。

更新:以下是类型库中生成的IDL(使用oleview ):

 [ uuid(C6EDF8BC-6C8B-3AB2-92AA-BBF4D29C376E), version(1.0), custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, AspObjects.IRequest) ] dispinterface IRequest { properties: methods: [id(0x60020000), propget] IRequestDictionary* QueryString(); }; [ uuid(8A494CF3-1D9E-35AE-AFA7-E7B200465426), version(1.0), custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, AspObjects.IRequestDictionary) ] dispinterface IRequestDictionary { properties: methods: [id(00000000), propget] VARIANT Item([in] VARIANT key); [id(00000000), propputref] void Item( [in] VARIANT key, [in] VARIANT rhs); }; 

几天前我偶然发现了这个问题。 我无法找到合理的解释,说明为什么它不起作用。

在花了很长时间尝试不同的解决方法后,我想我终于找到了似乎有用的东西,而且不是那么脏。 我所做的是将容器对象中的集合的访问器实现为方法,而不是属性。 这个方法接收一个参数,即密钥。 如果键是“missing”或null,则该方法返回该集合(它在VbScript中处理类似“testRequest.QueryString.Count”的表达式)。 否则,该方法返回集合中的特定项。

使用此方法的脏部分是此方法返回一个对象(因为有时返回引用是集合,有时是集合的项),因此从托管代码中使用它需要在任何地方进行转换。 为了避免这种情况,我在暴露集合的容器中创建了另一个属性(这次是一个适当的属性)。 此属性不向COM公开。 从C#/托管代码我使用此属性,并从COM / VbScript /非托管代码我使用该方法。

以下是使用此线程示例的上述解决方法的实现:

  [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IRequest { IRequestDictionary ManagedQueryString { get; } // Property to use form managed code object QueryString(object key); // Property to use from COM or unmanaged code } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public class TestRequest : IRequest { private IRequestDictionary _queryString = new RequestDictionary(); public IRequestDictionary ManagedQueryString { get { return _queryString; } } public object QueryString(object key) { if (key is System.Reflection.Missing || key == null) return _queryString; else return _queryString[key]; } } [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IRequestDictionary : IEnumerable { [DispId(0)] object this[object key] { [DispId(0)] get; [DispId(0)] set; } int Count { get; } } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public class RequestDictionary : IRequestDictionary { private Hashtable _dictionary = new Hashtable(); public object this[object key] { get { return _dictionary[key]; } set { _dictionary[key] = value; } } public int Count { get { return _dictionary.Count; } } #region IEnumerable Members public IEnumerator GetEnumerator() { throw new NotImplementedException(); } #endregion } 

我对这个问题的调查结果:

问题与公共语言运行库在向COM公开双接口和调度接口时使用的IDispatch实现有关。

像VBScript(ASP)这样的脚本语言在访问COM对象时使用OLE Automation IDispatch实现。

尽管它似乎有效,但我希望将该属性保留为属性,并且不希望具有某个function(上面解释的解决方法)。

您有两种可能的解决方案:

1 – 将弃用的IDispatchImplAttribute与IDispatchImplType.CompatibleImpl一起使用。

  [ClassInterface(ClassInterfaceType.None)] [IDispatchImpl(IDispatchImplType.CompatibleImpl)] public class TestRequest : IRequest { private IRequestDictionary _queryString = new RequestDictionary(); public IRequestDictionary QueryString { get { return _queryString; } } } 

如MSDN中所述,此属性已弃用但仍可使用.Net 2.0,3.0,3.5,4.0。 你必须决定它是否“弃用”这一事实对你来说可能是个问题…

2 – 或者在类TesRequest中将IReflect实现为自定义IDispatch,或者创建一个实现IReflect的generics类,并使您的类inheritance这个新创建的类。

通用类示例(interresting部分在InvokeMember方法中):

 [ComVisible(false)] public class CustomDispatch : IReflect { // Called by CLR to get DISPIDs and names for properties PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr) { return this.GetType().GetProperties(bindingAttr); } // Called by CLR to get DISPIDs and names for fields FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr) { return this.GetType().GetFields(bindingAttr); } // Called by CLR to get DISPIDs and names for methods MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr) { return this.GetType().GetMethods(bindingAttr); } // Called by CLR to invoke a member object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters) { try { // Test if it is an indexed Property if (name != "Item" && (invokeAttr & BindingFlags.GetProperty) == BindingFlags.GetProperty && args.Length > 0 && this.GetType().GetProperty(name) != null) { object IndexedProperty = this.GetType().InvokeMember(name, invokeAttr, binder, target, null, modifiers, culture, namedParameters); return IndexedProperty.GetType().InvokeMember("Item", invokeAttr, binder, IndexedProperty, args, modifiers, culture, namedParameters); } // default InvokeMember return this.GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); } catch (MissingMemberException ex) { // Well-known HRESULT returned by IDispatch.Invoke: const int DISP_E_MEMBERNOTFOUND = unchecked((int)0x80020003); throw new COMException(ex.Message, DISP_E_MEMBERNOTFOUND); } } FieldInfo IReflect.GetField(string name, BindingFlags bindingAttr) { return this.GetType().GetField(name, bindingAttr); } MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr) { return this.GetType().GetMember(name, bindingAttr); } MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr) { return this.GetType().GetMembers(bindingAttr); } MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr) { return this.GetType().GetMethod(name, bindingAttr); } MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) { return this.GetType().GetMethod(name, bindingAttr, binder, types, modifiers); } PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) { return this.GetType().GetProperty(name, bindingAttr, binder, returnType, types, modifiers); } PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr) { return this.GetType().GetProperty(name, bindingAttr); } Type IReflect.UnderlyingSystemType { get { return this.GetType().UnderlyingSystemType; } } } 

并为迈克的代码:

 [ClassInterface(ClassInterfaceType.None)] public class TestRequest : CustomDispatch, IRequest { private IRequestDictionary _queryString = new RequestDictionary(); public IRequestDictionary QueryString { get { return _queryString; } } } 

我David Porcher解决方案适合我。

但他发布的代码处理索引器的Get部分,所以我更新了他的代码以处理索引器的Set部分

这是更新的代码:

  // Called by CLR to invoke a member object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters) { try { // Test if it is an indexed Property - Getter if (name != "Item" && (invokeAttr & BindingFlags.GetProperty) == BindingFlags.GetProperty && args.Length > 0 && this.GetType().GetProperty(name) != null) { object IndexedProperty = this.GetType().InvokeMember(name, invokeAttr, binder, target, null, modifiers, culture, namedParameters); return IndexedProperty.GetType().InvokeMember("Item", invokeAttr, binder, IndexedProperty, args, modifiers, culture, namedParameters); } // Test if it is an indexed Property - Setter // args == 2 : args(0)=Position, args(1)=Vlaue if (name != "Item" && (invokeAttr & BindingFlags.PutDispProperty) == BindingFlags.PutDispProperty && (args.Length == 2) && this.GetType().GetProperty(name) != null) { // Get The indexer Property BindingFlags invokeAttr2 = BindingFlags.GetProperty; object IndexedProperty = this.GetType().InvokeMember(name, invokeAttr2, binder, target, null, modifiers, culture, namedParameters); // Invoke the Setter Property return IndexedProperty.GetType().InvokeMember("Item", invokeAttr, binder, IndexedProperty, args, modifiers, culture, namedParameters); } // default InvokeMember return this.GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); } catch (MissingMemberException ex) { // Well-known HRESULT returned by IDispatch.Invoke: const int DISP_E_MEMBERNOTFOUND = unchecked((int)0x80020003); throw new COMException(ex.Message, DISP_E_MEMBERNOTFOUND); } } 

WAG在这里……您是否使用oleview检查了程序集,以确保您的公共接口具有com消费者可见的索引器? 第二个WAG是直接使用get_Item方法,而不是尝试使用索引器属性(CLS合规性问题)……

我发现testRequest.QueryString()("key")有效,但我想要的是testRequest.QueryString("key")

我找到了Eric Lippert的一篇非常相关的文章(顺便说一下,他在VBScript上有一些非常好的文章)。 文章VBScript Default Property Semantics讨论了是否调用默认属性或仅调用方法调用的条件。 我的代码表现得像一个方法调用,虽然它似乎符合默认属性的条件。

以下是Eric的文章中的规则:

IDispatch :: Invoke的实现者的规则是,如果满足以下所有条件:

  • 调用者调用属性
  • 调用者传递参数列表
  • 该属性实际上并没有采用参数列表
  • 该属性返回一个对象
  • 该对象具有默认属性
  • 该默认属性采用参数列表

然后使用参数列表调用默认属性。

任何人都可以判断是否有任何这些条件没有得到满足? 或者, IDispatch.Invoke的默认.NET实现可能有不同的行为吗? 有什么建议?

我花了几天的时间用完全相同的问题尝试使用多种策略的每种可能的变化。 这篇文章解决了我的问题:

以下用于生成错误parentobj.childobj(0)以前必须执行的操作:parentobj.childobj.item(0)

通过改变:

 Default Public ReadOnly Property Item(ByVal key As Object) As string Get Return strSomeVal End Get End Property 

至:

 Public Function Fields(Optional ByVal key As Object = Nothing) As Object If key Is Nothing Then Return New clsFieldProperties(_dtData.Columns.Count) Else Return strarray(key) End If End Function 

哪里:

公共类clsFieldProperties Private _intCount As Integer

 Sub New(ByVal intCount As Integer) _intCount = intCount End Sub Public ReadOnly Property Count() As Integer Get Return _intCount End Get End Property 

结束类