Почему индексатор в моем .NET-компоненте не всегда доступен из VBScript?

StackOverflow https://stackoverflow.com/questions/317759

  •  11-07-2019
  •  | 
  •  

Вопрос

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

Как я могу заставить его работать со следующим синтаксисом: Parent.Member("key") где элемент имеет индексатор (аналогично доступу к свойству по умолчанию встроенного 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)
Неправильное количество аргументов или недопустимое присвоение свойства:'Строка запроса'

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

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

Примечание:Это продолжение того, что Предоставление доступа к свойству индексатора / default через COM-взаимодействие.

Обновить:Вот некоторые сгенерированные IDL из библиотеки типов (используя олевью):

[
  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);
};
Это было полезно?

Решение

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

Проведя долгие часы, пытаясь найти разные обходные пути, я думаю, что наконец-то нашел что-то, что, кажется, работает и не так грязно Я реализовал метод доступа к коллекции в объекте-контейнере как метод, а не как свойство. Этот метод получает один аргумент, ключ. Если ключ «отсутствует» или ноль, тогда метод возвращает коллекцию (это обрабатывает выражения как " 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
  }

Другие советы

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

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

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

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

У вас есть 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 - Или реализуйте 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; }
    }
}

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

Но код, который он опубликовал, обрабатывает часть индексатора 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").

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

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

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

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

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

Кто-нибудь может сказать, не выполняется ли какое-либо из этих условий?Или может быть возможно, что значение по умолчанию .ЧИСТАЯ реализация IDispatch.Invoke ведет себя по-другому?Есть какие-нибудь предложения?

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

следующее использовалось для генерации ошибки 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 Частный _intCount Как целое число

Sub New(ByVal intCount As Integer)
    _intCount = intCount

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top