217 lines
8.6 KiB
Plaintext
217 lines
8.6 KiB
Plaintext
|
COM 'Instance's (and explorer band categories)
|
||
|
980305
|
||
|
andyp
|
||
|
|
||
|
WARNING WARNING WARNING -- v. early draft of doc in progress, likely to
|
||
|
have many errors/omissions.
|
||
|
|
||
|
- abstract
|
||
|
...
|
||
|
|
||
|
- contents
|
||
|
...
|
||
|
|
||
|
- overview
|
||
|
COM has a (core!) notion of a CLSID for the code that implements an object
|
||
|
but no similar notion for a particular instance of that object. thus every
|
||
|
client must write custom code for each instance.
|
||
|
|
||
|
we provide a generalization which makes creation and initialization of such
|
||
|
instances easy and consistent.
|
||
|
|
||
|
- motivation
|
||
|
an e.g. of usage might be a browser band implementation. the code for
|
||
|
such a band is identical no matter what URL one happens to initialize
|
||
|
it to. however it is quite useful to be able to install many different
|
||
|
browser bands, each pointing to a different URL, by writing only
|
||
|
registry 'goo' (vs. writing custom code, however simple it may be,
|
||
|
to do the same thing).
|
||
|
|
||
|
- instance = code + data
|
||
|
an 'INSTID' is a GUID that identifies two things:
|
||
|
- the CLSID for the code for an object
|
||
|
- data for an IPersistXxx iface to initialize an instance of the object
|
||
|
|
||
|
- creation
|
||
|
INSTID's are fully hidden from the client. that is, there is no special
|
||
|
API to create an instance. one simply does a CoCreateInstance, and if the
|
||
|
CLSID happens to actually be an INSTID, we quietly do everything that is
|
||
|
necessary to create and initialize the instance.
|
||
|
|
||
|
- code
|
||
|
the code for an 'instance' is exactly a CLSID.
|
||
|
|
||
|
- initialization
|
||
|
initialization is done from an IPersistXxx iface. we try to load 1st from
|
||
|
an IPropertyBag, next from an IPersistStream, and finally from ???
|
||
|
|
||
|
all of these ifaces are created using our various registry-based
|
||
|
implementations. e.g. CRegStrPropertyBag (IPropertyBag on top of
|
||
|
a registry key), OpenRegStream (IPersistStream on top of a registry
|
||
|
key), etc.
|
||
|
|
||
|
NYI: only IPersistPropertyBag is implemented. this makes (some) sense,
|
||
|
since we're trying to provide a registry-goo-only method for 'coding',
|
||
|
and a property bag is the main string-based COM IPersistXxx mechanism.
|
||
|
|
||
|
- installation
|
||
|
for convenience, we also provide code to create the registry goo (why?).
|
||
|
|
||
|
CRegStrPropBag *InstallInstAndBag(LPTSTR pszInst, LPTSTR pszName, LPTSTR pszClass)
|
||
|
|
||
|
...
|
||
|
|
||
|
- registry goo
|
||
|
the key looks as follows:
|
||
|
subkey value(s)
|
||
|
------ --------
|
||
|
HKCR/CLSID/
|
||
|
{instid}/ @=...description...
|
||
|
InProcServer
|
||
|
@=...path...
|
||
|
ThreadingModel=...etc....
|
||
|
Instance
|
||
|
CLSID={clsid}
|
||
|
InitPropertyBag
|
||
|
name1=value1
|
||
|
...
|
||
|
|
||
|
the InProcServer should point to browseui.dll, which is where we've
|
||
|
implemented the generic support code for InstIDs. (if the idea proves
|
||
|
useful enough, perhaps COM will pick it up as a standard part of the
|
||
|
CoCreateInstance API).
|
||
|
|
||
|
- implementation
|
||
|
here's how it works...
|
||
|
|
||
|
the INSTID points to our DLL which implements inst.cpp. DllGetClassObject
|
||
|
goes thru its usual loop. if it fails, it tries to create a
|
||
|
CInstClassFactory for the given CLSID (INSTID). creation looks for and
|
||
|
caches the magic 'Instance' subkey (and fails if it's not found). then
|
||
|
when the ::CI method is called it gets the appropriate keys/values, does
|
||
|
the CCI, creates the IPropertyBag (etc.), and does the ::Load.
|
||
|
|
||
|
- perf
|
||
|
from the implementation details, it should be clear that a 'normal'
|
||
|
CCI goes thru exactly the same code path as before. we intentionally
|
||
|
keep this code path exactly the same cost.
|
||
|
|
||
|
the only time our INSTID code is hit at all is if the vanilla CCI
|
||
|
fails (due to it not being in our sccls.c table). when that happens,
|
||
|
we look for the 'Instance' subkey. if that fails, we fail the entire
|
||
|
CCI (w/ the only added cost for that failure case being the 1 extra
|
||
|
RegOpenKey call). we intentionally keep this code path as close to
|
||
|
0 extra cost as possible.
|
||
|
|
||
|
if that succeeds, we open/read several other keys, do the 'real' CCI,
|
||
|
and do the initialization. again, the only extra cost (vs. the equivalent
|
||
|
custom code) is the extra registry operations, which we try to keep
|
||
|
cheap.
|
||
|
|
||
|
to keep the extra registry operations cheap we do 'relative' opens
|
||
|
as we work our way down the registry.
|
||
|
|
||
|
BUGBUG what about cost of CInstClassFactory?
|
||
|
|
||
|
|
||
|
- example
|
||
|
to continue w/ our browser band e.g., here's the registry 'goo' for
|
||
|
such a band:
|
||
|
HKCR/CLSID/
|
||
|
{77777777-7777-7777-7777-777777777777}
|
||
|
InProcServer
|
||
|
@="%systemdir%/browseui.dll"
|
||
|
ThreadingModel="Apartment"
|
||
|
Instance
|
||
|
CLSID=Clsid_BrowserBand
|
||
|
InitPropertyBag
|
||
|
Url="http://www.nytimes.com"
|
||
|
... other properties ...
|
||
|
|
||
|
a CCI(7777, ...) will do:
|
||
|
- create an uninitialized instance of the object by doing
|
||
|
CCI(Clsid_BrowserBand, ...);
|
||
|
- create an IPropertyBag for the 'InitPropertyBag' registry data (using
|
||
|
our CRegStrPropertyBag implementation)
|
||
|
- call punk->IPB::Load to load the IPropertyBag into the punk
|
||
|
- we now have an initialized instance of the object (we're done!)
|
||
|
|
||
|
- gotcha's and subtleties
|
||
|
|
||
|
- gotcha: GetCLSID et. al.
|
||
|
while each INSTID is unique and will create a separate instance initialized
|
||
|
w/ the appropriate data, once the object has been created there is no
|
||
|
(standard) way to distinguish it from any other instance of the same class
|
||
|
(code).
|
||
|
|
||
|
e.g. two browsers, one pointing at www.nytimes.com and the other at
|
||
|
www.wsj.com, actually just look like two generic browsers.
|
||
|
|
||
|
thus (e.g.)
|
||
|
- IPS::GetCLSID gives the same CLSID (not different INSTID!) for both
|
||
|
of them,
|
||
|
- OleSaveToStream saves out the same CLSID and the (different) URL's
|
||
|
- a subsequent OleLoadFromStream will use the same CLSID and the
|
||
|
(different URL's)
|
||
|
|
||
|
while at 1st this might seem odd, it actually makes a lot of sense. INSTID's
|
||
|
are just a convenient standard 'packaging' of existing COM mechanisms.
|
||
|
if one does a 'classic' CCI of two CLSID_BrowserBand's, initializes them
|
||
|
to point to two different URLs, saves each w/ OleSaveToStream (presumably
|
||
|
to two different streams), and later reloads each w/ OleLoadFromStream
|
||
|
(again from the different streams), one will get *exactly* the same
|
||
|
behavior as w/ the above INSTID e.g.
|
||
|
|
||
|
in fact doing anything else (e.g. saving the object out w/ its INSTID)
|
||
|
would be wrong (or at least inefficient). consider: the user may have
|
||
|
changed various properties (e.g. navigated to a new URL). when we save
|
||
|
it, the object saves the properties that exactly represent its current
|
||
|
state. but if we create it by INSTID, we'd init it to a *different* set
|
||
|
of properties (which we'd then blast w/ the IPB::Load call).
|
||
|
|
||
|
moreover doing so would be impossible, since neither the object nor the
|
||
|
system even know what the INSTID is once the CCI is complete.
|
||
|
|
||
|
that said, one does need to be a bit careful.
|
||
|
|
||
|
- gotcha: find
|
||
|
the fact that IPS::GetCLSID returns the code CLSID rather than the
|
||
|
object INSTID also complicates uniquely identifying the object in
|
||
|
one's code. e.g. in our explorer bar implementation, each menu
|
||
|
item has a unique CLSID or INSTID. the 1st time one clicks on a menu
|
||
|
item, we just call CCI, add the band to the bar, show the band, and
|
||
|
hide all other bands. so far so good.
|
||
|
|
||
|
but now consider what happens steady-state when one re-clicks on a
|
||
|
previously created menu item. while the menu item still knows they
|
||
|
have different INSTID's, we have no way to ask the each object (when
|
||
|
enumerating our list of bands) if it came from the INSTID.
|
||
|
|
||
|
to solve this we use IE's Get/PutProperty mechanism to store a
|
||
|
<name,value> pair, in this case <instIdBand,punkBand>.
|
||
|
|
||
|
- gotcha: client dependencies
|
||
|
since we're providing inst.cpp (vs. ole32.dll), the InProcServer for each
|
||
|
INSTID must point to our implementation (ie4:shdocvw, ie5:browseui).
|
||
|
|
||
|
however while the code 'belongs' to us, the INSTIDs in question 'belong'
|
||
|
to the client app. that is, 3rd-party apps will point *their* INSTIDs at
|
||
|
*our* DLL. this means that their selfreg.inx will contain knowledge of
|
||
|
where we implement our support routines.
|
||
|
|
||
|
this in turn means that we can't move our implementation (or rather when
|
||
|
we do, we need to somehow forward it so that old code continues to work).
|
||
|
|
||
|
use a treat-as or somesuch so that old code continues to work).
|
||
|
|
||
|
BUGBUG we need to do this for ie5 since we've moved stuff!
|
||
|
|
||
|
- links
|
||
|
docs/inst.txt this document
|
||
|
browseui/inst.cpp CCI support code
|
||
|
browseui/stream.cpp CRegStrPropertyBag
|
||
|
|
||
|
- appendix: 777a.reg
|
||
|
here's the exact registry 'goo'
|
||
|
#include 777a.reg
|