Por que é o indexador no meu componente .NET nem sempre acessível a partir de VBScript?

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

  •  11-07-2019
  •  | 
  •  

Pergunta

Eu tenho um assembly .NET que eu estou acessando do VBScript (ASP clássico) através de interoperabilidade. Uma classe tem um indexador (propriedade padrão A.K.A.) que eu fui trabalhar a partir de VBScript, adicionando o seguinte atributo para o indexador: [DispId(0)]. Ele funciona na maioria dos casos, mas não quando estiver acessando a classe como um membro de outro objeto.

Como posso fazê-lo funcionar com a seguinte sintaxe: Parent.Member("key") onde membros tem o indexador (semelhante ao acesso a propriedade padrão do built-in Request.QueryString: Request.QueryString("key"))

?

No meu caso, há uma TestRequest classe pai com uma propriedade QueryString que retorna um IRequestDictionary, que tem o indexador padrão.

VBScript exemplo:

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

A linha a seguir faz com que um erro em vez de imprimir "valor". Esta é a sintaxe que eu gostaria de começar a trabalhar:

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

Microsoft VBScript runtime (0x800A01C2)
Número errado de argumentos ou atribuição de propriedade inválido: 'QueryString'

No entanto, as seguintes linhas do trabalho sem erro e saída do "valor" esperado (nota que a primeira linha acessa o indexador padrão em uma variável temporária):

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

A seguir estão as interfaces simplificadas e classes em C # 2.0. Eles foram registradas via 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; }
    }
}

Eu tentei pesquisando e experimentando com várias opções, mas ainda não encontrou uma solução. Qualquer ajuda seria apreciada para descobrir por que a sintaxe testRequest.QueryString("key") não está funcionando e como fazê-lo funcionar.

Nota: Esta é uma continuação a Expor a propriedade do indexador / default via COM Interop .

Update: Aqui está algum do IDL gerado a partir da biblioteca tipo (usando 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);
};
Foi útil?

Solução

me deparei com este problema exato há poucos dias. Eu não poderia encontrar uma explicação razoável do porquê ele não funciona.

Depois de passar longas horas tentando diferentes soluções, acho que finalmente encontrei algo que parece funcionar, e não é tão sujo. O que eu fiz é implementar o acessor à coleção no objeto de recipiente como um método, em vez de uma propriedade. Este método recebe um argumento, a chave. Se a chave é "ausente" ou nulo, então o método retorna a coleção (este alças expressões como "testRequest.QueryString.Count" em VBScript). Caso contrário, o método retorna o item específico da coleção.

A parte suja com esta abordagem é que este método retorna um objeto (porque às vezes a referência de retorno é a coleção, e às vezes um item da coleção), de modo a usá-lo a partir gerenciados castings necessita de código em todos os lugares. Para evitar isso, eu criei uma outra propriedade (desta vez uma propriedade adequada) no recipiente que expõe a coleção. Esta propriedade não está exposta a COM. De código C # / geridos eu usar essa propriedade e, a partir COM / VbScript / código não gerenciado Eu uso o método.

Aqui está uma implementação da solução acima usando o exemplo deste tópico:

  [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
  }

Outras dicas

Os resultados da minha investigação sobre este assunto:

O problema é relativo à implementação IDispatch os usos linguagem tempo de execução comum ao expor interfaces duplas e dispinterfaces para COM.

linguagem de script como VBScript (ASP) uso implementação de automação OLE IDispatch ao acessar a objetos COM.

Apesar parece funcionar, eu quero manter a propriedade como uma propriedade e não quero ter uma função (solução explicado acima).

Você tem 2 soluções possíveis:

1 -. Use o IDispatchImplAttribute obsoleta com IDispatchImplType.CompatibleImpl

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

Como referido no MSDN, este atributo é preterido mas ainda trabalhar com Net 2.0, 3.0, 3.5, 4.0. Você tem que decidir se o fato de que ele é "obsoleta" poderia ser um problema para você ...

2 -. Ou implementar IReflect como um IDispatch costume em sua classe TesRequest ou criar uma classe genérica que implementar IReflect e fazer seus herda da classe este novo criado

genérica amostra classe (a parte interresting é no Método 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; }
    }
}

e para o código de Mike:

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

    public IRequestDictionary QueryString {
        get { return _queryString; }
    }
}

solução que David Porcher funciona para mim.

Mas o código que ele tinha postado lidar com a parte Get do indexador, então eu atualizar seu código para manipular também a parte Set do indexador

Aqui está o código atualizado:

   // 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 aqui ... Você já examinou sua montagem com OLEVIEW para se certificar de sua interface pública tem um indexador visível aos consumidores cOM? Segundo WAG é usar o método get_Item diretamente, ao invés de tentar usar a propriedade do indexador (CLS questões de conformidade) ...

Eu achei que as obras testRequest.QueryString()("key"), mas o que eu quero é testRequest.QueryString("key").

Eu encontrei um artigo muito relevante por Eric Lippert ( que tem alguns realmente grandes artigos em VBScript, por sinal). O artigo, VBScript propriedade padrão Semântica , discute a condições para se invoca uma propriedade padrão ou apenas uma chamada de método. Meu código está se comportando como uma chamada de método, embora pareça para atender as condições para uma propriedade padrão.

Aqui estão as regras do artigo de Eric:

A regra para implementadores de IDispatch :: Invoke é se todo o seguintes são verdadeiras:

  • o chamador invoca uma propriedade
  • o chamador passa uma lista de argumentos
  • a propriedade não realmente ter uma lista de argumentos
  • que a propriedade retorna um objeto
  • esse objeto tem uma propriedade padrão
  • que a propriedade padrão entra em uma lista de argumentos

então invocar a propriedade padrão com a lista de argumentos.

Alguém pode dizer se alguma destas condições não estão sendo atendidas? Ou poderia ser possível que a implementação do .NET padrão de IDispatch.Invoke se comporta de forma diferente? Alguma sugestão?

Eu passei um par de dias com exatamente a mesma questão de tentar todas as variações possíveis usando várias táticas. Este post resolvido o meu problema:

Na sequência usado para gerar erro parentobj.childobj (0) anteriormente tinha que fazer: parentobj.childobj.item (0)

mudando:

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

    End Get
End Property

para:

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

Onde:

clsFieldProperties Classe pública _intCount privada 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

End Class

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top