2008-11-25 10 views
5

Tengo un ensamblado .NET al que estoy accediendo desde VBScript (ASP clásico) a través de interoperabilidad COM. Una clase tiene un indexador (una propiedad predeterminada de a.k.) que obtuve trabajando desde VBScript agregando el siguiente atributo al indexador: [DispId(0)]. Funciona en la mayoría de los casos, pero no al acceder a la clase como miembro de otro objeto.¿Por qué el indexador en mi componente .NET no siempre es accesible desde VBScript?

¿Cómo puedo conseguir que funcione con la siguiente sintaxis: Parent.Member("key") donde Miembro tiene el indexador (similar al acceso a la propiedad predeterminada de la incorporada en el Request.QueryString: Request.QueryString("key"))?

En mi caso, hay una clase padre TestRequest con una propiedad QueryString que devuelve IRequestDictionary, que tiene el indexador predeterminado.

ejemplo VBScript:

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

La siguiente línea provoca un error en lugar de la impresión de "valor". Esta es la sintaxis me gustaría conseguir trabajo:

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

Microsoft VBScript en tiempo de ejecución (0x800A01C2)
número incorrecto de argumentos o asignación de propiedad no válido: 'cadena de consulta'

Sin embargo, la siguiente líneas do funcionan sin error y generan el "valor" esperado (tenga en cuenta que la primera línea accede al indexador predeterminado en una variable temporal):

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

A continuación se detallan las interfaces y clases simplificadas en C# 2.0. Ellos se han registrado a través de 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; } 
    } 
} 

He intentado investigar y experimentar con varias opciones, pero aún no han encontrado una solución. Cualquier ayuda sería apreciada para descubrir por qué la sintaxis testRequest.QueryString("key") no funciona y cómo hacer que funcione.

Nota: Este es un seguimiento de Exposing the indexer/default property via COM Interop.

Actualización: Aquí es un poco de la IDL generado a partir de la biblioteca de tipos (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); 
}; 
+0

I También tengo este problema y he pasado horas tratando de resolverlo. ¿Alguien tiene algún buen consejo aquí? – MJJames

Respuesta

3

Me encontré con este problema exacto hace unos días. No pude encontrar una explicación razonable de por qué no funciona.

Después de pasar largas horas probando diferentes soluciones, creo que finalmente encontré algo que parece funcionar, y no es tan sucio. Lo que hice fue implementar el acceso a la colección en el objeto contenedor como un método, en lugar de una propiedad. Este método recibe un argumento, la clave. Si la clave está "perdida" o es nula, el método devuelve la colección (esto maneja expresiones como "testRequest.QueryString.Count" en VbScript). De lo contrario, el método devuelve el elemento específico de la colección.

La parte sucia con este enfoque es que este método devuelve un objeto (porque a veces la referencia de devolución es la colección, y algunas veces un elemento de la colección), por lo que usarlo desde el código administrado necesita fundiciones en todas partes. Para evitar esto, creé otra propiedad (esta vez una propiedad adecuada) en el contenedor que expone la colección. Esta propiedad NO está expuesta a COM. Desde C#/código administrado, uso esta propiedad, y desde COM/VbScript/código no administrado, uso el método.

Aquí es una implementación de la solución anterior con el ejemplo de este tema:

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

Gracias por su minuciosa respuesta. No he tenido la oportunidad de probar esto todavía, pero lo intentaré pronto. –

1

WAG aquí ... ¿Ha examinado su ensamblaje con oleview asegurarse de que su interfaz pública tiene un indexador visibles para com consumidores? Segundo WAG es usar el método get_Item directamente, en lugar de tratar de usar la propiedad del indexador (problemas de cumplimiento CLS) ...

+0

Gracias Will por su respuesta. Usar una variable temporal con un indexador funciona (como testQueryString ("clave")), pero acceder a él como testRequest.QueryString ("clave") no funciona. –

+0

Intenté usar get_Item y tenía el mismo problema con el que podía usar el indexador de una variable temporal pero no como testRequest.QueryString ("clave"). –

+0

Obtuve oleview trabajando desde el kit de recursos de win 2003 y publiqué el IDL generado. PD: ¿Sus iniciales son "WAG", o eso significa algo más? –

1

Encontré que testRequest.QueryString()("key") funciona, pero lo que quiero es testRequest.QueryString("key").

Encontré un artículo muy relevante por Eric Lippert (quien por cierto tiene algunos excelentes artículos sobre VBScript). El artículo, VBScript Default Property Semantics, analiza las condiciones para invocar una propiedad predeterminada o simplemente una llamada a un método. Mi código se comporta como una llamada a método, aunque parece cumplir las condiciones para una propiedad predeterminada.

Estas son las reglas del artículo de Eric:

La regla para los ejecutores de IDispatch :: Invoke es si todo el siguiente es cierto:

  • la persona que llama invoca una propiedad
  • la persona que llama pasa una lista de argumentos
  • la propiedad en realidad no tiene una lista de argumentos
  • que la propiedad devuelve un objeto
  • que objeto tiene una propiedad predeterminada
  • propiedad predeterminada que toma una lista de argumentos

luego llamar a la propiedad predeterminada con la lista de argumentos.

¿Alguien puede decir si se cumple alguna de estas condiciones? ¿O podría ser posible que la implementación de .NET predeterminada de IDispatch.Invoke se comporte de manera diferente? ¿Alguna sugerencia?

1

He pasado un par de días con el mismo problema exacto que intenta cada variación posible el uso de múltiples tácticas. Este post solucionado mi problema:

siguiente utiliza para generar el error parentobj.childobj (0) antes tenían que hacer: parentobj.childobj.elemento (0)

cambiando:

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

    End Get 
End Property 

a:

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 

donde:

clsFieldProperties

Clase pública _intCount privada como número entero

Sub New(ByVal intCount As Integer) 
    _intCount = intCount 

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

fin de la clase

5

resultados de mi investigación sobre este tema:

El problema es relativo a la implementación de IDispatch el Common Language Runtime utiliza al exponer interfaces duales y dispinterfaces a COM.

El lenguaje de script como VBScript (ASP) usa la implementación de IDispatch de automatización OLE al acceder al objeto COM.

A pesar de que parece funcionar, quiero mantener la propiedad como propiedad y no quiero tener una función (solución explicada anteriormente).

usted tiene 2 posibles soluciones:

1 - Use el IDispatchImplAttribute obsoleta con IDispatchImplType.CompatibleImpl.

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

Como se ha dicho en MSDN, este atributo está en desuso pero sigue trabajando con .Net 2.0, 3.0, 3.5, 4.0. Tiene que decidir si el hecho de que está "en desuso" podría ser un problema para usted ...

2 - O implemente IReflect como un IDispatch personalizado en su clase TesRequest o cree una clase genérica que implemente IReflect y haga su clase hereda este nuevo creado.

muestra de la clase genérica (la parte sin interés está en el 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; } 
    } 
} 

y para el código de Mike:

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

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

Tuve un problema donde mis objetos 'ComVisible' C# se usaron desde un VBScript usando MSScriptControl. Cuando esos objetos usaron propiedades indexadas, el script falló. He usado la segunda opción para crear una clase base para esos objetos, y el problema fue resuelto. ¡Muy buena solución! –

2

solución que David Porcher funciona para mí.

Pero el código que había publicado manejar la parte Get del indexador, así que actualizó su código para controlar también la parte Conjunto del indexador

Aquí está el código de actualización:

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