The Problem
VB/VBScript provides the implementation of For-Each using an implementation of
IEnumVARIANT exposed from a collection via the _NewEnum method. Unfortunately,
VB uses 1 for the argument to Next, making round-trip time terrible.
A Solution:
If you don’t own the collection implementation code…
If you don't own the collection implementation code, you can still wrap the
collection in a custom object called the CollectionBuffer, which implements the
following interface:
[
object,
uuid(B6175081-83D4-11D2-987D-00600823CFFB),
dual,
helpstring("ICollectionBuffer Interface"),
pointer_default(unique)
]
interface ICollectionBuffer : IDispatch
{
[propget, helpstring("property Collection")] HRESULT Collection([out, retval] IDispatch** pVal);
[propput, helpstring("property Collection")] HRESULT Collection([in] IDispatch* newVal);
[propget, helpstring("property BufferSize")] HRESULT BufferSize([out, retval] long *pVal);
[propput, helpstring("property BufferSize")] HRESULT BufferSize([in] long newVal);
[propget, id(DISPID_NEWENUM)] HRESULT _NewEnum([out, retval] IUnknown** ppunkEnum);
};
The Collection property is used to pass in the collection object to which you'd
like buffered access. The BufferSize property is used to control how many
VARIANTs you'd like to have returned on each call to Next. The implementation
of _NewEnum gets the IEnumVARIANT interface from the collection and wraps it in
a buffered implementation of IEnumVARIANT called SmartEnumVARIANT.
The SmartEnumVARIANT object manages buffered access to the underlying
IEnumVARIANT. VB can still ask for one item at a time, but the items will come
out of the buffer. Depending on the buffer size, this can improve performance
by many order of magnitude.
Usage
The following VB code uses an unbuffered collection and is slow:
Dim coll As Object
Set coll = CreateObject("DumbEnumSvr.CollectionOfNumbers")
coll.CountOfNumbers = 10000
Dim v As Variant
Dim c As Long
For Each v In coll
c = c + 1
Next v
MsgBox "Counted " & c
This VB code wraps the unbuffered collection in the CollectionBuffer object:
Dim collBuffer As Object
Set collBuffer = CreateObject("SmartEnumSvr.CollectionBuffer")
collBuffer.Collection = coll
collBuffer.BufferSize = 1024
c = 0
For Each v In collBuffer
c = c + 1
Next v
MsgBox "Counted " & c
Another Solution:
If you do own the collection implementation code…
If you do have the source to the collection, there's no reason that VB clients
have to do anything. Instead, your implementation of IEnumVARIANT can use a
manual form of handler marshaling (invented, as far as I know, by Don Box --
the manual form, MS invented handler marshaling...).
The handler marshaling works like this:
-
The server-side implementation of IEnumVARIANT implements IMarshal.
-
Server-side enumerator returns CLSID_SmartEnumVARIANT when GetUnmarshalClass is
called.
-
Server-side enumerator marshals the interface of it’s own stub (obtained by
calling CoGetStandardMarshal) into the stream provided during the call to
GetMarshalSizeMax and MarshalInterface.
-
On the client-side, the Proxy Manager used the CLSID provided by the
server-side enumerator to create an instance of SmartEnumVARIANT.
-
The SmartEnumVARIANT object also implements IMarshal and it will pull the
marshaled stub to the real implementation of IEnumVARIANT out of the marshal
packet.
-
The SmartEnumVARIANT becomes a “smart proxy,” forwarding calls to the
server-side implementation of IEnumVARIANT. However, instead of forwarding
_Next calls directly, the calls are buffered for efficiency.
This solution is nice because the VB client doesn't have to do anything
special. When the server-side implementation of IEnumVARIANT is marshaled to
the client, the SmartEnumVARIANT automatically becomes the smart proxy, making
buffered calls on the server.
For the handler marshaling to work, the server-side enumerator must implement
IMarshal appropriately. In UseSmartEnum.h, I’ve provided an implementation of
IMarshal for this purpose:
template <typename Deriving, ULONG celtBuffer = 1024>
class IMarshalForSmartEnumImpl : public IMarshal {...};
For example, the CollectionOfNumbers2 example class uses
IMarshalForSmartEnumImpl to add IMarshal to CComEnum like so:
typedef CComEnum< IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _Copy<VARIANT> > CComEnumVariant;
class CSmartProxiedEnumVARIANT :
public CComEnumVariant,
public IMarshalForSmartEnumImpl<CSmartProxiedEnumVARIANT, 1024>
{
public:
BEGIN_COM_MAP(CSmartProxiedEnumVARIANT)
COM_INTERFACE_ENTRY(IMarshal)
COM_INTERFACE_ENTRY_CHAIN(CComEnumVariant)
END_COM_MAP()
};
The Source
CollectionBuffer, SmartEnumVARIANT, IMarshalForSmartEnumVARIANT and two
samples, CollectionOfNumbers (which doesn't use handler marshaling) and
CollectionOfNumbers2 (which does use handler marshaling) are available
here.
Copyright
This source is copyright (c) 1998, Chris Sells.
All rights reserved. No warrenties extended. Use at your own risk.
Comments to
csells@sellsbrothers.com