Reg-Free COM with Out-of-Process Servers

Generally speaking, a COM object must be registered in the Windows registry to be used by applications. In the past, I have not had an issue with this, as it was just another step in the installation package. In recent years, we have been offering cloud services to our clients. On a single server, we now have multiple clients and dozens of users all relying on the same COM objects. This makes it more difficult to deploy updates to a COM object. It also prohibits having multiple versions of a COM object, because only one version can be registered at a time (AKA “DLL Hell”). These days, registration-free COM is looking a lot more attractive, as it enables multiple “side-by-side” versions of a COM object that can be managed independently. It also allows deployment without the admin elevation that is normally required to register a component.

For a while now, we have had the ability to use custom manifests that allow applications to make use of COM objects without registering them. Some Visual FoxPro devs might make use of tools by Craig Boyd to assist with this. Visual Studio provides tools for automating manifests as well. There’s one catch… this technique only works with in-process DLL COM servers. I often use out-of-process EXE COM servers for Visual FoxPro because they have fewer limitations than DLLs. Is there a way to use an EXE COM server without registering it first?

A cursory Google search might lead you to answer “No”, but Larry Osterman of Microsoft fame tells us otherwise. In his blog post from 2005, he points out a “clever trick” to accomplish this: “When COM tries to activate a COM object, before it looks in the registry, it looks to see if there’s a server already registered for that class.” In more detail… if the COM server EXE has already been launched and CoRegisterClassObject() was called BEFORE instantiating the COM object in a client application, the COM object can be used even though it is not in the Windows registry. Clear as mud?

First of all, we are talking about two different types of registration here. There’s the standard registration of a COM object into the Windows registry that we are all familiar with. Then there’s CoRegisterClassObject(), which registers out-of-process COM objects in memory with the Windows COM/OLE system (not the Windows registry). This is done from inside the COM server, and it is what allows the COM objects to be found by client applications. Okay… so how do we call CoRegisterClassObject()?

Good news! No Win32 API calls are necessary. Your out-of-process COM server already knows how to do this, you just have to tell it to do so. There is not a mandatory standard for all COM servers, but Microsoft convention is to include the following parameters when launching the EXE: “/automation -Embedding”. This convention is used for VFP COM EXEs, MS Office, and probably many others. Here’s some VFP code for launching the COM EXE:

Local lcRun
lcRun = [Run /n "] + FullPath("MyCOMServer.exe") + [" /automation -Embedding]
&lcRun

I have used FoxPro’s RUN command to launch the executable. Win32 API ShellExecute() could be used as well, although I should mention I did run into some timing issues that I didn’t bother to troubleshoot. CreateProcess() is another option.

If your COM server doesn’t use this convention, register and instantiate it normally on your dev machine. Then open Windows Task Manager, go to the Details tab, and enable the Command Line column. That will show you what parameters were included when the COM EXE was launched.

At this point, the COM EXE server is running and ready to be used by a client application. You should be able to use VFP’s CreateObject() to instantiate the object, right? Unfortunately, no. CoRegisterClassObject() has registered the object in memory using a “Class ID” (CLSID), which is a GUID. CreateObject() expects the more friendly “Program ID” (ProgID) that we are familiar with. Behind the scenes, CreateObject() calls CLSIDFromProgID() to lookup the ProgID in the Windows registry and return the corresponding CLSID, then it calls CoCreateInstance() using that CLSID. We want to avoid using the Windows registry for deployment, so job one is to find the CLSID for our COM object. To do that, you can look in the Windows registry on your development machine and find your ProgID under HKEY_CLASSES_ROOT. For VFP, there is also a VBR file in the project folder that you can open in a text editor and find an entry like this:

HKEY_CLASSES_ROOT\MyCOMServer.MyObject\CLSID = {12345678-90AB-CDEF-1234-567890ABCDEF}

Now that we have the CLSID, we are ready to instantiate the object. Again, VFP’s CreateObject() will not accept a CLSID, but CreateObjectEx() will!

#DEFINE MYCOMOBJECT_CLSID = "{12345678-90AB-CDEF-1234-567890ABCDEF}"
loMyComObject = CreateObjectEx(MYCOMOBJECT_CLSID, GetEnv("COMPUTERNAME"))

I should point out that this calls CoCreateInstanceEx() rather than CoCreateInstance() behind the scenes. This is typically used to instantiate a remote DCOM object on a different machine, but I have designated that it is on the current computer. I have not encountered any repercussions from doing this (performance or otherwise), but your mileage may vary. You could call CoCreateInstance() directly to instantiate the object as a “Local” out-of-process server instead.

We now have everything we need to instantiate an out-of-process COM object without registering it, but one caveat remains. FoxPro’s RUN command and ShellExecute() cause the client application to lose focus when the COM server EXE is launched. This doesn’t happen when instantiating a COM object that has been registered the normal way. You could probably prevent this by using CreateProcess() to launch the server, but it has a complex API. Using LockSetForegroundWindow() seems to avoid the unwanted behavior. Here’s the complete code:

#DEFINE MYCOMOBJECT_CLSID = "{12345678-90AB-CDEF-1234-567890ABCDEF}"
Local lcRun, loMyComObject 
DECLARE INTEGER LockSetForegroundWindow IN user32 INTEGER uLockCode
LockSetForegroundWindow(1) && lock	
lcRun = [Run /n "] + FullPath("MyCOMServer.exe") + [" /automation -Embedding]
&lcRun
loMyComObject = CreateObjectEx(MYCOMOBJECT_CLSID, GetEnv("COMPUTERNAME"))
LockSetForegroundWindow(2) && unlock