为什么我的.NET组件上的索引器总是无法从VBScript访问?

为什么我的.NET组件上的索引器总是无法从VBScript访问?

问题描述:

我有一个.NET程序集,我通过COM interop从VBScript(传统ASP)访问。一类有索引器(a.k.a.默认属性),我通过向索引器添加以下属性从VBScript获得工作:[DispId(0)]。它在大多数情况下工作,但不是在作为另一个对象的成员访问该类时。为什么我的.NET组件上的索引器总是无法从VBScript访问?

我怎样才能使它符合以下语法:Parent.Member("key")其中成员有索引器(类似于访问内置Request.QueryString的默认属性:Request.QueryString("key"))?

在我的情况下,有一个父类TestRequestQueryString属性,它返回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)
的参数或无效的属性赋值错误号码: '查询字符串'

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

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")语法不起作用,以及如何让它工作。

注意:这是Exposing the indexer/default property via 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); 
}; 
+0

我也有这个问题,并花了数小时试图解决它,有没有人有任何好的建议吗? – MJJames 2009-08-03 15:49:51

我在这个确切的问题,前几天偶然。我找不到合理的解释,为什么它不起作用。

花费很长时间尝试不同的解决方法后,我想我终于找到的东西,似乎工作,并且不那么脏。我所做的是将容器对象中的集合的访问器作为方法实现,而不是属性。该方法接收一个参数,即键。如果键是“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 
    } 

WAG这里...您可以检查您的装配与oleview,以确保您的公共接口有可见的索引给com消费者?第二个WAG是直接使用get_Item方法,而不是尝试使用索引器属性(CLS合规性问题)...

+0

感谢您的回应。与索引器一起使用临时变量确实可行(如testQueryString(“key”)),但像testRequest.QueryString(“key”)那样访问它不起作用。 – 2008-11-25 16:50:24

+0

我尝试过使用get_Item,它具有相同的问题,我可以使用索引器从临时变量但不像testRequest.QueryString(“key”)。 – 2008-11-25 16:56:18

+0

我从win 2003资源工具包中获得了oleview,并发布了生成的IDL。 PS你的首字母是“WAG”,还是这意味着别的? – 2008-11-25 17:50:03

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

我发现Eric Lippert(他有一些非常棒的VBScript文章,顺便说一下)非常相关的文章。文章VBScript Default Property Semantics讨论了是调用默认属性还是仅调用方法的条件。我的代码行为像一个方法调用,虽然它似乎符合默认属性的条件。

以下是Eric的文章规则:

为 的IDispatch ::调用的执行者的规则是,如果所有的 以下为真:

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

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

谁能告诉如果有这些条件没有得到满足?或者有可能IDispatch.Invoke的默认.NET实现的行为有所不同?有什么建议么?

我花了几天的使用完全相同的问题,尝试使用多种战术每一个可能的变化。这篇文章解决了我的问题:

根据用于产生错误 parentobj.childobj(0) 以前曾做的事: parentobj.childobj。项(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 私人_intCount作为整数

Sub New(ByVal intCount As Integer) 
    _intCount = intCount 

End Sub 
Public ReadOnly Property Count() As Integer 
    Get 
     Return _intCount 
    End Get 
End Property 

个末级

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

的问题是相对于暴露双接口和调度接口到COM时,公共语言运行时使用IDispatch实现。

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

尽管它似乎工作,我想保留该属性作为一个属性,并不想有一个函数(解决上述解决方法)。

你有2个可能的解决方案:

1 - 使用IDispatchImplType.CompatibleImpl过时的IDispatchImplAttribute。

[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自定义的IDispatch或创建实现减反射泛型类,使你的类继承了这个新创建的类。

泛型类样品(该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; } 
    } 
} 

我大卫PORCHER解决方案为我工作。

不过,他发布的代码处理索引的获取一部分,所以我更新了他的代码也处理索引

以下是更新后的代码的设置部分:

// 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); 
     } 
    }