Почему индексатор в моем компоненте .NET не всегда доступен из VBScript? - PullRequest
6 голосов
/ 25 ноября 2008

У меня есть сборка .NET, к которой я получаю доступ из VBScript (классический ASP) через COM-взаимодействие. У одного класса есть индексатор (a.k.a. default свойство), который я получил, работая с VBScript, добавив следующий атрибут к индексатору: [DispId(0)]. В большинстве случаев работает, но не при доступе к классу как члену другого объекта.

Как заставить его работать со следующим синтаксисом: Parent.Member("key"), где Member имеет индексатор (аналогично доступу к свойству по умолчанию встроенного Request.QueryString: Request.QueryString("key"))?

В моем случае есть родительский класс TestRequest со свойством QueryString, который возвращает 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'

Однако следующие строки do работают без ошибок и выводят ожидаемое «значение» (обратите внимание, что первая строка обращается к индексатору по умолчанию для временной переменной):

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") не работает и как заставить его работать.

Примечание. Это продолжение Предоставление свойства indexer / default через 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);
};

Ответы [ 6 ]

6 голосов
/ 08 ноября 2010

Результаты моего исследования на эту тему:

Проблема связана с реализацией IDispatch, используемой во время выполнения общеязыкового языка при представлении двойных интерфейсов и диспинтерфейсов для COM.

Язык сценариев, такой как VBScript (ASP), использует реализацию IDispatch автоматизации OLE при доступе к COM-объекту.

Несмотря на то, что это похоже на работу, я хочу сохранить свойство как свойство и не хочу иметь функцию (обходной путь объяснен выше).

У вас есть 2 возможных решения:

1 - использовать устаревший атрибут IDispatchImpl с 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 - Или реализуйте IReflect в качестве пользовательского IDispatch в своем классе TesRequest или создайте универсальный класс, который реализует 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; }
    }
}
4 голосов
/ 08 октября 2009

Я наткнулся на эту проблему несколько дней назад. Я не смог найти разумного объяснения, почему это не работает.

Проведя долгие часы, пытаясь найти разные обходные пути, я, кажется, наконец-то нашел что-то, что, кажется, работает и не так грязно. Я реализовал метод доступа к коллекции в объекте-контейнере как метод, а не как свойство. Этот метод получает один аргумент, ключ. Если ключ «отсутствует» или равен нулю, метод возвращает коллекцию (это обрабатывает выражения типа «testRequest.QueryString.Count» в VbScript). В противном случае метод возвращает определенный элемент из коллекции.

Грязная часть этого подхода заключается в том, что этот метод возвращает объект (потому что иногда возвращаемая ссылка является коллекцией, а иногда - элементом коллекции), поэтому для его использования из управляемого кода везде требуются преобразования. Чтобы избежать этого, я создал другое свойство (на этот раз правильное свойство) в контейнере, который предоставляет коллекцию. Это свойство не подвергается воздействию 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
  }
2 голосов
/ 01 августа 2011

У меня работает решение Дэвида Порчера.

Но код, который он опубликовал, обрабатывает часть индексатора 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);
        }
    }
1 голос
/ 24 ноября 2009

Я провел пару дней с точно такой же проблемой, пробуя все возможные варианты, используя несколько тактик. Этот пост решил мою проблему:

после используется для генерации ошибки 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

Конечный класс

1 голос
/ 26 ноября 2008

Я обнаружил, что testRequest.QueryString()("key") работает, но я хочу testRequest.QueryString("key").

Я нашел очень актуальную статью Эрика Липперта (кстати, у него есть несколько действительно хороших статей по VBScript). В статье Семантика свойства по умолчанию для VBScript обсуждаются условия вызова свойства по умолчанию или просто вызова метода. Мой код ведет себя как вызов метода, хотя кажется, что он соответствует условиям для свойства по умолчанию.

Вот правила из статьи Эрика:

Правило для разработчиков IDispatch :: Invoke, если все верно следующее:

  • вызывающая сторона вызывает свойство
  • вызывающий передает список аргументов
  • свойство фактически не принимает список аргументов
  • это свойство возвращает объект
  • этот объект имеет свойство по умолчанию
  • это свойство по умолчанию принимает список аргументов

затем вызовите свойство по умолчанию с список аргументов.

Может кто-нибудь сказать, не выполняется ли какое-либо из этих условий? Или возможно, что стандартная реализация .NET IDispatch.Invoke ведет себя иначе? Есть предложения?

1 голос
/ 25 ноября 2008

WAG здесь ... Вы проверяли вашу сборку с помощью oleview , чтобы убедиться, что ваш общедоступный интерфейс имеет индексатор, видимый для потребителей com? Второй WAG - использовать метод get_Item напрямую, а не пытаться использовать свойство индексатора (проблемы соответствия CLS) ...

...