Thread overview
COMSTL Enumeration Policy for IEnumerable?
Feb 13, 2009
Gabor Fischer
Feb 13, 2009
Matthew Wilson
Feb 13, 2009
Gabor Fischer
Feb 13, 2009
Matthew Wilson
Feb 16, 2009
Gabor Fischer
Feb 16, 2009
Gabor Fischer
Feb 17, 2009
Matthew Wilson
Feb 18, 2009
Gabor Fischer
Feb 18, 2009
Gabor Fischer
February 13, 2009
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

February 13, 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
>


February 13, 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

February 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
>


February 16, 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

February 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

February 17, 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
>


February 18, 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 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

February 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