Last time, we learned about the wonderful world of shell bind context strings, and I promised some helper functions to make this slightly more manageable.
Here are some helper functions which supplement the CreateBindCtxWithOpts
function we created some time ago.
#include <propsys.h> HRESULT EnsureBindCtxPropertyBag( IBindCtx *pbc, REFIID riid, void **ppv) { *ppv = nullptr; CComPtr<IUnknown> spunk; HRESULT hr = pbc->GetObjectParam(STR_PROPERTYBAG_PARAM, &spunk); if (FAILED(hr)) { hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&spunk)); if (SUCCEEDED(hr)) { hr = pbc->RegisterObjectParam(STR_PROPERTYBAG_PARAM, spunk); } } if (SUCCEEDED(hr)) { hr = spunk->QueryInterface(riid, ppv); } return hr; } HRESULT AddBindCtxDWORD( IBindCtx *pbc, LPCWSTR pszName, DWORD dwValue) { CComPtr<IPropertyBag> sppb; HRESULT hr = EnsureBindCtxPropertyBag(pbc, IID_PPV_ARGS(&sppb)); if (SUCCEEDED(hr)) { hr = PSPropertyBag_WriteDWORD(sppb, pszName, dwValue); } return hr; } HRESULT AddBindCtxString( IBindCtx *pbc, LPCWSTR pszName, LPCWSTR pszValue) { CComPtr<IPropertyBag> sppb; HRESULT hr = EnsureBindCtxPropertyBag(pbc, IID_PPV_ARGS(&sppb)); if (SUCCEEDED(hr)) { hr = PSPropertyBag_WriteStr(sppb, pszName, pszValue); } return hr; } HRESULT CreateDwordBindCtx( LPCWSTR pszName, DWORD dwValue, IBindCtx **ppbc) { CComPtr<IBindCtx> spbc; HRESULT hr = CreateBindCtx(0, &spbc); if (SUCCEEDED(hr)) { hr = AddBindCtxDWORD(spbc, pszName, dwValue); } *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr; return hr; } HRESULT CreateStringBindCtx( LPCWSTR pszName, LPCWSTR pszValue, IBindCtx **ppbc) { CComPtr<IBindCtx> spbc; HRESULT hr = CreateBindCtx(0, &spbc); if (SUCCEEDED(hr)) { hr = AddBindCtxString(spbc, pszName, pszValue); } *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr; return hr; }
The EnsureBindCtxPropertyBag
function puts a property bag in the bind context if there isn’t one already.
The AddBindCtxDWORD
function adds a DWORD
to that associated property bag. If you wanted to add multiple DWORD
s to a bind context, you would call this function multiple times. You can also use the AddBindCtxString
if the thing you want to add is a string.
The CreateDwordBindCtx
function handles the simple case where you want to create a bind context that contains a single DWORD
. Similarly, CreateStringBindCtx
.
But now things are starting to get kind of unwieldy. What if you want a bind context with a string and a DWORD
? Let’s go for something a bit more fluent.
But first, some scaffolding.
class CStaticUnknown : public IUnknown { public: // *** IUnknown *** IFACEMETHODIMP QueryInterface( _In_ REFIID riid, _Outptr_ void **ppv) { *ppv = nullptr; HRESULT hr = E_NOINTERFACE; if (riid == IID_IUnknown) { *ppv = static_cast<IUnknown *>(this); AddRef(); hr = S_OK; } return hr; } IFACEMETHODIMP_(ULONG) AddRef() { return 2; } IFACEMETHODIMP_(ULONG) Release() { return 1; } }; CStaticUnknown s_unkStatic;
This static implementation of IUnknown
is one we’ll use for the bind context strings whose mere presence indicates that a flag is set.
class CBindCtxBuilder { public: CBindCtxBuilder() { m_hrCumulative = CreateBindCtx(0, &m_spbc); } CBindCtxBuilder& SetMode(DWORD grfMode); CBindCtxBuilder& SetFindData(const WIN32_FIND_DATA *pfd); CBindCtxBuilder& SetFlag(PCWSTR pszName); CBindCtxBuilder& SetVariantDword(PCWSTR pszName, DWORD dwValue); CBindCtxBuilder& SetVariantString(PCWSTR pszName, PCWSTR pszValue); HRESULT Result() const { return m_hrCumulative; } IBindCtx *GetBindCtx() const { return SUCCEEDED(m_hrCumulative) ? m_spbc : nullptr; } private: HRESULT EnsurePropertyBag(); private: CComPtr<IBindCtx> m_spbc; CComPtr<IPropertyBag> m_sppb; HRESULT m_hrCumulative; };
The bind context builder class is a helper class that creates a bind context, and then fills it with stuff. For now, we let you set the following:
- The mode to use for opening the target of the bind. The default is
STGM_READWRITE
. - The find data to use, if creating a simple pidl.
- An arbitrary flag, associated with a dummy
IUnknown
. - A
DWORD
in the property bag. - A string in the property bag.
After you build up the bind context, you can check the Result()
to see if it was built successfully, and use GetBindCtx
to extract the result.
Here’s the implementation. It’s really not that exciting. We accumulate any error in m_hrCumulative
, and once an error occurs, all future methods do nothing aside from preserving the error. To make the object fluent, the methods return a reference to themselves.
There is a special bind context method for setting the mode:
CBindCtxBuilder& CBindCtxBuilder::SetMode(DWORD grfMode) { if (SUCCEEDED(m_hrCumulative)) { BIND_OPTS bo = { sizeof(bo), 0, grfMode, 0 }; m_hrCumulative = m_spbc->SetBindOptions(&bo); } return *this; }
Find data is set as a direct object on the bind context, as we saw some time ago:
CBindCtxBuilder& CBindCtxBuilder::SetFindData(const WIN32_FIND_DATA *pfd) { if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = AddFileSysBindCtx(m_spbc, pfd); } return *this; }
Flags are set by there mere presence, so we associate them with a dummy IUnknown
that does nothing:
CBindCtxBuilder& CBindCtxBuilder::SetFlag(PCWSTR pszName) { if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = m_spbc->RegisterObjectParam( const_cast<PWSTR>(pszName), &s_unkStatic); } return *this; }
If a property is set in the property bag, we need to proceed in two steps. First, we create the property bag if we don’t have one already. Second, we put the value into the property bag:
CBindCtxBuilder& CBindCtxBuilder::SetVariantDword( PCWSTR pszName, DWORD dwValue) { if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = EnsurePropertyBag(); } if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = PSPropertyBag_WriteDWORD( m_sppb, pszName, dwValue); } return *this; } CBindCtxBuilder& CBindCtxBuilder::SetVariantString( PCWSTR pszName, PCWSTR pszValue) { if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = EnsurePropertyBag(); } if (SUCCEEDED(m_hrCumulative)) { m_hrCumulative = PSPropertyBag_WriteStr( m_sppb, pszName, pszValue); } return *this; }
And finally, the helper function that creates a property bag if we don’t have one already.
HRESULT CBindCtxBuilder::EnsurePropertyBag() { HRESULT hr = S_OK; if (!m_sppb) { hr = PSCreateMemoryPropertyStore( IID_PPV_ARGS(&m_sppb)); if (SUCCEEDED(hr)) { hr = m_spbc->RegisterObjectParam( STR_PROPERTYBAG_PARAM, m_sppb); } } return hr; }
The idea here is that the class is used like this:
CBindCtxBuilder builder; builder.SetMode(STGM_CREATE) .SetFindData(&wfd) .SetFlag(STR_FILE_SYS_BIND_DATA_WIN7_FORMAT) .SetFlag(STR_BIND_FOLDERS_READ_ONLY); hr = builder.Result(); if (SUCCEEDED(hr)) { hr = psf->ParseDisplayName(hwnd, builder.GetBindCtx(), pszName, &cchEaten, &pidl, &dwAttributes); }
You create the bind context builder, then use the various SetXxx
methods to fill the bind context with goodies, and then you check if it all worked okay. If so, then you use GetBindCtx
to get the resulting bind context and proceed on your way.
0 comments