|
|
"...pass in a URL, get a stream..."
Without a doubt, the easiest, most efficient, and most powerful way to download data from the Internet is to use the new URL Open Stream (UOS) functions. Before jumping into the specifications, let's cover a few of the high-level features and the philosophy behind the creation of these functions. The UOS functions are the newest addition to the ActiveX extensions to the Win32 API. They combine the familiarity of C-style programming with the power of COM. These functions work equally well inside an ActiveX framework (for example, a component, a document or frame window, a subcomponent, or a scriptable object) or standalone.
Unlike other more complex interface negotiations, using these functions requires knowledge of no more than two COM interfaces, IStream and IBindStatusCallback.
The UOS functions use services from URL monikers and WinInet; therefore, all the caching and thread synchronization features of those components are in use whenever you call these functions. In addition, these functions handle all the host binding operations if your code is in an ActiveX container: the UOS functions automatically do the right thing to manage the download in the ActiveX client framework.
Every UOS function works in the same basic way: the caller implements an IBindStatusCallback interface (optional in some cases), then calls the function. The URLOpenStream and URLOpenPullStream functions require the caller to be on a thread that has a message loop (GetMessage/DispatchMessage). For ActiveX components, a message loop is given when these functions are called from the main thread. For standalone applications without a user interface, a message loop is still necessary for these functions.
With the URL Open Stream functions, you can:
Callbacks are issued on an IBindStatusCallback interface that is implemented on the caller. This is a simple callback interface to implement because most of the methods on the interface are optional (for example, OnStartBinding, GetPriority, OnStopBinding) and can return S_OK or E_NOTIMPL. Clients may choose to implement many of the IBindStatusCallback methods; however, only OnDataAvailable is needed for the UOS functions URLOpenStream and URLOpenPullStream to work correctly. In fact, for some UOS functions (URLDownloadToFile, URLDownloadToCacheFile and URLOpenBlockingStream), OnDataAvailable is never called because it is unnecessary. Furthermore, GetBindInfo is never invoked for UOS clients because the the UOS function that is called determines the bind information. The UOS programming model is straightforward because there are no special flags to pass to the functions.
The URL Open Stream functions are described below:
HRESULT URLDownloadToCacheFile(
LPUNKNOWN lpUnkcaller,
LPCWSTR szURL,
LPWSTR szFileName,
DWORD dwBufLength,
DWORD dwReserved,
IBindStatusCallback *pBSC
);
URLDownloadToCacheFile downloads data into the Internet cache and returns the filename of the cache location for retrieving the bits. The client can choose to be notified of progress via a notification callback.
This function will always return a filename if the download operation succeeds. If the given URL is a "file:" URL, URLDownloadToCacheFile will directly return the filename for the "file:" URL rather than making a copy to the cache. If the given URL is an Internet URL ("http:", "ftp:"), URLDownloadToCacheFile will download this file and return the local filename of the cached copy. Using this function ensures that a filename is returned without unnecessary copying of data.
HRESULT URLDownloadToFile(
LPUNKNOWN pCaller,
LPCWSTR szURL,
LPCTSTR szFileName,
DWORD dwReserved,
LPBINDSTATUSCALLBACK lpfnCB
);
URLDownloadToFile downloads bits from the Internet and saves them to a file. The client can choose to be notified of progress via a notification callback.
HRESULT URLOpenStream(
LPUNKNOWN pCaller,
LPCWSTR szURL,
DWORD dwReserved,
LPBINDSTATUSCALLBACK lpfnCB
);
URLOpenStream creates a push-type stream object from a URL. The data is downloaded from the Internet as fast as possible. When data is available, it is "pushed" at the client through a notification callback.
The logic in the following code fragment is a typical implementation of OnDataAvailable as it is used by the URLOpenStream function:
HRESULT MyBindStatusCallback::OnDataAvailable (DWORD grfBSCF,
DWORD dwSize, ..., STGMEDIUM * pstgmed)
{
if( dwSize < sizeof(BITMAPINFOHEADER) )
return(NOERROR); // not enough has been read yet, just return
if( !g_bGotInfoHeader ) { // did we get info before?
// No, go ahead, read now...
DWORD dwRead;
HRESULT hr = pstgmed->pstm->Read( &bmih, sizeof(bmih), &dwRead);
if( SUCCEEDED(hr) ) {
// now we got it... we can return
g_bGotInfoHeader = TRUE;
return(hr);
}
}
}
HRESULT URLOpenBlockingStream(
LPUNKNOWN pCaller,
LPCWSTR szURL,
LPSTREAM *ppStream,
DWORD dwReserved,
LPBINDSTATUSCALLBACK lpfnCB
);
URLOpenBlockingStream creates a blocking-type stream object from a URL. The data is downloaded from the Internet on demand by a call to IStream::Read. The Read call will block until enough data has arrived.
IStream * pStream;
URLOpenStream( 0, L"http://www.msn.com/", &pStream, 0, 0);
char buffer[0x100];
DWORD dwGot;
HRESULT hr = NOERROR;
do {
hr = pStream->Read( buffer, sizeof(buffer), &dwGot );
// do something with contents of buffer
} while( SUCCEEDED(hr) );
HRESULT URLOpenPullStream(
LPUNKNOWN pCaller,
LPCWSTR szURL,
DWORD dwReserved,
LPBINDSTATUSCALLBACK lpfnCB
);
URLOpenPullStream creates a pull-type stream object from a URL. The data is downloaded from the Internet on demand. If not enough data is available locally to satisfy the requests, the IStream::Read call will not block until enough data has arrived. Instead, Read will immediately return E_PENDING, and URLOpenPullStream will request the next packet of data from the Internet server.
The pull model is slightly more cumbersome than the push model, but it allows the client to control the amount of Internet access for the download.
The logic in the following code fragment is a typical implementation of OnDataAvailable as it is used by the URLOpenStream function:
HRESULT CMyBindStatusCallback::OnDataAvailable( ...)
{
HRESULT hr = NOERROR;
DWORD dwAmountToRead = dwSize - g_readSoFar;
BYTE * buffer = new BYTE[ dwAmountToRead ];
while( TRUE ) {
DWORD dwRead;
hr = pstgmed->pstrm->Read( buffer, dwAmountToRead, &dwRead );
if( hr == E_PENDING ) {
// we'll get notified again when more data comes
return(NOERROR);
}
if( SUCCEEDED(hr) ) {
.
. // ok, process bits and keep looping
.
}
else {
// we have an error...
return(hr);
}
}
}
|
|