www.digitalmars.com         C & C++   DMDScript  

c++.stlsoft - COMSTL Enumeration Policy for IEnumerable?

reply Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi!


There are currently three enumeration policies for use with  
collection_sequence in STLSOFT:

new_enum_property_policy, which calls get__NewEnum on the collection  
interface.

new_enum_method_policy, which calls _NewEnum (as a method) on the  
collection interface.

new_enum_by_dispid_policy, which gets the property with DISPID_NEWENUM  
through IDispatch.

IMHO a fourth policy class should be added, which calls GetEnumerator on  
the collection interface. That would cover collections with the  
IEnumerable interface, which ist exposed by .NET collections through COM  
interop.

It would be a useful addition.



So Long...

Gabor
Feb 12 2009
parent reply "Matthew Wilson" <matthew hat.stlsoft.dot.org> writes:
Maybe it's too long since I did any COM/Interop, but I really don't have a clue
how that would work. Isn't IEnumerable a .NET type,
not a COM type?

Please clarify

Thanks

Matt

"Gabor Fischer" <Gabor.Fischer systecs.com> wrote in message
news:Avl7$nP4QNB systecs.com...
 Hi!


 There are currently three enumeration policies for use with
 collection_sequence in STLSOFT:

 new_enum_property_policy, which calls get__NewEnum on the collection
 interface.

 new_enum_method_policy, which calls _NewEnum (as a method) on the
 collection interface.

 new_enum_by_dispid_policy, which gets the property with DISPID_NEWENUM
 through IDispatch.

 IMHO a fourth policy class should be added, which calls GetEnumerator on
 the collection interface. That would cover collections with the
 IEnumerable interface, which ist exposed by .NET collections through COM
 interop.

 It would be a useful addition.



 So Long...

 Gabor

Feb 12 2009
next sibling parent reply Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi Matthew!

 Maybe it's too long since I did any COM/Interop, but I really don't have a
 clue how that would work. Isn't IEnumerable a .NET type, not a COM type?

Yes, IEnumerable is a .NET type, but it is exposed to COM via interop. On the .NET side it is defined as: using System.Runtime.InteropServices; namespace System.Collections { // Summary: // Exposes the enumerator, which supports a simple iteration // over a non-generic collection. [ComVisible(true)] [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerable { // Summary: // Returns an enumerator that iterates through a collection. // // Returns: // An System.Collections.IEnumerator object that can be used // to iterate through the collection. [DispId(-4)] IEnumerator GetEnumerator(); } } This inteface is exposed to COM clients through interop. You can access it from C++ by importing mscorlib with #import "mscorlib.tlb" named_guids no_namespace no_smart_pointers raw_interfaces_only raw_native_types Then a file named mscorlib.tlh is generated, which, among other things, has the following definition: struct __declspec(uuid("496b0abe-cdee-11d3-88e8-00902754c43a")) IEnumerable : IDispatch { // // Raw methods provided by interface // virtual HRESULT __stdcall GetEnumerator ( /*[out,retval]*/ struct IEnumVARIANT * * pRetVal ) = 0; }; As you can see, with GetEnumerator you get the well known IEnumVARIANT interface, which you can then use to enumerate the collection. Scripting clients can get the enumerator through IDispatch. Because the GetEnumerator method has a DispId of -4 (set by the DispId attribute in the definition above), which is DISPID_NEWENUM, the same DispId as the _NewEnum property of normal COM collection interfaces, they don't notice a difference and can work with IEnumerable. But for C++ clients, GetEnumerator is more appropriate. In my project I currently use following policy class for collection_sequence with IEnumerable: template <class CI> struct get_enumerator_policy { public: typedef CI collection_interface; public: static HRESULT acquire(collection_interface *pcoll, LPUNKNOWN *ppunkEnum) { ATLASSERT(NULL != pcoll); ATLASSERT(NULL != ppunkEnum); CComPtr<IEnumVARIANT> spEnum; HRESULT hr = pcoll->GetEnumerator(&spEnum); if (FAILED(hr)) return hr; return spEnum->QueryInterface(ppunkEnum); } }; I think such a policy class would be a useful addition to COMSTL (of course the ATL assertions and smart pointer will have to be replaced by the COMSTL ones). So Long... Gabor
Feb 13 2009
next sibling parent reply "Matthew Wilson" <matthew hat.stlsoft.dot.org> writes:
Ok. Thanks for clarifying.

Question: since it exposes via DISPID_NEWENUM, why not just use the
new_enum_by_dispid_policy<> policy?

Matt

"Gabor Fischer" <Gabor.Fischer systecs.com> wrote in message
news:Avl7cfc4QNB systecs.com...
 Hi Matthew!

 Maybe it's too long since I did any COM/Interop, but I really don't have a
 clue how that would work. Isn't IEnumerable a .NET type, not a COM type?

Yes, IEnumerable is a .NET type, but it is exposed to COM via interop. On the .NET side it is defined as: using System.Runtime.InteropServices; namespace System.Collections { // Summary: // Exposes the enumerator, which supports a simple iteration // over a non-generic collection. [ComVisible(true)] [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerable { // Summary: // Returns an enumerator that iterates through a collection. // // Returns: // An System.Collections.IEnumerator object that can be used // to iterate through the collection. [DispId(-4)] IEnumerator GetEnumerator(); } } This inteface is exposed to COM clients through interop. You can access it from C++ by importing mscorlib with #import "mscorlib.tlb" named_guids no_namespace no_smart_pointers raw_interfaces_only raw_native_types Then a file named mscorlib.tlh is generated, which, among other things, has the following definition: struct __declspec(uuid("496b0abe-cdee-11d3-88e8-00902754c43a")) IEnumerable : IDispatch { // // Raw methods provided by interface // virtual HRESULT __stdcall GetEnumerator ( /*[out,retval]*/ struct IEnumVARIANT * * pRetVal ) = 0; }; As you can see, with GetEnumerator you get the well known IEnumVARIANT interface, which you can then use to enumerate the collection. Scripting clients can get the enumerator through IDispatch. Because the GetEnumerator method has a DispId of -4 (set by the DispId attribute in the definition above), which is DISPID_NEWENUM, the same DispId as the _NewEnum property of normal COM collection interfaces, they don't notice a difference and can work with IEnumerable. But for C++ clients, GetEnumerator is more appropriate. In my project I currently use following policy class for collection_sequence with IEnumerable: template <class CI> struct get_enumerator_policy { public: typedef CI collection_interface; public: static HRESULT acquire(collection_interface *pcoll, LPUNKNOWN *ppunkEnum) { ATLASSERT(NULL != pcoll); ATLASSERT(NULL != ppunkEnum); CComPtr<IEnumVARIANT> spEnum; HRESULT hr = pcoll->GetEnumerator(&spEnum); if (FAILED(hr)) return hr; return spEnum->QueryInterface(ppunkEnum); } }; I think such a policy class would be a useful addition to COMSTL (of course the ATL assertions and smart pointer will have to be replaced by the COMSTL ones). So Long... Gabor

Feb 13 2009
next sibling parent "Matthew Wilson" <matthew hat.stlsoft.dot.org> writes:
You have me convinced.

Could you send me the smallest possible .NET & C++ projects that demonstrate
the need, and I'll work on it. Also, if you want to
supply an implementation for the new policy, I wouldn't say no. ;-)

Matt

"Gabor Fischer" <Gabor.Fischer systecs.com> wrote in message
news:Avx7jvu$QNB systecs.com...
 Hi Matthew!

 Question: since it exposes via DISPID_NEWENUM, why not just use the
 new_enum_by_dispid_policy<> policy?

I just found another reason: In new_enum_by_dispid_policy, you call QueryInterface for IDispatch. If the .NET class has more than one interface, the standard way for interop is to convert them all to dual interfaces on the COM side (you can override this behaviour with interop attributes on the .NET interfaces). If you then call QueryInterface for IDispatch, you get the IDispatch for the default interface of the object, which may or may not be IEnumerable. If the default interface is not IEnumerable, new_enum_by_dispid_policy fails. So Long... Gabor

Feb 17 2009
prev sibling parent Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi Matthew!

 In the client project I included a file get_enumerator_policy.h with a
 suggested implementation of the new policy.

I have further played around with the sample projects and found out that if mscorlib.tlb is imported without the raw_interfaces_only attribute, yet another policy class is needed, which calls raw_GetEnumerator instead of GetEnumerator. My suggested implementation would be the following: template <class CI> struct raw_get_enumerator_policy { public: typedef CI collection_interface; public: static HRESULT acquire(collection_interface *pColl, LPUNKNOWN *ppunkEnum) { COMSTL_ASSERT(NULL != pColl); COMSTL_ASSERT(NULL != ppunkEnum); IEnumVARIANT* pEnum = NULL; HRESULT hr = pColl->raw_GetEnumerator(&pEnum); if (FAILED(hr)) return hr; stlsoft::ref_ptr<IEnumVARIANT> spEnum(pEnum, false); return spEnum->QueryInterface(ppunkEnum); } }; The reason is the behaviour of the #import directive of Visual C++. Without the attribute raw_interfaces_only, it creates wrapper methods around the real interface calls which do some argument conversion and error checking, and it prefixes the real method names with raw_. So to cover both cases, two new enumeration policies are needed, the get_enumerator_policy which I included in my test project and the raw_get_enumerator_policy above. And the only difference between the two is the method name called. So Long... Gabor
Feb 18 2009
prev sibling parent Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi Matthew!

 Could you send me the smallest possible .NET & C++ projects that demonstrate
 the need, and I'll work on it.

Just posted a zip file. It has a Visual C++ 9 solution with two projects, InteropCollectionServer and InteropCollectionClient. The server is a .NET project in C# set up for interop. The client is a C++ project that ues an IEnumerable interface provided by the server. Please let me know if you have any problems with compiling and running it.
 Also, if you want to supply an implementation
 for the new policy, I wouldn't say no. ;-)

In the client project I included a file get_enumerator_policy.h with a suggested implementation of the new policy. So Long... Gabor
Feb 18 2009
prev sibling next sibling parent Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi Matthew!

 Question: since it exposes via DISPID_NEWENUM, why not just use the
 new_enum_by_dispid_policy<> policy?

That would be possible. But for C++ clients, using the vtable part of a dual interface is more efficient. With "normal" collection interfaces, I don't use new_enum_by_dispid_policy either, but new_enum_property_policy. Why should it be not the same with IEnumerable? So Long... Gabor
Feb 16 2009
prev sibling parent Gabor.Fischer systecs.com (Gabor Fischer) writes:
Hi Matthew!

 Question: since it exposes via DISPID_NEWENUM, why not just use the
 new_enum_by_dispid_policy<> policy?

I just found another reason: In new_enum_by_dispid_policy, you call QueryInterface for IDispatch. If the .NET class has more than one interface, the standard way for interop is to convert them all to dual interfaces on the COM side (you can override this behaviour with interop attributes on the .NET interfaces). If you then call QueryInterface for IDispatch, you get the IDispatch for the default interface of the object, which may or may not be IEnumerable. If the default interface is not IEnumerable, new_enum_by_dispid_policy fails. So Long... Gabor
Feb 16 2009