7
votes

I know that we can use CoLoadLibrary and DllGetClassObject to get the IClassFactory interface and get the COM component interface without registering the DLL.

But what about a COM component in an EXE? Is there a way that I can get a COM component interface from an EXE-type COM server by just providing a different file path?

3

3 Answers

3
votes

If you use the real registration free COM, you should be able to get this working for both in-proc and out-of-proc COM objects.

As Sharptooth pointed out, you're really not using registration free COM. Instead you're really rolling your own by faking the calls that COM uses during activation. Your solution CAN work if you control both your application and the COM server you're activating but it's likely to fail otherwise.

2
votes

No, you cannot. You need COM setup marshalling between your program and out-proc COM server. To achieve this you have to call CoInitialize() and then either CoCreateInstance() or CoGetClassObject().

The path you describe with an in-proc server - calling CoLoadLibrary() and then DllGetClassObject() - is in fact a dirty hack - it bypasses normal COM mechanisms and so for example no marshalling will kick in even if it is needed to satisfy threading model requirements (STA/MTA stuff). Such dirty hack is possible because an in-proc server is a regular DLL with several well-known functions exposed. The same is impossible for an out-proc COM server - you need to rely on COM in that case.

1
votes

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".