|
COM Pipes Author: Richard Grimes What are COM Pipes?
DCE RPC has a concept called pipes; the idea is that a client and server set up a data transfer conduit between them for efficient transfer of data. RPC pipes are far from straight forward to set up because they involve defining the pipe and the data that will be transferred, as well as memory allocation and deallocation routines and, of course, the code to do the actual transfer. However, it is important to point out that RPC pipes are defined as an RPC mechanism and they are not the same as the networking concept of named pipes (although since RPC can be layered over named pipes, they may use named pipes as a transport). COM pipes are available in Windows 2000 and are the COM equivalent of DCE pipes. They are used to transfer large buffers of BYTEs, longs or doubles, and to this end COM defines three interfaces: IPipeByte, IPipeLong and IPipeDouble. COM provides the marshalling code for these interfaces in OLE32.DLL. On the surface they look quite innocuous, here is IPipeByte:
[ object,
uuid(DB2F3ACA-2F86-11d1-8E04-00C04FB9989A),
async_uuid(DB2F3ACB-2F86-11d1-8E04-00C04FB9989A),
pointer_default(unique)]
interface IPipeByte : IUnknown
{
HRESULT Pull(
[out, size_is(cRequest),
length_is(*pcReturned)] BYTE* buf,
[in] ULONG cRequest, [out] ULONG* pcReturned);
HRESULT Push([in, size_is(cSent)] BYTE* buf,
[in] ULONG cSent);
}
The first thing to point out is that pipes can be called synchronously, or asynchronously. The next thing to notice is that pipes are bi-directional, so that once created they can be use to pass data from the pipe implementation to the data consumer, or from the data consumer to the implementer. However, there is more to pipes than just this. The pipe marshalling code provided by Windows 2000 implements read-ahead. What this means is that when a pipe user has received a buffer from the pipe implementer COM will call the implementer again for another buffer's worth of data before the caller makes this request. Since large amounts of data will be pulled it can be assumed that once the data consumer has got one buffer's worth of data it will have some finite processing time before it is ready to ask for more. However, the next data buffer will take time to be transmitted over the network so it means that there is a double performance hit for the application. The first hit occurs when the consumer is processing the data because the data source will effectively be idle, because no call is active. The second hit occurs when the consumer makes a subsequent call to get more data, at which point the consumer thread is blocked while the data is transmitted. Since COM pipe marshalling uses read-ahead, it means that while the consumer is processing data from a previous call, COM is already making a call to the data source, hopefully so that when the data consumer makes another call COM will already have received the data ready for the consumer to process. Of course, you could implement some mechanism like this yourself with asynchronous COM (also know as non-blocking calls), but it is not straightforward. Note that COM just provides the marshalling code, it does not provide any implementation for the actual pipes. The developer has to write this code, both for pulling and pushing data. There are two ways to use pipes, depending on where the pipe implementation resides: in the client or in the server. If the client implements the pipe it needs to have an object that implements one of the pipe interfaces and then it should pass this to the server component as an [in] parameter on an interface method call.
On the other hand the server can implement the pipe, in which case the pipe interface is returned to the client via an [out] pointer and so the client makes the calls to the pipe.
To a large extent whether the client or server implements the pipe is moot because as I have already pointed out, a pipe implementation can be used both to push and pull data. To aid read-ahead pipe implementations must indicate when there is no more data to transfer. To do this an implementation of Pull() must return the data requested in cRequest until there is no more to be returned, and return the actual number of data items transferred in the pcReturned parameter. COM will continue to call Pull() during read-ahead until it gets a value of 0 via pcReturned. This means that there will always be one extra call to the pipe implementation: COM does not assume that because *pcReturned != cRequest there is no more data. This is a good thing because a data source could be generating data and may not be able to do this as fast as COM is making the calls to Pull(). An implementation of Pull() could look like this:
HRESULT CMyObj::Pull(BYTE* pBuf, ULONG cRequest,
ULONG* pcReturned)
{
if (bNoMoreData)
{
*pcReturned = 0;
return S_OK;
}
*pcReturned = GetDataFromSomewhere(pBuf, cRequest);
return S_OK;
}
where GetDataFromSomewhere() is some method that generates the data. It could be called like this:
ULONG ret;
do
{
// ask for 1000 bytes to be sent at a time
pPipe->Pull(buf, 1000, &ret);
if (ret) ProcessData(buf, ret);
}
while (ret !=0)
where ProcessData() is some method to do something with the transferred data. The Push() method is used to send data from a user of a pipe to the pipe implementer. Again, COM will manage the network calls for you, buffering the data so that the pipe caller can continue to send data even if the pipe implementation is still processing a previous buffer. An implementation of Push() could look like this:
HRESULT CMyObj::Push(BYTE* pBuf, ULONG cSent)
{
ProcessData(pBuf, cSent);
return S_OK;
}
and it could be called like this:
// send first 1000 bytes
pPipe->Push(buf, 1000);
// send remaining 500 bytes
pPipe->Push(buf + 1000, 500);
// finish transfer
pPipe->Push(0, 0);
Here, the caller is passing 1500 bytes in batches no larger than 1000 bytes. The final call to Push() is required to inform COM that no more data will be transferred. Since pipes are bi-directional, it means that your code can both push and pull data along the same pipe. In one scenario a client could pass binary data like a graphic file to a component via one or more pushes and then pick up the processed data via one or more pulls. What about the asynchronous versions of the pipe interfaces? Well, you can use these to free the data consumer thread from blocking (which is a good thing if the client thread is a UI thread) and to make better use of the threads in the data source. When pulling data via the async versions of the pipe interfaces the data consumer thread initiates the transfer by calling the Begin_Pull() method indicating how many items are required. It can then perform some other processing and return later to obtain the data (and the number of items returned) by calling the Finish_Pull() method. During this time COM will have received the data and cached it ready for collection. The only oddity that I can see with this asynchronous method is that when you call Finish_Pull() you pass the buffer which you want filled, after the actual transfer has been performed. Presumably COM will have read the data into another buffer and when you call Finish_Pull() it will copy the data from its buffer into yours. The asynchronous version of Push() is useful to send data to another process without the current thread blocking, which is quite a useful thing to do especially if the amount of data is large. Since there are no [out] parameters on Push() it means that the only purpose of calling Push_Finish() is to allow COM to clean up any resources it may have used, and to determine if the push was successful (the return value from Push_Begin() only indicates that the method call was accepted by COM). Finally, one tip. To use pipes you must have the headers and libs from the most recent Platform SDK and also make sure that in your stdafx.h you define _WIN32_WINNT to have a value of 0x500. What do you think of this article? You can also write a review. We will publish the best ones here on this article. Send your review to [email protected]. Mail a question to the author!! As part of the IDevResource commitment to Open Publishing, all of our authors are available to answer all of your trickiest questions at Author Central. For information about the authors, or to mail a question, visit them at Author Central. Want to read more articles by this author? Try these: Byte size articles: ATL Server By Richard Grimes, 220200 COM and Apartments By Richard Grimes, January 7th 2000 An Introduction to WTL By Richard Grimes, January 7th 2000 An Introduction to Interface Programming By Richard Grimes, January 7th 2000 Full size articles: What is Async COM? By Richard Grimes. Microsoft Transaction Server By Richard Grimes, December 9th 1999 What COM is all about By Richard Grimes, December 6th 1999 More COM Library articles: CAtlBitmapButton - ATL/WTL Ownerdraw Superclassed Bitmap Button By Amit Dey. ATL COM and ADO By Paddy Srinivas. ATL COM and ADO By Amit Dey. COM Singletons: A Dangerous Animal By Richard Blewett. ASP COM Objects By Jan Verhoeven. SafeArrays - For the Beginner By A. Abdul Azeez. COM+ Basics - Creating your first COM+ Application By Martin Lapierre. COM+ - the backbone of Windows DNA By Mahesh Bhide. Exploring COM Threading and Apartments By Anthony Toivonen. COM on Linux By Frank Rem. ATL Server By Richard Grimes. ATL Internals - Part 2 By Shivesh Viswanathan. How to use DDX with WTL By Girish Bharadwaj. COM Patterns By Tony Toivonen. COM+ Object Pooling By Jeremiah Talkar. What are COM Pipes? By Richard Grimes. Java COM Integration - Use Visual J++ to implement COM Objects By Gopalan Suresh Raj. Developing an MSMQ Server Application using VJ++ By Gopalan Suresh Raj. Developing an MSMQ Client using VJ++ By Gopalan Suresh Raj. What is Async COM? By Richard Grimes. Coding a DCOM Server Component from IDL By Gopalan Suresh Raj. Coding a DCOM Client By Gopalan Suresh Raj. Threads and Apartments By Brad Wilson. WTL Architecture By Richard Grimes. The MTS Series by Gopalan Suresh Raj: Microsoft Transaction Server By Gopalan Suresh Raj. Developing a Simple MTS Server Component By Gopalan Suresh Raj. Developing a Simple MTS Client Application By Gopalan Suresh Raj. Developing The Bank Account IDL By Gopalan Suresh Raj. MTS Server Component By Gopalan Suresh Raj. MTS Client By Gopalan Suresh Raj. Other Articles Active Template Library: Architecture & Internals By Shivesh Viswanathan. Microsoft Transaction Server By Richard Grimes. What COM is all about By Richard Grimes. String Binding Moniker By Frank Rem. Author: Richard Grimes Richard Grimes started programming aeons ago on 8-bit computers and hasn't looked back since. He has spent an interesting time as a research scientist (the little known "Grimes Effect" is his creation), underpaid time as a computer trainer and done time as a distributed object developer. ATL took hold of Richard while he was part of a team developing a COM-based workflow system and its elegance and simplicity has had a lasting effect on him. Although his is not an obsessively pure COM existence, he finds that an ATL-assisted COM lifestyle gives him more time to enjoy his garden. Go to Richards pages in Author Central. Contribute to IDR: To contribute an article to IDR, a click here.
|
|