2008-11-25 19 views
5

Tôi có một assembly .NET mà tôi đang truy cập từ VBScript (ASP cổ điển) thông qua COM interop. Một lớp có một trình chỉ mục (thuộc tính mặc định a.k.a.) mà tôi đã làm việc từ VBScript bằng cách thêm thuộc tính sau vào chỉ mục: [DispId(0)]. Nó hoạt động trong hầu hết các trường hợp, nhưng không phải khi truy cập vào lớp như là một thành viên của một đối tượng khác.Tại sao trình lập chỉ mục trên thành phần .NET của tôi không phải lúc nào cũng có thể truy cập được từ VBScript?

Tôi làm cách nào để nó hoạt động với cú pháp sau: Parent.Member("key") trong đó Thành viên có trình chỉ mục (tương tự như truy cập thuộc tính mặc định của built-in Request.QueryString: Request.QueryString("key"))?

Trong trường hợp của tôi, có một lớp cha TestRequest với thuộc tính QueryString trả về một IRequestDictionary, có trình chỉ mục mặc định.

VBScript ví dụ:

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

Các dòng sau gây ra một lỗi thay vì in ấn "giá trị". Đây là cú pháp Tôi muốn được làm việc:

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

Microsoft VBScript runtime (0x800A01C2)
Sai số của các đối số hoặc chuyển nhượng bất động sản không hợp lệ: 'QueryString'

Tuy nhiên, sau đây các dòng do hoạt động mà không có lỗi và xuất "giá trị" mong đợi (lưu ý rằng dòng đầu tiên truy cập vào trình chỉ mục mặc định trên biến tạm thời):

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

Dưới đây là các giao diện và lớp học được đơn giản hóa trong C# 2.0. Chúng đã được đăng ký qua 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; } 
    } 
} 

Tôi đã thử nghiên cứu và thử nghiệm các tùy chọn khác nhau nhưng chưa tìm được giải pháp. Bất kỳ trợ giúp nào cũng sẽ được đánh giá cao để tìm ra lý do tại sao cú pháp testRequest.QueryString("key") không hoạt động và cách làm cho nó hoạt động.

Lưu ý: Đây là lần theo dõi Exposing the indexer/default property via COM Interop.

Cập nhật: Dưới đây là một số các IDL tạo ra từ các thư viện kiểu (sử dụng 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); 
}; 
+0

tôi cũng có vấn đề này và đã dành nhiều giờ cố gắng để giải quyết nó, không ai có bất cứ lời khuyên tốt ở đây? – MJJames

Trả lời

3

Tôi tình cờ gặp vấn đề chính xác này vài ngày trước. Tôi không thể tìm thấy một lời giải thích hợp lý là tại sao nó không hoạt động.

Sau khi dành nhiều thời gian để thử các cách giải quyết khác nhau, tôi nghĩ cuối cùng tôi đã tìm thấy thứ gì đó có vẻ hoạt động và không quá bẩn thỉu. Những gì tôi đã làm là thực hiện accessor cho bộ sưu tập trong đối tượng container như là một phương thức, thay vì một thuộc tính. Phương thức này nhận được một đối số, khóa. Nếu khóa là "thiếu" hoặc null, thì phương thức trả về bộ sưu tập (điều này xử lý các biểu thức như "testRequest.QueryString.Count" trong VbScript). Nếu không, phương thức trả về một mục cụ thể từ bộ sưu tập. Một phần bẩn với phương pháp này là phương thức này trả về một đối tượng (vì đôi khi tham chiếu trả về là tập hợp và đôi khi là một mục của bộ sưu tập), do đó, sử dụng nó từ mã được quản lý cần đúc ở mọi nơi. Để tránh điều này, tôi đã tạo thuộc tính khác (lần này là thuộc tính thích hợp) trong vùng chứa hiển thị bộ sưu tập. Khách sạn này KHÔNG được tiếp xúc với COM. Từ mã C#/được quản lý, tôi sử dụng thuộc tính này và từ mã COM/VbScript/không được quản lý, tôi sử dụng phương thức này.

Đây là một thực hiện các workaround trên sử dụng các ví dụ về chủ đề này:

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

Cảm ơn bạn đã trả lời kỹ lưỡng. Tôi chưa có cơ hội để kiểm tra điều này nhưng sẽ cố gắng sớm. –

1

WAG ở đây ... Bạn đã kiểm tra lắp ráp của bạn với oleview để đảm bảo giao diện công cộng của bạn có một indexer có thể nhìn thấy để com người tiêu dùng? Thứ hai WAG là sử dụng phương thức get_Item trực tiếp, thay vì cố gắng sử dụng thuộc tính indexer (các vấn đề tuân thủ CLS) ...

+0

Cảm ơn bạn đã phản hồi. Sử dụng một biến tạm thời với một trình chỉ mục làm việc (như testQueryString ("key")), nhưng việc truy cập nó như testRequest.QueryString ("key") không hoạt động. –

+0

Tôi đã thử bằng cách sử dụng get_Item và nó đã có cùng một vấn đề mà tôi có thể sử dụng các chỉ mục từ một biến tạm thời nhưng không giống như testRequest.QueryString ("key"). –

+0

Tôi nhận được oleview làm việc từ bộ tài nguyên giành chiến thắng 2003 và đăng IDL được tạo. PS Có phải chữ cái đầu của bạn là "WAG", hay điều đó có nghĩa là cái gì khác? –

1

Tôi thấy rằng testRequest.QueryString()("key") hoạt động, nhưng những gì tôi muốn là testRequest.QueryString("key").

Tôi tìm thấy một bài viết rất phù hợp theo số Eric Lippert (người có một số bài viết thực sự tuyệt vời về VBScript, nhân tiện). Bài viết, VBScript Default Property Semantics, thảo luận về các điều kiện cho việc có nên gọi thuộc tính mặc định hay chỉ gọi phương thức. Mã của tôi hoạt động như một cuộc gọi phương thức, mặc dù nó dường như đáp ứng các điều kiện cho một thuộc tính mặc định.

Dưới đây là các quy tắc từ bài viết của Eric:

Các quy tắc cho người thực hiện IDispatch :: Gọi là nếu tất cả các sau là đúng:

  • người gọi gọi một tài sản
  • người gọi chuyển danh sách đối số
  • thuộc tính không thực sự lấy danh sách đối số
  • rằng tài sản trả về một đối tượng
  • rằng đối tượng có thuộc tính mặc định
  • rằng bất động sản mặc định có một danh sách đối số

sau đó gọi thuộc tính mặc định với danh sách đối số.

Bất kỳ ai trong số các điều kiện này không được đáp ứng? Hoặc có thể thực thi .NET mặc định là IDispatch.Invoke hoạt động khác? Bất kỳ đề xuất?

1

Tôi đã dành một vài ngày với cùng vấn đề chính xác cố gắng mọi biến thể có thể sử dụng nhiều chiến thuật. Bài đăng này đã giải quyết được sự cố của tôi:

sau đây được sử dụng để tạo lỗi parentobj.childobj (0) trước đây phải làm: parentobj.childobj.mục (0)

bằng cách thay đổi:

Default Public ReadOnly Property Item(ByVal key As Object) As string 
    Get 
     Return strSomeVal 

    End Get 
End Property 

tới:

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 

nơi:

clsFieldProperties

Công Lớp 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 

End Class

5

Kết quả điều tra của tôi về chủ đề này:

Vấn đề là tương đối để thực hiện IDispatch thời gian chạy ngôn ngữ chung sử dụng khi để lộ giao diện kép và dispinterfaces để COM.

Ngôn ngữ kịch bản như VBScript (ASP) sử dụng triển khai IDLE tự động OLE Automation khi truy cập vào đối tượng COM.

Mặc dù có vẻ như nó hoạt động, tôi muốn giữ tài sản là tài sản và không muốn có một hàm (giải pháp thay thế được giải thích ở trên).

Bạn có 2 giải pháp khả thi:

1 - Sử dụng IDispatchImplAttribute bị phản đối với IDispatchImplType.CompatibleImpl.

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

Như đã nói trong MSDN, thuộc tính này không còn được dùng nhưng vẫn hoạt động với .Net 2.0, 3.0, 3.5, 4.0. Bạn phải quyết định xem thực tế là "không được chấp nhận" có thể là vấn đề cho bạn hay không ...

2 - Hoặc triển khai IReflect làm ID tùy chỉnh trong lớp của bạn TesRequest hoặc tạo một lớp chung thực hiện IReflect và tạo lớp của bạn kế thừa lớp được tạo mới này.

mẫu lớp Generic (phần interresting là trong phương pháp 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; } 
    } 
} 

và cho mã của Mike:

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

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

Tôi đã có một vấn đề mà tôi 'ComVisible' C# đối tượng đã được sử dụng từ một VBScript bằng cách sử dụng MSScriptControl. Khi các đối tượng đó sử dụng các thuộc tính được lập chỉ mục, tập lệnh không thành công. Tôi đã sử dụng tùy chọn thứ hai để tạo một lớp cơ sở cho các đối tượng đó và vấn đề đã được giải quyết. Giải pháp rất đẹp! –

2

giải pháp tôi David Porcher làm việc cho tôi.

Nhưng mã mà ông đã đăng tải xử lý các phần Nhận của indexer, vì vậy tôi cập nhật mã của mình để xử lý cũng là Set phần của indexer

Đây là mã Cập nhật:

// 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); 
     } 
    } 
Các vấn đề liên quan