COM Interop apartment issue - QueryInterface failing?!?

I have a solution consisting of two projects, an in-process COM DLL server and a .NET 2.0 C# windows application. (this is a simplification of my issue, but I've manged to repro it with this setup).

(A lot of code follows).

The COM component's IDL is:
[
uuid(703642F5-3FF0-4b47-A856-8B7A0878EFCD),
version(1.0),
helpstring("SimpleCOMLibrary")
]

library SimpleCOMLibrary
{

[object, uuid(B51D2FCA-2258-44c2-961F-C7F52DFA2833)]
interface ISquare : IUnknown
{
HRESULT SquareFloat([in] float value,[out,retval] float *retval);
}

importlib("stdole2.tlb");
[
uuid(50D5FA7D-B195-42a9-963C-B741D69E7F28),
version(1.0),
helpstring("SimpleComLibraryClassObject")
]
coclass SquareClass
{
[default] interface ISquare;
}
}

The basics of the coclass/class factory implementation in C++ is:

class SquareImpl : public ISquare
{
public:
STDMETHODIMP_(ULONG) AddRef(){return 1;}
STDMETHODIMP_(ULONG) Release(){return 1;}
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
{
*ppvObject = NULL;
if(riid == __uuidof(ISquare) || riid == __uuidof(IUnknown))
{
*ppvObject = this;
}
if(*ppvObject)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP SquareFloat(float in, float *out)
{
*out = in * in;
return S_OK;
}
};

SquareImpl s_Square;


class CSquareClassFactory : public IClassFactory
{
public:
STDMETHODIMP IClassFactory::CreateInstance( IUnknown *pUnkOuter, REFIID riid, void **ppvObject )
{
if( pUnkOuter ) return CLASS_E_NOAGGREGATION;
return s_Square.QueryInterface(riid, ppvObject);
}

STDMETHODIMP IClassFactory::LockServer( BOOL bLock )
{
if( bLock ){InterlockedIncrement( &m_LockCount );}
else{InterlockedDecrement( &m_LockCount );}
return S_OK;
}
public: // IUnknown
STDMETHODIMP_(ULONG) AddRef( void ){return 2;}
STDMETHODIMP_(ULONG) Release( void ){return 1;}
STDMETHODIMP QueryInterface( REFIID riid, void **ppvObject )
{
// find interface
//
if( riid == IID_IClassFactory || riid == IID_IUnknown )
{
*ppvObject = static_cast<IClassFactory*>(this);
}
else
{
// interface not found...
//
*ppvObject = 0;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
private:
LONG m_LockCount;
};

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID FAR* ppv)
{
static CSquareClassFactory s_squarefactory;
*ppv = NULL;
return s_squarefactory.QueryInterface( riid, ppv );
}


Registration code is hidden. However, here are the registry keys, dumped from regedit's export function.

Typelib:

[HKEY_CLASSES_ROOT\TypeLib\{703642F5-3FF0-4B47-A856-8B7A0878EFCD}]
[HKEY_CLASSES_ROOT\TypeLib\{703642F5-3FF0-4B47-A856-8B7A0878EFCD}\1.0]
@="SimpleCOMTest"
[HKEY_CLASSES_ROOT\TypeLib\{703642F5-3FF0-4B47-A856-8B7A0878EFCD}\1.0\0]
[HKEY_CLASSES_ROOT\TypeLib\{703642F5-3FF0-4B47-A856-8B7A0878EFCD}\1.0\0\win32]
@="C:\\Documents and Settings\\ddeptford\\My Documents\\Visual Studio 2005\\Projects\\CSharp_Apartment_Test\\debug\\SimpleCOMServer.dll"

coclass:

[HKEY_CLASSES_ROOT\CLSID\{50D5FA7D-B195-42A9-963C-B741D69E7F28}]
@="SimpleCOMServer SquareClass"
[HKEY_CLASSES_ROOT\CLSID\{50D5FA7D-B195-42A9-963C-B741D69E7F28}\InprocServer32]
@="C:\\Documents and Settings\\ddeptford\\My Documents\\Visual Studio 2005\\Projects\\CSharp_Apartment_Test\\debug\\SimpleCOMServer.dll"
"ThreadingModel"="Both"
[HKEY_CLASSES_ROOT\CLSID\{50D5FA7D-B195-42A9-963C-B741D69E7F28}\ProgID]
@="SimpleCOMServer.SquareClass.1"
[HKEY_CLASSES_ROOT\CLSID\{50D5FA7D-B195-42A9-963C-B741D69E7F28}\TypeLib]
@="{703642F5-3FF0-4B47-A856-8B7A0878EFCD}"

interface:
[HKEY_CLASSES_ROOT\Interface\{B51D2FCA-2258-44C2-961F-C7F52DFA2833}]
@="ISquare"
[HKEY_CLASSES_ROOT\Interface\{B51D2FCA-2258-44C2-961F-C7F52DFA2833}\ProxyStubClsid]
@="{00020424-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Interface\{B51D2FCA-2258-44C2-961F-C7F52DFA2833}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Interface\{B51D2FCA-2258-44C2-961F-C7F52DFA2833}\TypeLib]
@="{703642F5-3FF0-4B47-A856-8B7A0878EFCD}"
"Version"="1.0"

The GUID that the ProxyStubClsid points to is that of the standard marshaller. I used Larry Osterman's post at http://blogs.msdn.com/larryosterman/archive/2006/01/09/510856.aspx as the basis for using these keys.


Now I have a simple .NET 2.0 application, really just the default C# Windows app. It has a reference to the registered SimpleCOMLibrary COM object, and has generated an interop assembly. I've added a button, and have written the following code:


SimpleCOMLibrary.SquareClass m_Square = null;
public Form1()
{
InitializeComponent();
m_Square = new SimpleCOMLibrary.SquareClassClass();
}

private void button1_Click(object sender, EventArgs e)
{
System.Console.WriteLine("{0}\n", m_Square.SquareFloat(123));
(new Thread(new ThreadStart(DoSomething))).Start();
}

void DoSomething()
{
System.Console.WriteLine("{0}\n", m_Square.SquareFloat(123));
}

The main thread HAS to be set as [STAThread]. In the larger application I'm working on, OLE Drag & Drop is used, and we get an error if the main thread's apartment is [MTAThread].

Running this code, the first SquareFloat method call succeeds and prints the output to the console. The second one fails, with the exception:

System.InvalidCastException was unhandled
Message="Unable to cast COM object of type 'SimpleCOMLibrary.SquareClassClass' to interface type 'SimpleCOMLibrary.ISquare'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{B51D2FCA-2258-44C2-961F-C7F52DFA2833}' failed due to the following error: Unspecified error (Exception from HRESULT: 0x80004005 (E_FAIL))."
Source="Interop.SimpleCOMLibrary"
StackTrace:
at SimpleCOMLibrary.SquareClassClass.SquareFloat(Single value)
at CSharp_Apartment_Test.Form1.DoSomething() in C:\Documents and Settings\ddeptford\My Documents\Visual Studio 2005\Projects\CSharp_Apartment_Test\CSharp_Apartment_Test\Form1.cs:line 29
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()


I'm at a complete loss as to why it's not working properly. I thought that by using the default marshaller, the COM runtime used information available in the type library to marshal the call across apartment boundaries. I can't see anything wrong with the QueryInterface methods in the COM object I've written. It works with the main application thread set to [MTAThread], but for the application I'm putting this in this cannot be done.

Any help would be greatly appreciated!

Thanks

Daniel



Answer this question

COM Interop apartment issue - QueryInterface failing?!?

  • VinceExtense

    Check this thread...


  • elianaca

    nobugz wrote:
    Check this thread...

    I tried setting my new thread to STAThread using

    SetApartmentState(ApartmentState.STA);

    and the error was still manifested itself. Still, using a proper proxy instead of typelib worked.


  • Jan Byvaly

    Okay, I've solved this now by biting the bullet and using the MIDL generated files to create a proxy DLL. I'm still not sure why using the type library didn't work, but hey :)
  • COM Interop apartment issue - QueryInterface failing?!?