7

COM 相互運用機能を介して VBScript (クラシック ASP) からアクセスしている .NET アセンブリがあります。1 つのクラスには、次の属性をインデクサーに追加することで VBScript から動作するようになったインデクサー (別名既定のプロパティ) があります[DispId(0)]。ほとんどの場合は機能しますが、別のオブジェクトのメンバーとしてクラスにアクセスする場合は機能しません。

次の構文で動作させるにはどうすればよいですか:どこで Member にインデクサーがありますか (組み込みの:Parent.Member("key")の既定のプロパティにアクセスするのと同様です)?Request.QueryStringRequest.QueryString("key")

私の場合、デフォルトのインデクサーを持つを返すプロパティをTestRequest持つ親クラスがあります。QueryStringIRequestDictionary

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)
引数の数が間違っているか、プロパティの割り当てが無効です: 'QueryString'

ただし、次の行エラーなしで機能し、期待される「値」を出力します (最初の行は一時変数の既定のインデクサーにアクセスすることに注意してください)。

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")構文が機能しない理由と、それを機能させる方法を理解するための助けをいただければ幸いです。

注: これは、COM Interop を介してインデクサー / デフォルト プロパティを公開するのフォローアップです。

更新: これは、タイプ ライブラリから生成された IDL の一部です ( 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);
};
4

6 に答える 6

7

この件に関する私の調査結果:

この問題は、共通言語ランタイムがデュアル インターフェイスとディスパッチ インターフェイスを COM に公開するときに使用する IDispatch 実装に関連しています。

VBScript (ASP) などのスクリプト言語は、COM オブジェクトにアクセスするときに OLE オートメーション IDispatch 実装を使用します。

機能しているように見えますが、プロパティをプロパティとして保持したいので、関数を持ちたくありません(上記の回避策)。

考えられる解決策は 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; }
    }
}
于 2010-11-08T12:58:51.640 に答える
5

私は数日前にこの正確な問題に出くわしました。なぜ機能しないのかについての合理的な説明が見つかりませんでした。

さまざまな回避策を試すのに長い時間を費やした後、ようやくうまくいくようで、それほど汚れていないものを見つけたと思います。私がしたことは、コンテナ オブジェクトのコレクションへのアクセサを、プロパティではなくメソッドとして実装することです。このメソッドは、キーという 1 つの引数を受け取ります。キーが「見つからない」または null の場合、メソッドはコレクションを返します (これは、VbScript の「testRequest.QueryString.Count」のような式を処理します)。それ以外の場合、メソッドはコレクションから特定のアイテムを返します。

このアプローチの厄介な部分は、このメソッドがオブジェクトを返すことです (戻り参照がコレクションである場合もあれば、コレクションの項目である場合もあるため)。そのため、マネージ コードからこのメソッドを使用するには、どこでもキャストが必要です。これを回避するために、コレクションを公開するコンテナーに別のプロパティ (今回は適切なプロパティ) を作成しました。このプロパティは 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
  }
于 2009-10-08T18:03:50.377 に答える
2

私はDavidPorcherソリューションが役に立ちます。

しかし、彼が投稿したコードはインデクサーの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);
        }
    }
于 2011-08-01T08:33:56.520 に答える
1

私はそれがtestRequest.QueryString()("key")うまくいくことを発見しました、しかし私が欲しいのは testRequest.QueryString("key")です。

Eric Lippert(ちなみに、VBScriptに関する非常に優れた記事がいくつかあります)による非常に関連性の高い記事を見つけました。記事「VBScriptのデフォルトのプロパティセマンティクス」では、デフォルトのプロパティを呼び出すか、メソッド呼び出しだけを呼び出すかについての条件について説明しています。私のコードはメソッド呼び出しのように動作していますが、デフォルトのプロパティの条件を満たすようです。

エリックの記事のルールは次のとおりです。

IDispatch :: Invokeの実装者のルールは、次のすべてが当てはまる場合です。

  • 呼び出し元がプロパティを呼び出す
  • 呼び出し元は引数リストを渡します
  • プロパティは実際には引数リストを取りません
  • そのプロパティはオブジェクトを返します
  • そのオブジェクトにはデフォルトのプロパティがあります
  • そのデフォルトのプロパティは引数リストを取ります

次に、引数リストを使用してデフォルトのプロパティを呼び出します。

これらの条件のいずれかが満たされていないかどうか誰かにわかりますか?IDispatch.Invokeまたは、デフォルトの.NET実装の動作が異なる可能性がありますか?助言がありますか?

于 2008-11-26T15:40:55.723 に答える
1

複数の戦術を使用して、考えられるすべてのバリエーションを試して、まったく同じ問題に数日を費やしました。この投稿は私の問題を解決しました:

以下は、エラー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

どこ:

Public Class 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

クラス終了

于 2009-11-24T12:12:31.143 に答える
1

ここで WAG... oleviewを使用してアセンブリを調べて、パブリック インターフェイスに com コンシューマーに表示されるインデクサーがあることを確認しましたか? 2 つ目の WAG は、インデクサー プロパティを使用するのではなく、get_Item メソッドを直接使用することです (CLS 準拠の問題)...

于 2008-11-25T16:03:01.543 に答える