Wednesday, December 28, 2005

“Class Does Not Support Automation” Error (Problems creating C++ COM component from VBScript)

They say you don't appreciate what you have until it's gone, but with COM, I did not realize how crappy it was until I moved to .Net. Unfortunately COM is still in some of our lives. I have not had to deal with COM implementation details in a while, but here is a problem I ran into last night:

One of our clients provides Web Casting of sporting events (games), via a legacy ASP Web Site. The current implementation uses a both threaded COM component, developed in VC++. This COM component is dual interface (implements IDispatch), so it can be called from ASP/VBScript (a.k.a. Automation).

During an incremental upgrade of this site to ASP.Net 2005, I found that I was not able to create an instance of the exposed COM class in the VC++ component from Asp.Net (using .Net COM Interop), in my test environment. I then tested using a simple VBScript CreateObject() to create an instance of this class, but this returned the error: ‘Class does not support automation’. I had recompiled the VC++ component using VS.Net 2005, so I reverted back to the previous version compiled with VS.Net 2003, then even the old VS 6 version, and still the same problem existed!

This was surprising, since this same source code was used to compile the component currently running in production, being called from ASP/VBScript! So what was the difference?

I finally debugged the COM component during the attempted instantiation, (using VS.Net 2005) to see if I could determine where the root problem was. I noticed the error got raised after a call to the class factories QueryInterface method. I also noticed that the implementation of QueryInterface (in our VC++ source code) was ALWAYS returning E_NOINTERFACE, even if the IID being queried for (QI’d) was implemented (in this case the interface being asked for was IID_IClassFactory, which makes sense since it was CoGetClassObject making the call). Seemed like a pretty bad and basic bug (see code below):

STDMETHODIMP CGenericCF::QueryInterface( IN REFIID riid, OUT LPVOID FAR* ppvObj )

{
//Just Addref and return if it is supported
ASSERTND( NULL != ppvObj );

if( NULL == ppvObj )
return E_INVALIDARG;

*ppvObj = NULL;

if( IsEqualIID( IID_IClassFactory, riid )
IsEqualIID( IID_IUnknown, riid ) )
{
AddRef();
*ppvObj = (VOID*)this;
// NOTE THE MISSING RETURN OF S_OK HERE?!?
}

return E_NOINTERFACE; // ALWAYS RETURNING THIS..
}

So how the heck has this been working properly in production? I then got back in the debugger, and realized the following:

ole32!CoGetClassObject was making the QueryInterface call into the Class Factory (with the above buggy implementation). But ASP Server.CreateObject was not calling CoGetClassObject and not calling the QueryInterface on the class factory!

That is why it worked in production! The bug fix was required for .Net interoperability, since .Net also called CoGetClassObject.

COM programming allows you to use a components class factory or not, using the class factory requires a call to CoGetClassObject , as opposed to just calling CoCreateInstance for the class you want to create. (Check out the Related Links below for more information on this).

From my testing/debugging, this is what calls ole32!CoGetClassObject (uses class factory)
VBScript CreateObject: Yes
ASP Server.CreateObject: No
WScript.CreateObject (CScript): No
.Net COM Interop (New): Yes

Technologies
COM, VBScript, Legacy ASP, Windows Scripting Host, Visual Studio 6, VS.Net 2003, 2005

Note: For legacy ASP, you should ALWAYS be using Server.CreateObject.

Related Links

Please let me know if you have anything to add, hope this is helpful.