Pourquoi l'indexeur sur mon composant .NET n'est-il pas toujours accessible à partir de VBScript?

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

  •  11-07-2019
  •  | 
  •  

Question

J’ai accès à un assemblage .NET depuis VBScript (ASP classique) via COM Interop. Une classe a un indexeur (propriété par défaut a.k.a.) que j'ai obtenu de VBScript en ajoutant l'attribut suivant à l'indexeur: [DispId (0)] . Cela fonctionne dans la plupart des cas, mais pas lorsque vous accédez à la classe en tant que membre d'un autre objet.

Comment puis-je le faire fonctionner avec la syntaxe suivante: Parent.Member (" clé>) où Member a l'indexeur (similaire à l'accès à la propriété par défaut du code Request.QueryString : Request.QueryString ("clé") )?

Dans mon cas, il existe une classe parent TestRequest avec une propriété QueryString qui renvoie un IRequestDictionary , qui possède l'indexeur par défaut.

Exemple VBScript:

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

La ligne suivante provoque une erreur au lieu d’afficher "valeur". Voici la syntaxe avec laquelle j'aimerais travailler:

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

Environnement d'exécution Microsoft VBScript (0x800A01C2)
  Nombre d'arguments incorrect ou attribution de propriété non valide: 'QueryString'

Cependant, les lignes suivantes fonctionnent sans erreur et génèrent la "valeur" attendue. (notez que la première ligne accède à l'indexeur par défaut d'une variable temporaire):

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

Vous trouverez ci-dessous les interfaces et les classes simplifiées en C # 2.0. Ils ont été enregistrés 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; }
    }
}

J'ai essayé de rechercher et d'expérimenter différentes options, mais je n'ai pas encore trouvé de solution. Toute aide serait la bienvenue pour comprendre pourquoi la syntaxe testRequest.QueryString ("clé") ne fonctionne pas et comment le faire fonctionner.

Remarque: il s'agit de la suite de 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);
};
Était-ce utile?

La solution

Je suis tombé sur ce problème il y a quelques jours. Je ne trouvais pas d'explication raisonnable quant aux raisons pour lesquelles cela ne fonctionnait pas.

Après avoir passé de longues heures à essayer différentes solutions de contournement, j’ai enfin trouvé quelque chose qui semble fonctionner et qui n’est pas si sale. Ce que j'ai fait est d'implémenter l'accesseur à la collection dans l'objet conteneur en tant que méthode, au lieu d'une propriété. Cette méthode reçoit un argument, la clé. Si la clé est " manquante " ou null, la méthode retourne la collection (elle gère des expressions telles que "testRequest.QueryString.Count" dans VbScript). Sinon, la méthode renvoie l'élément spécifique de la collection.

Le problème avec cette approche est que cette méthode retourne un objet (car parfois la référence de retour est la collection, et parfois un élément de la collection), son utilisation à partir de code managé nécessite donc des transts partout. Pour éviter cela, j'ai créé une autre propriété (cette fois une propriété appropriée) dans le conteneur qui expose la collection. Cette propriété n'est PAS exposée à COM. À partir de C # / code géré, j'utilise cette propriété et de COM / VbScript / code non géré, j'utilise la méthode.

Voici une implémentation de la solution ci-dessus utilisant l'exemple de ce fil de discussion:

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

Autres conseils

Résultats de mon enquête sur ce sujet:

Le problème est relatif à la mise en oeuvre IDispatch utilisée par le Common Language Runtime lors de l'exposition de doubles interfaces et interfaces d'affichage à COM.

Un langage de script tel que VBScript (ASP) utilise l'implémentation OLE Automation IDispatch lors de l'accès à un objet COM.

Bien que cela semble fonctionner, je souhaite conserver la propriété en tant que propriété et ne souhaite pas avoir de fonction (solution de contournement expliquée ci-dessus).

Vous avez 2 solutions possibles:

1 - Utilisez l'IDispatchImplAttribute obsolète avec IDispatchImplType.CompatibleImpl.

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

Comme indiqué dans MSDN, cet attribut est déconseillé, mais fonctionne toujours avec .Net 2.0, 3.0, 3.5, 4.0. Vous devez décider si le fait qu'il soit "obsolète" pourrait être un problème pour vous ...

2 - Ou implémentez IReflect en tant qu'IDispatch personnalisé dans votre classe TesRequest ou créez une classe générique qui implémente IReflect et fait en sorte que votre classe hérite de ce nouveau créé.

Exemple de classe générique (la partie intéressante est dans la méthode 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; }
    }
}

et pour le code de Mike:

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

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

La solution de David Porcher fonctionne pour moi.

Mais le code qu'il a posté gère la partie Get de l'indexeur, j'ai donc mis à jour son code pour gérer également la partie Définir de l'indexeur

Voici le code mis à jour:

   // 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 ici ... Avez-vous examiné votre assemblage avec oleview pour vous assurer que votre interface publique dispose d'un indexeur visible pour les consommateurs com? La deuxième étape consiste à utiliser directement la méthode get_Item, plutôt que d'essayer d'utiliser la propriété d'indexeur (problèmes de conformité CLS) ...

J'ai trouvé que testRequest.QueryString () ("clé") fonctionnait, mais ce que je voulais, c'est testRequest.QueryString ("clé") .

J'ai trouvé un article très pertinent de Eric Lippert ( qui a de très bons articles sur VBScript, en passant). L'article, Sémantique de propriété par défaut VBScript , traite de la conditions pour invoquer une propriété par défaut ou simplement un appel de méthode. Mon code se comporte comme un appel de méthode, même s'il semble remplir les conditions pour une propriété par défaut.

Voici les règles de l'article d'Eric:

  

La règle pour les développeurs de   IDispatch :: Invoke est si tous les   Les éléments suivants sont vrais:

     
      
  • l'appelant invoque une propriété
  •   
  • l'appelant passe une liste d'arguments
  •   
  • la propriété ne prend pas réellement une liste d'arguments
  •   
  • cette propriété retourne un objet
  •   
  • cet objet a une propriété par défaut
  •   
  • cette propriété par défaut prend une liste d'arguments
  •   
     

invoquez ensuite la propriété par défaut avec   la liste d'arguments.

Quelqu'un peut-il savoir si l'une de ces conditions n'est pas remplie? Ou est-il possible que l'implémentation .NET par défaut de IDispatch.Invoke se comporte différemment? Des suggestions?

J'ai passé deux jours exactement avec le même problème à essayer toutes les variantes possibles en utilisant plusieurs tactiques. Ce message a résolu mon problème:

suivant utilisé pour générer une erreur parentobj.childobj (0) auparavant dû faire: parentobj.childobj.item (0)

en changeant:

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

où:

Classe publique 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

Classe de fin

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top