You can pass a COM component in a function call, as a pointer.
So suppose you implement an object in your EXE, and that loads another COM object from a DLL, you can pass the EXE-based object to the object from the DLL. The loaded object would need to support an interface that has a function which accepts a pointer, e.g.
interface ILoadedObject
{
HRESULT GiveObject(IUnknown *pObj);
};
If the DLL-based object implements that, you can call it from your EXE and pass it an object that is not registered anywhere, so there is no need to register objects in an EXE to achieve this.
The only requirements are on the correct implementation of IUnknown
: don't destroy the object until Release
has been called the right number of times, and ensure that QueryInterface
can be used to traverse between a fixed set of interfaces on the object and that querying for IUnknown
always returns the same address.
On the other hand, you can register an EXE as a server of objects, but that introduces a lot of complexity; COM has to start the EXE running and then send it messages via the Windows message queue. This is only widely used for OLE; it can be quite fragile.
Update
A more complete solution is to define a standard way to create an instance of an object type, but to allow the EXE to define how it works. The EXE would implement:
interface IComponent;
interface IEnvironment : IUnknown
{
HRESULT CreateInstance(REFCLSID clsid, IComponent **ppNew);
}
Every component must support this interface:
interface IComponent : IUnknown
{
HRESULT SetEnvironment(IEnvironment *pEnv);
}
Now, to get the standard behaviour where the EXE wants to use the registry to find components, it can implement the CreateInstance
method like this:
HRESULT Env::CreateInstance(REFCLSID clsid, IComponent **ppNew)
{
HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
__uuidof(IComponent), (void **)&ppNew);
if (FAILED(hr))
return hr;
(*ppNew)->SetEnvironment(this);
return S_OK;
}
But of course it can change this and "inject" some components. So instead of (or in addition to) the registry, a configuration file may be used. Or (as you've asked) the EXE could have built-in implementations of some components:
Because every component is notified of the environment when it is created, it can use the environment to create further components:
// inside some component:
HRESULT Comp::SetEnvironment(IEnvironment *e)
{
m_env = e; // using a smart pointer for ref-counting
return S_OK;
}
// in some method of the component
ComPtr<IComponent> button;
m_env->CreateInstance(CLSID_Button, &button);
// now query button for more useful interface...
So whenever a component is created, the environment (defined in the EXE) can control exactly how the implementation of the component is found. Every creation goes via the EXE.
This is sometimes called "dependency injection" or "inversion of control".