Supporting Components for "Write Only Interfaces". This .DLL provides services similar to COM+ Queued Components in that it allows you to generically record, playback and multicast method calls, stream them over a network etc. etc.
CoProxyImpl.DLL
CoWriteOnlyDispatch utilises CoProxyImpl.Dll to implement the CreateProxy() method of the IProxyStubFactory implementation.
Background
What do the following interfaces have in common?
interface IPriceBlockSink : IUnknown
{
HRESULT SetBidPrice([in] float Val);
HRESULT SetAskPrice([in] float Val);
HRESULT SetLastPrice([in] float Val);
};
interface ITextPropertyTreeBuilder : IUnknown
{
HRESULT SetProperty([in] BSTR name, [in] BSTR value);
HRESULT AddChild([in] BSTR name, [out, retval] ITextPropertyTreeBuilder **ppChild);
};
interface IXMLBuilder : IUnknown
{
HRESULT SetAttribute([in] BSTR name, [in] BSTR value);
HRESULT SetNamespaceURI([in] BSTR Namespace);
HRESULT SetValue([in] BSTR value);
HRESULT AddSubElement([in] BSTR tag, [out, retval] IXMLBuilder **ppSubElement);
};
Think about how these interfaces are normally used.
It's fair to say that for many such interfaces, the "HRESULT" is being used solely to handle errors. It's highly unlikely that any clients check the HRESULT for anything but to see if it SUCCEEDED. This "error" is not information that the client actually cares to know about immediately anyway. The failure of such a method call serves to tell the client "You may as well not bother talking to me anymore and release me anyway - because for whatever reason (Out of Memory, Lost the Network connection) I can no longer handle your requests." But the general expectation for the above interfaces is that a successful HRESULT will be returned.
Think about any implementation of such an interface, and I'd bet that for 90% of them, as soon as one method call returns failure, all subsequent method calls will simply serve to return failure also.
So the HRESULT is not really being used to return "useful" information. It's being used to communicate a hint to the client to stop making calls, and it's simply considered inefficient, but not illegal, for clients to continue making calls after the first call has failed.
The other issue is that such interfaces are rarely expected to return a NULL pointer. For example, one wouldn't normally think twice about the following client code of IXMLBuilder:
CComPtr<IXMLBuilder> pChild; HRESULT hr = pXMLBuilder-AddSubElement(bstrPriceTag, &pChild); if(FAILED(hr)) return hr; hr = pChild->SetValue(bstrPrice);
In reality, there is nothing in the IDL here that says that pChild cannot be 0 after the method returns a successful HRESULT.
But we all know that in reality this was never the intended use of this interface.
The net result then is that after any method call on such interfaces, a FAILED HRESULT or a NULL return interface indicates a truly "exceptional" circumstance that will propagate up to a much higher level in the client application.
We rarely document such interfaces saying things like "AddSubElement will return 0 if an element of that name already exists". Such code just makes the client code more complex. In a situation like this, we'll just return a reference to the existing interface - this is more polymorphic anyway.
What are we left with then? For any given method call, the "Caller" of the interface is only ever receiving one thing from the "Callee" - a new interface for receiving method calls.
OK, OK Cut To the Chase Already!
For the interfaces described above, all of the "Real" Information is only actually travelling from the "Caller" to the "Callee".
I call these interfaces "WriteOnlyInterfaces".
Once you constrain yourself to the fact that information only ever travels from the caller to the callee, you can do all sorts of cool things. For each of the above interfaces, it's possible to
CoWriteOnlyDispatch gives you some useful stuff to help with doing these things.
Everything housed in CoWriteOnlyDispatch.DLL centers around the following interface, defined in CoWriteOnlyDispatch.idl:
[
object,
uuid(A7489E33-2295-11D4-9D22-009027133993),
helpstring("An interface for which all information flows from the caller to the callee"),
oleautomation
]
interface IWriteOnlyDispatch : IUnknown
{
// POST: FAILED(hr) || (*ppNewContext != 0)
[helpstring("Invoke a method in which information flows from caller to callee")]
HRESULT InvokeMethod(
[in] long nWhichMethod,
[in, unique] SAFEARRAY(VARIANT) ArgList,
[out, retval] IWriteOnlyDispatch** ppNewContext);
};
Note the post condition - InvokeMethod should not return 0 in *ppNewContext along with a successful HRESULT, as this could only be an attempt to communicate information back to the caller, and as you'll see, the Recorder, TeeDecorator and other components won't be able to do this anyway.
CoWriteOnlyDispatch.DLL has one CoClass in it, called "Factory". It implements the IFactory interface, which is used for creating all of the components in CoWriteOnlyDispatch.DLL.
[
object,
uuid(A7489E38-2295-11D4-9D22-009027133993),
oleautomation
]
interface IFactory : IUnknown
{
[helpstring("Multicasts all methods calls to both pFirst and then pSecond. If you want to multicast to more than one other sink - create TeeDecorators on top of other TeeDecorators!")]
HRESULT CreateTeeDecorator(
[in] IWriteOnlyDispatch* pFirst,
[in] IWriteOnlyDispatch* pSecond,
[out, retval] IWriteOnlyDispatch** ppVal);
[helpstring("Ignores all method calls - returns S_OK for everything.")]
HRESULT CreateNull([out, retval] IWriteOnlyDispatch** ppVal);
[helpstring("Forwards all method calls until BlowFuse is called. Then ignores all method calls (forwards them to Null)")]
HRESULT CreateFuseDecorator(
[in] IWriteOnlyDispatch* pTarget,
[out, retval] IFuseDecorator** ppVal);
[helpstring("Records all method calls")]
HRESULT CreateRecorder([out, retval] IRecorder **ppRecorder);
// Alas - BSTR is the only pragmatic way of passing an IID to
// an oleautomation interface!
[helpstring("Create Proxy/Stub Factory to map between vtable and IWriteOnlyDispatch")]
HRESULT GetProxyStubFactory([in] BSTR string_iid,
[out, retval] IProxyStubFactory **ppVal);
};
GetProxyStubFactory is the most useful method in this interface, as it exposes the "hardest" functionality of this module. It locates the TypeLibrary for the specified interface (it only works with OleAutomation interfaces), and uses this type library information to implement the IProxyStubFactoryInterface:
[
object,
uuid(DD2A3CDB-3014-11d4-9D24-009027133993),
oleautomation,
helpstring("A Proxy/Stub Factory - use to bridge between early-bound and late-bound interfaces")
]
interface IProxyStubFactory : IUnknown
{
// IUnknown here will be an interface that responds to QI for the
// "appropriate" IID - whatever that is.
[helpstring("Create implementation of a Write-Only interface on top of pWriteOnlyDispatch")]
HRESULT CreateProxy([in] IWriteOnlyDispatch *pWriteOnlyDispatch,
[out, retval] IUnknown **ppVTableInterface);
[helpstring("Create implemention of IWriteOnlyDispatch on top of a Write-Only interface")]
HRESULT CreateStub([in] IUnknown *pVTableInterface,
[out, retval] IWriteOnlyDispatch **ppWriteOnlyDispatch);
};
All of the other components in this .DLL work with instances of IWriteOnlyDispatch. You use IProxyStubFactory to create implementations of IWriteOnlyDispatch on top of a given Write Only OleAutomation interface (a Stub), and to create implementations of your Write Only OleAutomation interface on top of an IWriteOnlyDispatch instance (a Proxy). Creating a Proxy immediately on top of a Stub yields a logically identical implementation of the interface. That is, the following code
OLECHAR *pChar; ::StringFromIID(__uuidof(IXMLBuilder), &pChar); _bstr_t XMLBuilderIID(pChar); ::CoTaskMemFree(pChar); pProxyStubFactory = pFactory->GetProxyStubFactory(XMLBuilderIID); IXMLBuilderPtr pWrapped = pProxyStubFactory->CreateProxy(pProxyStubFactory->CreateStub(pBaseInterface));
yields an implementation that is logically equivalent to (although less efficient than) that yielded by:
IXMLBuilderPtr pWrapped = pBaseInterface;
Because information only ever flows in one direction, connections of Write Only Interfaces resemble "Pipelines" or "Electrical Circuits" with some of the familiar properties of "switches", "tee junctions", "buffers" or "Capacitors" and so on. The other components that can be created by IFactory are an initial set of "useful pipefittings" to go by this analogy. All of these components talk the same "language" - the IWriteOnlyDispatch interface.
pProxyStubFactory is the mechanism by which convert between your strongly typed Write Only OleAutomation interface and the weakly typed IWriteOnlyDispatch interface. This is the general usage pattern then - Create appropriate Proxys and Stubs to enable you to fit your interfaces into the other components, then utilise the functionality of the other "pipe fittings" as required.
Current LimitationsThe current implementation of IProxyStubFactory does not handle interfaces that are derived off of IDispatch. Similarly, the Proxy implementation cannot be instantiated on top of interfaces with methods that have "out" parameters. For example, if you pass in the IID of the following interface
interface ITextStream : IUnknown
{
HRESULT WriteText([in] BSTR Text);
HRESULT ReadText([out, retval] BSTR *pText);
};
to CreateProxy, it will fail, even though you might only have ever intended to call the WriteText method . CreateProxy could be made more useful by allowing you to instantiate a proxy, and simply failing when you try to invoke the ReadText method.
Dispatch support (dual interfaces) is not included simply because this would have required extra code to check for IDispatch and "ignore" such method calls, and IMO, it's better to just not use them anyway. Check the ScriptAdapter for an alternative to Duals.
Create a TeeDecorator using the CreateTeeDecorator method. A TeeDecorator forwards all method calls made on its IWriteOnlyDispatch interface to the First interface and then the Second interface. The TeeDecorator wraps each return interface in a new TeeDecorator. The TeeDecorator provides an easy way to "MultiCast" method calls made on a given oleautomation interface. If more than two listeners are required, you can create TeeDecorators on top of TeeDecorators.
Example Say you have a server that is broadcasting stock price updates and other information on a given interface. You might want to keep a log of all of the method calls made in a separate file, for subsequent analysis. So you've got two implementations of the "callback" interface here - one updates the user interface with the new changes, the other logs all updates to a text file. Rather than adding the "logging" logic to the user interface, or writing hundreds of tedious "forwarding" methods, you can create a TeeDecorator and by using the "ProxyStubFactory", connect your listeners to the actual server.

The Null object simply returns S_OK from every method call, and returns itself as the "New Context". By creating a "Proxy" on top of the "Null" object, you can make an implementation simply that ignores all calls.
[
object,
uuid(B9A355D1-24FF-11d4-9D22-009027133993),
oleautomation,
pointer_default(unique)
]
interface IFuseDecorator : IUnknown
{
[helpstring("Get input interface that forwards all method calls")]
HRESULT GetInput([out, retval] IWriteOnlyDispatch **ppVal);
[helpstring("Irreversibly release all interfaces - all subsequent calls go to Null")]
HRESULT BlowFuse();
};
The Fuse Decorator forwards all method calls to the IWriteOnlyDispatch interface that it was instantiated on, until BlowFuse() is called. When BlowFuse() is called, the interface, and all interfaces obtained from it, are released. Subsequent method calls simply return S_OK.
The FuseDecorator is useful for accumulating "temporary" state information from method calls, that is only needed in the computation of intermediate values.
Example For example, imagine you are writing something to listen to a "News Wire" that publishes news stories on the following interface:
interface IStoryBuilder : IUnknown
{
HRESULT SetPlace([in] BSTR Place);
HRESULT SetInterestLevel([in] int nLevel); // 0 - really boring article 100 - newsflash material
HRESULT SetStoryText([in] BSTR StoryText);
};
interface INewsListener : IUnknown
{
HRESULT AddStory([in] BSTR Title,
[in] DATE time,
[out, retval] IStoryBuilder **ppStoryBuilder);
};
You might have a "Seven O'Clock Nightly News" Story Collector that implements the INewsListener interface by keeping track of the top 20 stories coming in off the wire (ranked by InterestLevel). As each story comes in, method calls are made on the INewsListener interface - the Story Collector keeps an index of all stories it's received so far, ordered by InterestLevel. By 6:30pm, you want to take what you've got from the Story Collector and generate a transcript for the News reader to read
Once you've generated the transcript, and the news has been brought to air, you no longer want to hear about any more news stories. You could put a FuseDecorator between the News Story broadcaster and the Story Collector. The News Story broadcaster actually talks to the FuseDecorator, which initially forwards all calls to the Story Collector. At 6:30pm you call BlowFuse, which disconnects the Story Collector from the News Story broadcaster - without the News Story broadcaster ever knowing.

This example is a bit contrived, but I created the Fuse Decorator for a real world problem that was more complicated to explain - but similar in nature. The Fuse Decorator helps you solve the general problem of collecting asynchronous, unordered, partial updates into an aggregated whole. Until you have all of the information that is necessary, you usually have to keep everything in a dynamic structure that is efficient to update (it has lots of maps, lists and other information), but which takes up memory and resources. Once you have everything necessary, you can generate the more "compact" structure that is more efficient in memory, but which cannot be updated efficiently. The dynamic structure is no longer used and is just taking up memory. You call "BlowFuse" once you've copied all of the information you needed out of the dynamic structure and generated the compact structure. The Fuse Decorator will release all references to the dynamic structure, and become very small in size.
[
object,
uuid(3FFC1593-26CA-11d4-9D22-009027133993),
oleautomation,
pointer_default(unique)
]
interface IRecorder : IUnknown
{
[helpstring("Record method calls - appends to any previous recording(s)")]
HRESULT Record([out, retval] IWriteOnlyDispatch **ppRecordSink);
[helpstring("Playback method calls made so far")]
HRESULT PlayBack([in] IWriteOnlyDispatch *pSink);
};
The Recorder is the most intuitively useful component. A Recorder "remembers" every method call made. When you call "PlayBack", it repeats the method calls, in order. That is, it "Plays Back" the method calls on the supplied interface.
This Recorder has only one Recording. All method calls are "appended" to whatever is in there already. If you wish to erase a recording, Release() it, and create a new one.
Implementation Notes It is interesting to ask how the Recorder was implemented, as the technique is useful for other components that haven't been written yet. It is a general mechanism for "serialising" and "deserialising" method calls. Each method call played into the recorder is written to an internal "list" as:
WhichInterface WhichMethod ArgList
WhichInterface is an integer numbered as "The interface returned by the Nth method call". Consider the following sequence of method calls, assuming that "AddChild" is method number 0:
pSon = pParent->AddChild("Bob"); // 1st Method Call
pDaughter = pParent->AddChild("Jennifer"); // 2nd Method call
pGrandChild = pSon->AddChild("Tom"); // 3rd Method call
pGrandChild2 = pDaughter->AddChild("Susan"); // 4th Method call
This is serialised internally as:
0 0 "Bob" 0 0 "Jennifer" 1 0 "Tom" 2 0 "Susan"
0 denotes the first interface, returned by "Record()", and supplied into "PlayBack()" later on. The above instructions translated into English read
Invoke method 0 with ArgList "Bob" on the original interface.
Invoke method 0 with ArgList "Jennifer" on the original interface
Invoke method 0 with ArgList "Tom" on the interface returned by the first method call
Invoke method 0 with ArgList "Susan" on the interface returned by the second method call
The playback implementation simply keeps an array of all interfaces returned so far, so that it can refer back to any interface returned by a previous method call. When an interface is "Released", this is also recorded as a special instruction Release the Interface returned by the Nth method call. From this implementation, it is clear that the playback of method calls requires at least an array that is the size of the number of method calls received so far, for the duration of the "PlayBack".
The technique used in the Recorder can be used to implement many other things.
Stream Proxy Stub Factory
Suggested interface:
interface IStreamProxyStubFactory : IUnknown
{
HRESULT CreateProxy([in] IStream *pStream, [out, retval] IWriteOnlyDispatch **ppVal);
HRESULT CreateStub([in] IWriteOnlyDispatch *pVal, [out, retval] IStream **ppVal);
};
The Proxy would implement IWriteOnlyDispatch on top of, say, an IStream by calling the Write method appropriately. The Stub would implement IStream on top of IWriteOnlyDispatch. Streams can be efficiently transported over the network, saved to a file (automatic logging), loaded up again later and replayed, etc.
Asynch DecoratorImplement IWriteOnlyDispatch on an IWriteOnlyDispatch interface. However, calls to the IWriteOnlyDispatch interface complete quickly, as they are simply logged to a First-In-First-Out queue. A second thread is removing the calls from the FIFO queue and "replaying" them on the underlying IWriteOnlyDispatch interface in a similar fashion to the Recorder "PlayBack" method described above.
MSMQ AdapterImplement IWriteOnlyDispatch on top of a Microsoft Message Queue to allow the ordering, routing etc. that are provided by that infrastructure.
The chief benefit of this .DLL is the implementation of IProxyStubFactory, which converts to and from a given OleAutomation interface and IWriteOnlyDispatch. The other components were farely trivial to write. I encourage you to find other useful "components" to write in terms of IWriteOnlyDispatch. Be sure to examine the test harness that is included - CoWriteOnlyDispatchTest. This component uses all of the functionality of the .DLL.