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
