c++.stlsoft - COMSTL Enumeration Policy for IEnumerable?
- Gabor.Fischer systecs.com (Gabor Fischer) (16/16) Feb 12 2009 Hi!
- Matthew Wilson (6/22) Feb 12 2009 Maybe it's too long since I did any COM/Interop, but I really don't have...
- Gabor.Fischer systecs.com (Gabor Fischer) (71/73) Feb 13 2009 Yes, IEnumerable is a .NET type, but it is exposed to COM via interop. O...
- Matthew Wilson (4/77) Feb 13 2009 Ok. Thanks for clarifying.
- Gabor.Fischer systecs.com (Gabor Fischer) (7/9) Feb 16 2009 That would be possible. But for C++ clients, using the vtable part of a ...
- Gabor.Fischer systecs.com (Gabor Fischer) (11/13) Feb 16 2009 I just found another reason: In new_enum_by_dispid_policy, you call
- Matthew Wilson (5/18) Feb 17 2009 You have me convinced.
- Gabor.Fischer systecs.com (Gabor Fischer) (10/14) Feb 18 2009 Just posted a zip file. It has a Visual C++ 9 solution with two projects...
- Gabor.Fischer systecs.com (Gabor Fischer) (34/36) Feb 18 2009 I have further played around with the sample projects and found out that...
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
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
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
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
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
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
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
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 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
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