.NETコンポーネントのインデクサーがVBScriptから常にアクセスできるとは限らないのはなぜですか?
-
11-07-2019 - |
質問
VBScript(クラシックASP)からCOM相互運用機能を介してアクセスしている.NETアセンブリがあります。 1つのクラスにはインデクサー(デフォルトプロパティとも呼ばれます)があり、インデクサーに次の属性を追加することでVBScriptから取得しました: [DispId(0)]
。ほとんどの場合は機能しますが、別のオブジェクトのメンバーとしてクラスにアクセスする場合は機能しません。
次の構文で動作させるにはどうすればよいですか: Parent.Member(" key")
ここで、メンバーはインデクサーを持ちます(組み込みの Request.QueryString
: Request.QueryString(" key")
)?
私の場合、デフォルトのインデクサーを持つ IRequestDictionary
を返す QueryString
プロパティを持つ親クラス TestRequest
があります。
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'
ただし、次の行は do エラーなしで機能し、期待される「値」を出力します。 (最初の行は一時変数のデフォルトのインデクサーにアクセスすることに注意してください):
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相互運用経由。
更新:タイプライブラリから生成された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);
};
解決
数日前にこの問題に出くわしました。なぜ機能しないのかについての合理的な説明が見つかりませんでした。
さまざまな回避策を試みて長い時間を費やした後、私はようやく機能しているようで、それほど汚れていないものを見つけたと思います。私がやったのは、プロパティではなくメソッドとしてコンテナオブジェクトのコレクションへのアクセサを実装することです。このメソッドは、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
}
他のヒント
この主題に関する私の調査の結果:
問題は、デュアルインターフェイスとディスパッチインターフェイスをCOMに公開するときに共通言語ランタイムが使用するIDispatch実装に関連しています。
VBScript(ASP)などのスクリプト言語は、COMオブジェクトにアクセスするときにOLEオートメーションIDispatch実装を使用します。
機能しているように見えますが、プロパティをプロパティとして保持し、機能を持たせたくありません(上記で説明した回避策)。
2つの解決策があります:
1-IDispatchImplType.CompatibleImplで非推奨のIDispatchImplAttributeを使用します。
[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-または、TesRequestクラスにIReflectをカスタムIDispatchとして実装するか、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; }
}
}
私はDavid Porcherのソリューションでうまくいきます。
ただし、彼が投稿したコードはインデクサーの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);
}
}
WAG here ... oleview を使用して、パブリックインターフェースにcomコンシューマーに表示されるインデクサーがあることを確認しますか? 2番目のWAGは、インデクサープロパティを使用するのではなく、get_Itemメソッドを直接使用することです(CLS準拠の問題)...
testRequest.QueryString()(" key")
は機能することがわかりましたが、欲しいのは testRequest.QueryString(" key")
です。
Eric Lippert による非常に関連性の高い記事を見つけました(ちなみに、VBScriptに関する本当に素晴らしい記事をいくつか持っています)。記事 VBScript Default Property Semantics では、デフォルトのプロパティを呼び出すか、メソッド呼び出しだけを呼び出すかの条件。私のコードはメソッド呼び出しのように動作していますが、デフォルトのプロパティの条件を満たしているようです。
エリックの記事のルールは次のとおりです。
の実装者のルール IDispatch :: Invokeは、すべてが 次のとおりです。
- 呼び出し元がプロパティを呼び出します
- 呼び出し元が引数リストを渡します
- プロパティは実際には引数リストを取りません
- そのプロパティはオブジェクトを返します
- そのオブジェクトにはデフォルトのプロパティがあります
- そのデフォルトのプロパティは引数リストを取ります
次に、デフォルトのプロパティを呼び出します 引数リスト。
これらの条件のいずれかが満たされていないかどうかは誰にもわかりますか?または、 IDispatch.Invoke
のデフォルトの.NET実装が異なる動作をする可能性はありますか?提案はありますか?
複数の戦術を使用して考えられるすべてのバリエーションを試して、まったく同じ問題で数日を費やしました。この投稿は私の問題を解決しました:
エラーの生成に使用されるフォロー 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
to:
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
where:
パブリッククラス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
終了クラス