为什么我的.NET组件上的索引器总是无法从VBScript访问?
我有一个.NET程序集,我通过COM interop从VBScript(传统ASP)访问。一类有索引器(a.k.a.默认属性),我通过向索引器添加以下属性从VBScript获得工作:[DispId(0)]
。它在大多数情况下工作,但不是在作为另一个对象的成员访问该类时。为什么我的.NET组件上的索引器总是无法从VBScript访问?
我怎样才能使它符合以下语法:Parent.Member("key")
其中成员有索引器(类似于访问内置Request.QueryString
的默认属性:Request.QueryString("key")
)?
在我的情况下,有一个父类TestRequest
与QueryString
属性,它返回IRequestDictionary
,它具有默认的索引器。
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)
的参数或无效的属性赋值错误号码: '查询字符串'
但是,以下行做工作没有错误,并输出预期的“值”(注意第一行访问临时变量上的默认索引器):
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")
语法不起作用,以及如何让它工作。
注意:这是Exposing the indexer/default property via 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);
};
我在这个确切的问题,前几天偶然。我找不到合理的解释,为什么它不起作用。
花费很长时间尝试不同的解决方法后,我想我终于找到的东西,似乎工作,并且不那么脏。我所做的是将容器对象中的集合的访问器作为方法实现,而不是属性。该方法接收一个参数,即键。如果键是“missing”或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
}
WAG这里...您可以检查您的装配与oleview,以确保您的公共接口有可见的索引给com消费者?第二个WAG是直接使用get_Item方法,而不是尝试使用索引器属性(CLS合规性问题)...
感谢您的回应。与索引器一起使用临时变量确实可行(如testQueryString(“key”)),但像testRequest.QueryString(“key”)那样访问它不起作用。 – 2008-11-25 16:50:24
我尝试过使用get_Item,它具有相同的问题,我可以使用索引器从临时变量但不像testRequest.QueryString(“key”)。 – 2008-11-25 16:56:18
我从win 2003资源工具包中获得了oleview,并发布了生成的IDL。 PS你的首字母是“WAG”,还是这意味着别的? – 2008-11-25 17:50:03
我发现testRequest.QueryString()("key")
的作品,但我想要的是testRequest.QueryString("key")
。
我发现Eric Lippert(他有一些非常棒的VBScript文章,顺便说一下)非常相关的文章。文章VBScript Default Property Semantics讨论了是调用默认属性还是仅调用方法的条件。我的代码行为像一个方法调用,虽然它似乎符合默认属性的条件。
以下是Eric的文章规则:
为 的IDispatch ::调用的执行者的规则是,如果所有的 以下为真:
- 呼叫者调用属性
- 呼叫者传递参数列表
- 该属性实际上没有参数列表
- 该属性返回一个对象
- 该对象是否具有默认属性
- 该默认属性采用参数列表
然后用 参数列表调用默认属性。
谁能告诉如果有这些条件没有得到满足?或者有可能IDispatch.Invoke
的默认.NET实现的行为有所不同?有什么建议么?
我花了几天的使用完全相同的问题,尝试使用多种战术每一个可能的变化。这篇文章解决了我的问题:
根据用于产生错误 parentobj.childobj(0) 以前曾做的事: parentobj.childobj。项(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
其中:
公共类clsFieldProperties 私人_intCount作为整数
Sub New(ByVal intCount As Integer)
_intCount = intCount
End Sub
Public ReadOnly Property Count() As Integer
Get
Return _intCount
End Get
End Property
个末级
我对这个问题的调查结果:
的问题是相对于暴露双接口和调度接口到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自定义的IDispatch或创建实现减反射泛型类,使你的类继承了这个新创建的类。
泛型类样品(该interresting部分是在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; }
}
}
我大卫PORCHER解决方案为我工作。
不过,他发布的代码处理索引的获取一部分,所以我更新了他的代码也处理索引
以下是更新后的代码的设置部分:
// 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);
}
}
我也有这个问题,并花了数小时试图解决它,有没有人有任何好的建议吗? – MJJames 2009-08-03 15:49:51