c# - proyecto - ¿Por qué el indexador de mi componente.NET no siempre es accesible desde VBScript?
agregar dll a proyecto c# (6)
Tengo un ensamblado .NET al que estoy accediendo desde VBScript (ASP clásico) a través de interoperabilidad COM. Una clase tiene un indexador (también conocido como propiedad predeterminada) 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.
¿Cómo puedo hacer que funcione con la siguiente sintaxis: Parent.Member("key")
donde Member tiene el indexador (similar al acceso a la propiedad predeterminada de la Request.QueryString
incorporada: Request.QueryString("key")
) ?
En mi caso, hay una clase de TestRequest
con una propiedad QueryString
que devuelve un IRequestDictionary
, que tiene el indexador predeterminado.
Ejemplo de VBScript:
Dim testRequest, testQueryString
Set testRequest = Server.CreateObject("AspObjects.TestRequest")
Set testQueryString = testRequest.QueryString
testQueryString("key") = "value"
La siguiente línea causa un error en lugar de imprimir "valor". Esta es la sintaxis que me gustaría que funcione:
Response.Write(testRequest.QueryString("key"))
Tiempo de ejecución de Microsoft VBScript (0x800A01C2)
Número incorrecto de argumentos o asignación de propiedad no válida: ''QueryString''
Sin embargo, las siguientes líneas funcionan sin errores 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 muestran las interfaces y clases simplificadas en C # 2.0. 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; }
}
}
Intenté investigar y experimentar con varias opciones, pero aún no encontré una solución. Se agradecería cualquier ayuda para descubrir por qué la testRequest.QueryString("key")
no funciona y cómo hacer que funcione.
Nota: Este es un seguimiento de la exposición de la propiedad indexador / predeterminado a través de COM Interop .
Actualización: Aquí está el IDL generado de la biblioteca de tipos (utilizando 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);
};
WAG aquí ... ¿Ha examinado su conjunto con oleview para asegurarse de que su interfaz pública tenga un indexador visible para los consumidores? Segundo WAG es usar el método get_Item directamente, en lugar de tratar de usar la propiedad indexer (problemas de cumplimiento CLS) ...
Descubrí que testRequest.QueryString()("key")
funciona, pero lo que quiero es testRequest.QueryString("key")
.
Encontré un artículo muy relevante de Eric Lippert (que tiene algunos artículos realmente geniales sobre VBScript, por cierto). 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.
Aquí están las reglas del artículo de Eric:
La regla para los implementadores de IDispatch :: Invoke es si todos los siguientes son verdaderos:
- la persona que llama invoca una propiedad
- quien llama pasa una lista de argumentos
- la propiedad no toma una lista de argumentos
- esa propiedad devuelve un objeto
- ese objeto tiene una propiedad predeterminada
- esa propiedad predeterminada toma una lista de argumentos
luego invoque la propiedad predeterminada con la lista de argumentos.
¿Alguien puede decir si alguna de estas condiciones no se cumple? ¿O podría ser posible que la implementación .NET predeterminada de IDispatch.Invoke
comporte de manera diferente? ¿Alguna sugerencia?
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í hay una implementación de la solución anterior utilizando el ejemplo de este hilo:
[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
}
He pasado un par de días con el mismo problema tratando cada variación posible usando múltiples tácticas. Esta publicación resolvió mi problema:
siguiente utilizado para generar error parentobj.childobj (0) anteriormente tenía que hacer: parentobj.childobj.item (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
dónde:
Clase pública 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
Clase final
Resultados de mi investigación sobre este tema:
El problema es relativo a la implementación de IDispatch que el lenguaje de ejecución común utiliza al exponer interfaces duales y dispinterfaces a COM.
El lenguaje de scripting como VBScript (ASP) utiliza la implementación de IDispatch de Automatización OLE al acceder a un 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).
Tienes 2 soluciones posibles:
1 - Utilice el IDispatchImplAttribute en desuso 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 dijo en MSDN, este atributo está en desuso, pero aún funciona con .Net 2.0, 3.0, 3.5, 4.0. Tienes que decidir si el hecho de que está "en desuso" podría ser un problema para ti ...
2 - O implemente IReflect como IDispatch personalizado en su clase TesRequest o cree una clase genérica que implemente IReflect y haga que su clase herede este nuevo creado.
Muestra genérica de la clase (la parte interesante 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; }
}
}
La solución de David Porcher funciona para mí.
Pero el código que había publicado manejaba la parte Obtener del indexador, así que actualicé su código para manejar también la parte del conjunto del indexador
Aquí está el código actualizado:
// 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);
}
}