More than half the member functions of IMoniker take an argument of type IBindCtx *, which points to the bind context object for the current binding operation. The bind context carries information that applies to the entire binding underway, as opposed to the binding action of a single moniker. In other words, the bind context carries information global to the entire outer composite being bound and thus all other monikers within it, regardless of the complexity of the composite.
OLE provides bind context objects as a standard service. To create a bind context, call the API function CreateBindCtx as follows (the first argument is reserved and must be 0):
IBindCtx *pbc;
HRESULT hr;
hr=CreateBindCtx(0, &pbc);
§
pbc->Release();
This creates a default initialized bind context that can be passed to various IMoniker functions. A bind context is, in fact, the first argument to IMoniker::BindToObject; creation of the context is one of the steps that the API function BindMoniker performs:
HRESULT BindMoniker(IMoniker *pmk, DWORD grfOpt, REFIID riid, void **ppv)
{
IBindCtx *pbc;
HRESULT hr;
hr=CreateBindCtx(0, &pbc);
if (SUCCEEDED(hr))
{
hr=pmk->BindToObject(pbc, NULL, riid, ppv)
pbc->Release();
}
return hr;
}
So one bind context travels through a binding procedure regardless of the monikers involved, as shown in Figure 9-5.
Figure 9-5.
The role of the bind context when binding a composite moniker. It acts as a centralized repository for information available to all monikers used in the process.
CreateBindCtx is the extent to which clients usually have to deal with these objects. Monikers, on the other hand, deal with them frequently during binding, always through the IBindCtx interface:
interface IBindCtx : IUnknown
{
HRESULT RegisterObjectBound IUnknown *pUnk);
HRESULT RevokeObjectBound(IUnknown *pUnk);
HRESULT ReleaseBoundObjects(void);
HRESULT SetBindOptions(BIND_OPTS *pbindopts);
HRESULT GetBindOptions(BIND_OPTS *pbindopts);
HRESULT GetRunningObjectTable(IRunningObjectTable **ppROT);
HRESULT RegisterObjectParam(LPOLESTR pszKey, IUnknown *pUnk);
HRESULT GetObjectParam(LPOLESTR pszKey, IUnknown **ppUnk);
HRESULT EnumObjectParam(IEnumString **ppEnum);
HRESULT RevokeObjectParam(LPOLESTR pszKey);
};
As you can see, four groupings of functions represent the basic capabilities of a bind context:
These last four *ObjectParam functions enable the monikers involved in the binding to store custom information that remains valid throughout the operation. A moniker can place a pointer to any object whatsoever in this table as long as it supports IUnknown. Only the moniker that stores the object knows what the object means, and these objects are always in the same process as the moniker. This means you can use as many custom interfaces as you want to store whatever information you want with the object. You can even get away with storing a C++ object pointer here as long as that object's vtable starts with IUnknown. In short, this is a very nice little object repository.6
The bind options described in a BIND_OPTS structure remain fixed throughout binding. This structure contains a set of flags, sharing flags, and a timer count that establishes the client's deadline for binding to complete:
typedef struct tagBIND_OPTS
{
DWORD cbStruct;
DWORD grfFlags;
DWORD grfMode;
DWORD dwTickCountDeadline;
} BIND_OPTS;
The cbStruct field describes the size of this structure. The default bind context sets grfFlags to 0, but the client can store other options from the enumeration BINDFLAGS, as you see in the code at the top of the following page.
typedef enum tagBINDFLAGS
{
BIND_MAYBOTHERUSER = 1,
BIND_JUSTTESTEXISTENCE = 2
} BINDFLAGS;
The BIND_MAYBOTHERUSER option tells monikers that they can incorporate user interface in their binding process; for example, they can display a dialog box that a user must type a password in, or they can ask the user to make a network connection manually. If this option is not present, monikers must either use a binding algorithm that requires no user interaction or return MK_E_MUSTBOTHERUSER to fail the binding.
The BIND_JUSTTESTEXISTENCE flag allows the client requesting the binding to indicate that it wants to know only whether binding is possible but not to actually carry out the operation. Usually this ends up being as expensive as performing the actual binding, especially because monikers themselves may completely ignore this flag if they choose to. This flag enables a sophisticated client to create a populated bind context ahead of time and hold that bind context until it really does want to bind at some time shortly thereafter. Because the bind context would still have all the registered bound objects inside it, the actual binding operation would be much faster than the test.
The flags allowed for grfMode are taken from the STGM enumeration that we saw in Chapter 7—that is, storage-mode flags. These flags communicate to the object being bound, as well as to any intermediate objects, the types of access mode to use for any storages that need to be opened in the process. This is especially useful for IMoniker::BindToStorage and is critical if there is an issue with concurrent access. By default, a new bind context will set this option to STGM_READWRITE ¦ STGM_SHARE_EXCLUSIVE.
Finally, the dwTickCountDeadline field gives the client a way to say how long it is willing to wait for binding to be complete—clients who need binding to occur rapidly can ensure that they won't get locked up inside a moniker for too long. The value 0 in dwTickCountDeadline, which is the default value, means "no deadline." A client that wants to set a deadline must call the Windows API GetTickCount and add to it the number of milliseconds it is willing to wait, taking wraparound of the DWORD tick count into consideration. That value then goes into this field.
Monikers that might perform a potentially time-consuming operation should check the deadline frequently; monikers such as a pointer moniker—which just call QueryInterface—need not pay attention to this. Those that do pay attention do not have to be completely accurate in the timing (it being difficult to predict how long some operation might take) as long as they stay in the ballpark of the deadline. It is allowable to exceed it a little, say by a few hundred milliseconds at most. When the deadline is passed, the moniker should fail with MK_E_EXCEEDEDDEADLINE.
If a moniker exceeds the deadline because one or more intermediate objects that it would like to use in binding are not running, it can save the monikers of these objects with IBindCtx::RegisterObjectParam. This tells the client that if these objects had been running, the binding might have happened faster. The client can then force the objects into the running state with the OleRun API function and try again. The monikers themselves are stored with the key names ExceededDeadline, ExceededDeadline1, ExceededDeadline2, and so on.
6 It is worthwhile to point out that if you have a need for some generic list management service, you could use the *ObjectParam members in a bind context, just as you might use a Windows list box control for the same purpose. |