7

我有一个 .NET 程序集,我通过 COM 互操作从 VBScript(经典 ASP)访问它。一个类有一个索引器(也称为默认属性),我通过向索引器添加以下属性从 VBScript 中得到它:[DispId(0)]. 它在大多数情况下都有效,但在作为另一个对象的成员访问该类时无效。

我怎样才能让它使用以下语法:Parent.Member("key")其中 Member 有索引器(类似于访问内置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)
参数数量错误或属性分配无效:“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);
};
4

6 回答 6

7

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

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

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

尽管它似乎有效,但我想将该属性保留为属性并且不想拥有一个功能(上面解释了解决方法)。

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

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 并使您的类继承这个新创建的类的泛型类。

通用类示例(有趣的部分在 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; }
    }
}
于 2010-11-08T12:58:51.640 回答
5

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

在花了很长时间尝试不同的解决方法之后,我想我终于找到了一些似乎有效的东西,而且不是那么脏。我所做的是将容器对象中的集合的访问器实现为方法,而不是属性。此方法接收一个参数,即键。如果键是“丢失”或为空,则该方法返回集合(它处理像 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
  }
于 2009-10-08T18:03:50.377 回答
2

我 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);
        }
    }
于 2011-08-01T08:33:56.520 回答
1

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

于 2008-11-25T16:03:01.543 回答
1

我发现这testRequest.QueryString()("key")行得通,但我想要的是 testRequest.QueryString("key").

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

以下是 Eric 文章中的规则:

IDispatch::Invoke 实施者的规则是,如果以下所有条件都为真:

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

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

谁能说出这些条件中的任何一个是否没有得到满足?或者,默认的 .NET 实现是否有可能IDispatch.Invoke表现不同?有什么建议么?

于 2008-11-26T15:40:55.723 回答
1

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

以下用于生成错误 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

结束类

于 2009-11-24T12:12:31.143 回答